tipos tipo objeto manejo instanciar genericos generico genericas datos crear con comparar comodin como clases arreglos java generics type-erasure

objeto - ¿Cuál es la razón por la que no puedo crear tipos de arreglos genéricos en Java?



tipo comodin java (14)

¿Cuál es la razón por la que Java no nos permite hacer?

private T[] elements = new T[initialCapacity];

Pude entender que .NET no nos permitió hacerlo, ya que en .NET tiene tipos de valor que en tiempo de ejecución pueden tener diferentes tamaños, pero en Java todos los tipos de T serán referencias de objetos, por lo tanto, tendrán el mismo tamaño ( corrígeme si me equivoco).

¿Cual es la razon?


Al no proporcionar una solución decente, simplemente terminas con algo peor IMHO.

El trabajo común es como sigue.

T[] ts = new T[n];

se reemplaza por (asumiendo que T extiende el objeto y no otra clase)

T[] ts = (T[]) new Object[n];

Prefiero el primer ejemplo, sin embargo, los tipos más acedémicos parecen preferir el segundo, o simplemente prefieren no pensar en ello.

La mayoría de los ejemplos de por qué no puede usar un Objeto [] se aplica igualmente a la Lista o Colección (que son compatibles), por lo que los veo como argumentos muy pobres.

Nota: esta es una de las razones por las que la biblioteca de Colecciones no se compila sin advertencias. Si no se puede admitir este caso de uso sin advertencias, algo se rompe con el modelo genérico IMHO.


Citar:

Arreglos de tipos genéricos no están permitidos porque no son sólidos. El problema se debe a la interacción de las matrices de Java, que no son estáticas, sino que se verifican dinámicamente, con genéricos, que son estáticas y no se controlan dinámicamente. Aquí es cómo se podría explotar la laguna:

class Box<T> { final T x; Box(T x) { this.x = x; } } class Loophole { public static void main(String[] args) { Box<String>[] bsa = new Box<String>[3]; Object[] oa = bsa; oa[0] = new Box<Integer>(3); // error not caught by array store check String s = bsa[0].x; // BOOM! } }

Habíamos propuesto resolver este problema utilizando arreglos estáticamente seguros (también conocido como Varianza), pero fue rechazado por Tiger.

- gafter

(Creo que es Neal Gafter , pero no estoy seguro)

Véalo en contexto aquí: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316


Desde el tutorial de Oracle :

No se pueden crear matrices de tipos parametrizados. Por ejemplo, el siguiente código no se compila:

List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error

El siguiente código ilustra lo que sucede cuando se insertan diferentes tipos en una matriz:

Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown.

Si intentas lo mismo con una lista genérica, habría un problema:

Object[] stringLists = new List<String>[]; // compiler error, but pretend it''s allowed stringLists[0] = new ArrayList<String>(); // OK stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown, // but the runtime can''t detect it.

Si se permitieran las matrices de listas parametrizadas, el código anterior fallaría para lanzar la ArrayStoreException deseada.

Para mí, suena muy débil. Creo que cualquier persona con una comprensión suficiente de los genéricos estaría perfectamente bien, e incluso espera, que la excepción ArrayStoredException no se lance en tal caso.


En mi caso, simplemente quería una serie de pilas, algo como esto:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

Como esto no era posible, utilicé lo siguiente como solución alternativa:

