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




7 Answers

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

java generics type-erasure

¿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?




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.




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




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?




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.




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.




Related