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


Answers

Citar:

Las matrices de tipos genéricos no están permitidas porque no son sólidas. El problema se debe a la interacción de las matrices de Java, que no son estáticamente sanas sino que se controlan dinámicamente, con genéricos, que son estáticamente sanos y no se controlan dinámicamente. Aquí es cómo 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 matrices estáticamente seguras (también conocidas como Variance) bute que se rechazaron para 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

Question

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

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

Pude entender .NET no nos permitió hacer eso, ya que en .NET usted tiene tipos de valores que en tiempo de ejecución pueden tener diferentes tamaños, pero en Java todos los tipos de T serán referencias de objetos, teniendo así el mismo tamaño ( Corrígeme si estoy equivocado).

¿Cual es la razon?




Sé que llegué un poco tarde a la fiesta aquí, pero pensé que podría ayudar a futuros usuarios de Google, ya que ninguna de estas respuestas solucionó mi problema. La respuesta de Ferdi265 ayudó inmensamente sin embargo.

Intento 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 tipo genérico para mí:

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



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

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

¡Así que es una mentira, puedes crear instancias genéricas, a través de varargs!

Por supuesto, los problemas con las 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 la matriz genérica .

Por otro lado, hemos estado utilizando varargs genéricos durante una década, y el cielo no está cayendo aún. 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 eso.

Y podemos señalar foo2 para refutar la afirmación de que las especificaciones nos impiden los problemas de los que nos alejan. Si Sun tuviera más tiempo y recursos para 1.5 , creo que podrían haber alcanzado una resolución más satisfactoria.




La razón por la que esto es imposible es que Java implementa sus genéricos puramente en el nivel del compilador, y solo se genera un archivo de clase para cada clase. Esto se llama Tipo Borrado .

En tiempo de ejecución, la clase compilada necesita manejar todos sus usos con el mismo bytecode. Entonces, la new T[capacity] no tendría absolutamente ninguna idea de qué tipo necesita ser instanciado.




Las matrices son covariantes

Se dice que las matrices son covariantes, lo que básicamente significa que, dadas las reglas de subtipificación 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 subtipificación 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 así también es válido:

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

Debido a que de acuerdo con las reglas de subtipado en Java, una matriz Integer [] es un subtipo de una matriz Number [] porque Integer es un subtipo de Number.

Pero esta regla de subtipificación puede conducir 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 muy 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 estamos accediendo a la matriz a través de una referencia numérica es irrelevante aquí, 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 las matrices son lo que llamamos un tipo reificable. Esto significa que, en tiempo de ejecución, Java sabe que esta matriz en realidad se creó 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, otra cosa es el tipo de referencia que usamos para acceder a ella, ¿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 una vez que se realiza la compilación del código; por lo tanto, este tipo de información 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 el 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 forma de garantizar que no estamos cometiendo contaminación acumulativa.

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 manera, en tiempo de ejecución, de determinar que esta lista se supone que es una lista de enteros solamente. El tiempo de ejecución de Java nos permitiría poner lo que deseamos en esta lista, cuando solo debería contener enteros, porque cuando se creó, se declaró como una lista de enteros. Es por eso que el compilador rechaza la línea 4 porque no es segura y, si se permite, puede romper las suposiciones del sistema de tipos.

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

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

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




Seguramente debe haber una buena forma de evitarlo (tal vez usando el reflejo), 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 en 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.

Por lo tanto, una forma de toArray(T[] a) sería usar esta función, es decir, crear una ArrayList de los objetos que desea en la matriz y luego usar toArray(T[] a) para crear la matriz real. No sería rápido, pero no mencionó sus requisitos.

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




Me gusta la respuesta indirecta dada por Gafter . Sin embargo, propongo que está mal. Cambié el código de Gafter un poco. Se compila y funciona por un tiempo y 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();
        }
    }
}

El resultado 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)

Entonces me parece que puedes crear tipos genéricos de arreglos en java. ¿He malinterpretado la pregunta?




Related