  1. Creó una clase de envoltorio no genérico alrededor de Stack (por ejemplo, MyStack)
  2. MyStack [] apila = el nuevo MyStack [2] funcionó perfectamente bien

Feo, pero Java es feliz.

Nota: como lo mencionó BrainSlugs83 en el comentario a la pregunta, es totalmente posible tener matrices de genéricos en .NET


Es porque los arreglos de Java (a diferencia de los genéricos) contienen, en tiempo de ejecución, información sobre su tipo de componente. Por lo tanto, debe conocer el tipo de componente cuando cree la matriz. Como no sabe qué es T en tiempo de ejecución, no puede crear la matriz.


La razón por la que esto es imposible es que Java implementa sus Genéricos únicamente en el nivel del compilador, y solo se genera un archivo de clase para cada clase. Esto se denomina tipo de borrado .

En tiempo de ejecución, la clase compilada necesita manejar todos sus usos con el mismo código de bytes. Por lo tanto, la new T[capacity] no tendría absolutamente ninguna idea de qué tipo se necesita instanciar.


La razón principal se debe al hecho de que los arreglos en Java son covariantes.

Hay una buena visión general here .


La respuesta ya se dio, pero si ya tiene una instancia de T, puede hacer esto:

T t; //Assuming you already have this object instantiated or given by parameter. int length; T[] ts = (T[]) Array.newInstance(t.getClass(), length);

Espero, podría ayudar, Ferdi265


Me gusta la respuesta dada indirectamente por Gafter . Sin embargo, propongo que está mal. Cambié un poco el código de Gafter. Se compila y se ejecuta durante un tiempo, luego bombardea donde Gafter predijo que lo haría

class Box<T> { final T x; Box(T x) { this.x = x; } } class Loophole { public static <T> T[] array(final T... values) { return (values); } public static void main(String[] args) { Box<String> a = new Box("Hello"); Box<String> b = new Box("World"); Box<String> c = new Box("!!!!!!!!!!!"); Box<String>[] bsa = array(a, b, c); System.out.println("I created an array of generics."); Object[] oa = bsa; oa[0] = new Box<Integer>(3); System.out.println("error not caught by array store check"); try { String s = bsa[0].x; } catch (ClassCastException cause) { System.out.println("BOOM!"); cause.printStackTrace(); } } }

La salida es

I created an array of generics. error not caught by array store check BOOM! java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Loophole.main(Box.java:26)

Así que me parece que puedes crear tipos de arreglos genéricos en Java. ¿Entendí mal la pregunta?


Sé que llego un poco tarde a la fiesta aquí, pero me di cuenta de que podría ayudar a cualquier futuro de Google, ya que ninguna de estas respuestas solucionó mi problema. Sin embargo, la respuesta de Ferdi265 ayudó enormemente.

Estoy tratando de crear mi propia lista vinculada, por lo que el siguiente código es lo que funcionó para mí:

package myList; import java.lang.reflect.Array; public class MyList<TYPE> { private Node<TYPE> header = null; public void clear() { header = null; } public void add(TYPE t) { header = new Node<TYPE>(t,header); } public TYPE get(int position) { return getNode(position).getObject(); } @SuppressWarnings("unchecked") public TYPE[] toArray() { TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size()); for(int i=0 ; i<size() ; i++) result[i] = get(i); return result; } public int size(){ int i = 0; Node<TYPE> current = header; while(current != null) { current = current.getNext(); i++; } return i; }

En el método toArray () se encuentra la manera de crear una matriz de un tipo genérico para mí:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());


Se debe a que los genéricos se agregaron a Java después de que lo crearon, por lo que es un poco torpe porque los creadores originales de Java pensaron que al hacer una matriz, el tipo se especificaría en su fabricación. Así que eso no funciona con genéricos, así que tienes que hacer E [] array = (E []) nuevo Objeto [15]; Esto compila pero da una advertencia.


Seguramente debe haber una buena forma de evitarlo (tal vez utilizando la reflexión), porque me parece que eso es exactamente lo que hace ArrayList.toArray(T[] a) . Yo cito:

public <T> T[] toArray(T[] a)

Devuelve una matriz que contiene todos los elementos de esta lista en el orden correcto; el tipo de tiempo de ejecución de la matriz devuelta es el de la matriz especificada. Si la lista se ajusta a la matriz especificada, se devuelve allí. De lo contrario, se asigna una nueva matriz con el tipo de tiempo de ejecución de la matriz especificada y el tamaño de esta lista.

Así que una forma de toArray(T[] a) sería utilizar esta función, es decir, crear una lista de ArrayList de los objetos que desea en el arreglo, luego usar toArray(T[] a) para crear el arreglo real. No sería rápido, pero no mencionaste tus requisitos.

Entonces, ¿alguien sabe cómo se toArray(T[] a) ?


Si no podemos crear instancias de arreglos genéricos, ¿por qué el lenguaje tiene tipos de arreglos genéricos? ¿Para qué sirve tener un tipo sin objetos?

La única razón por la que puedo pensar es en varargs - foo(T...) . De lo contrario, podrían haber borrado completamente los tipos de arreglos genéricos. (Bueno, realmente no tenían que usar array para varargs, ya que varargs no existía antes del 1.5 . Ese es probablemente otro error).

Así que es una mentira, puedes instanciar arreglos genéricos, a través de varargs!

Por supuesto, los problemas con matrices genéricas siguen siendo reales, por ejemplo

static <T> T[] foo(T... args){ return args; } static <T> T[] foo2(T a1, T a2){ return foo(a1, a2); } public static void main(String[] args){ String[] x2 = foo2("a", "b"); // heap pollution! }

Podemos usar este ejemplo para demostrar realmente el peligro de una matriz genérica .

Por otro lado, hemos estado utilizando varargs genéricos durante una década, y el cielo todavía no está cayendo. Entonces podemos argumentar que los problemas están siendo exagerados; no es un gran problema. Si se permite la creación explícita de matrices genéricas, tendremos errores aquí y allá; pero hemos estado acostumbrados a los problemas de borrado, y podemos vivir con él.

Y podemos apuntar a foo2 para refutar la afirmación de que la especificación nos evita los problemas de los que afirman que nos evitan. Si Sun tuviera más tiempo y recursos para 1.5 , creo que podrían haber alcanzado una resolución más satisfactoria.


Las matrices son covariantes

Se dice que las matrices son covariantes, lo que básicamente significa que, dadas las reglas de subtipo de Java, una matriz de tipo T [] puede contener elementos de tipo T o cualquier subtipo de T. Por ejemplo

Number[] numbers = newNumber[3]; numbers[0] = newInteger(10); numbers[1] = newDouble(3.14); numbers[2] = newByte(0);

Pero no solo eso, las reglas de subtipo de Java también establecen que una matriz S [] es un subtipo de la matriz T [] si S es un subtipo de T, por lo tanto, algo como esto también es válido:

Integer[] myInts = {1,2,3,4}; Number[] myNumber = myInts;

Debido a que de acuerdo con las reglas de subtipo en Java, un entero de la matriz [] es un subtipo de un Número de la matriz [] porque el entero es un subtipo de Número.

Pero esta regla de subtipo puede llevar a una pregunta interesante: ¿qué pasaría si tratamos de hacer esto?

myNumber[0] = 3.14; //attempt of heap pollution

Esta última línea se compilaría bien, pero si ejecutamos este código, obtendríamos una ArrayStoreException porque estamos tratando de poner un doble en una matriz de enteros. El hecho de que estemos accediendo a la matriz a través de una referencia numérica es irrelevante, lo que importa es que la matriz es una matriz de enteros.

Esto significa que podemos engañar al compilador, pero no podemos engañar al sistema de tipo de tiempo de ejecución. Y esto es así porque los arreglos son lo que llamamos un tipo confiable. Esto significa que, en tiempo de ejecución, Java sabe que esta matriz se creó en realidad como una matriz de enteros a los que simplemente se accede mediante una referencia de tipo Número [].

Entonces, como podemos ver, una cosa es el tipo real del objeto, y otra cosa es el tipo de referencia que usamos para acceder a él, ¿verdad?

El problema con los genéricos de Java

Ahora, el problema con los tipos genéricos en Java es que el compilador descarta la información de tipo para los parámetros de tipo después de que se realiza la compilación del código; por lo tanto, esta información de tipo no está disponible en tiempo de ejecución. Este proceso se llama borrado de tipo. Existen buenas razones para implementar genéricos como este en Java, pero esa es una larga historia, y tiene que ver con la compatibilidad binaria con código preexistente.

El punto importante aquí es que dado que en el tiempo de ejecución no hay información de tipo, no hay manera de asegurarnos de que no estamos cometiendo la contaminación del montón.

Consideremos ahora el siguiente código inseguro:

List<Integer> myInts = newArrayList<Integer>(); myInts.add(1); myInts.add(2); List<Number> myNums = myInts; //compiler error myNums.add(3.14); //heap polution

Si el compilador de Java no nos impide hacer esto, el sistema de tipo de tiempo de ejecución tampoco puede detenernos, porque no hay forma, en tiempo de ejecución, de determinar que se suponía que esta lista era solo una lista de enteros. El tiempo de ejecución de Java nos permitiría poner lo que queramos en esta lista, cuando solo debe contener números enteros, porque cuando se creó, se declaró como una lista de números enteros. Es por eso que el compilador rechaza la línea número 4 porque no es seguro y, si se permite, podría romper las suposiciones del sistema de tipos.

Como tales, los diseñadores de Java se aseguraron de que no podamos engañar al compilador. Si no podemos engañar al compilador (como podemos hacer con matrices), tampoco podemos engañar al sistema de tipo de tiempo de ejecución.

Como tal, decimos que los tipos genéricos no son verificables, ya que en el tiempo de ejecución no podemos determinar la verdadera naturaleza del tipo genérico.

Salté algunas partes de estas respuestas, puedes leer el artículo completo aquí: https://dzone.com/articles/covariance-and-contravariance