java - parameter - ArrayList<Integer> toma String
java generic type inheritance (6)
public class Main {
public static void main(String[] args) {
ArrayList<Integer> ar = new ArrayList<Integer>();
List l = new ArrayList();
l.add("a");
l.add("b");
ar.addAll(l);
System.out.println(ar);
}
}
Salida: [a,b]
No se puede agregar directamente String
a ArrayList<Integer> ar
, pero al usar addAll()
es posible.
¿Cómo podemos agregar String
a ArrayList
cuyo tipo ya ha sido especificado como Integer
? ¿Alguien puede resaltar detalles claros de implementación y la razón detrás de esto?
Pero, ¿cómo podemos agregar cadenas a la lista de arrays cuyo tipo ya ha sido especificado como entero?
Debido a la forma en que los genéricos de Java fueron diseñados para compatibilidad con versiones anteriores, con borrado de tipos y tipos crudos, básicamente.
En el momento de la ejecución, no hay elementos como ArrayList<Integer>
; solo hay una ArrayList
. Está utilizando la List
tipo sin List
, por lo que el compilador no está haciendo ninguna de sus comprobaciones normales, ya sea en tiempo de compilación o agregando conversiones en tiempo de ejecución.
El compilador te advierte que estás haciendo cosas inseguras:
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
... y cuando recompile con la bandera correspondiente, advertirá sobre todo, incluida probablemente la línea más sorprendente:
ar.addAll(l);
Esa es la que me sorprende un poco, en términos de compilación: creo que de hecho se está confiando en que la List
es una Collection<? extends Integer>
Collection<? extends Integer>
realmente, cuando sabemos que no lo es.
Si evita usar tipos crudos, este tipo de desorden desaparece.
Cuando nació, Java no tenía genéricos (es decir, clases que están parametrizadas por otra clase). Cuando se agregaron genéricos, para mantener la compatibilidad, se decidió no cambiar el bytecode de Java y el formato de archivo de clase. Entonces, las clases genéricas son transformadas por el compilador en las no genéricas. Esto significa que una ArrayList en realidad está almacenando instancias de Object de clase, por lo que también puede aceptar instancias de String (que es una subclase de Object). El compilador no siempre puede detectar usos indebidos.
Estás usando un tipo sin procesar. Si usa List<String> l = new ArrayList<>()
, encontrará que su código ya no se compilará. Los tipos en bruto existen solo para compatibilidad con versiones anteriores y no deben usarse en códigos nuevos.
Olvidaste el tipo de tu segunda lista. Dejando el tipo de la primera lista también puede hacer esto:
ArrayList ar = new ArrayList();
ar.add(Integer.valueOf(42));
ar.add("Hello");
El tipo solo se considera en tiempo de compilación. Es por eso que puede recibir una advertencia en eclipse. En el código de bytes, el tipo no se considera y su aplicación se ejecuta sin excepción.
Se trata más de mezclar tipos crudos y genéricos en el sistema de tipos de Java que de borrar tipos. Permítanme aumentar el fragmento de código de la pregunta:
ArrayList<Integer> ar = new ArrayList<Integer>();
List l = new ArrayList(); // (1)
l.add("a");
l.add("b");
ar.addAll(l); // (2)
System.out.println(ar);
Integer i = ar.get(0); // (3)
Con los genéricos borrados de hoy, la línea (3) arroja ClassCastException
. Si los genéricos de Java se reificaron, es fácil suponer que la verificación del tipo de tiempo de ejecución provocaría una excepción en la línea (2). Ese sería un posible diseño de genéricos reificados, pero otros diseños podrían no hacer esa comprobación. Por qué no? Principalmente por la misma razón que hoy hemos eliminado los genéricos: la compatibilidad de la migración.
Neal Gafter observó en su artículo Reified Generics for Java que hay muchos usos inseguros de genéricos, con moldes inadecuados, etc. Hoy, incluso más de diez años después de que se introdujeron los genéricos, todavía veo un montón de uso de tipos crudos. (Incluyendo, desafortunadamente, aquí en .) La realización incondicional de la verificación de tipo genérico reificado rompería una gran cantidad de código, lo que por supuesto sería un gran golpe para la compatibilidad.
Cualquier propuesta de reificación genérica realista debería proporcionar la reificación en forma voluntaria, como a través de la subtipificación (como en la propuesta de Gafter), o mediante anotaciones (Gerakios, Biboudis, Smaragdakis. Parámetros de tipo reutilizado utilizando anotaciones de Java. [PDF] GPSE 2013 .), y debería decidir cómo tratar con los tipos crudos. Parece completamente impráctico desautorizar completamente los tipos crudos. A su vez, permitir tipos crudos de manera efectiva significa que hay una forma de eludir el sistema de tipo genérico.
(Este tipo de decisión no se toma a la ligera. He sido testigo de gritos de enfrentamiento entre teóricos de tipo, uno de los cuales se quejaba de que el sistema de tipos de Java es incorrecto . Para un teórico de tipos, este es el más grave de los insultos).
Básicamente, eso es lo que hace este código: evita el tipo genérico que verifica la bondad mediante el uso de tipos sin formato. Incluso si los genéricos de Java se reificaron, es posible que no se verifique en la línea (2). Bajo algunos de los diseños de genéricos reificados, el código podría comportarse exactamente igual que hoy: lanzar una excepción en la línea (3).
En la respuesta de Jon Skeet , admite estar algo sorprendido de que en la línea (2) el compilador confíe en que la lista contiene elementos del tipo correcto. No se trata de confianza, después de todo, el compilador emite una advertencia aquí. Es más el compilador que dice: "Ok, estás usando tipos crudos, estás solo. Si obtienes una ClassCastException
más adelante, no es mi culpa". De nuevo, sin embargo, se trata de permitir tipos crudos para fines de compatibilidad, no borrado.
Tipo de seguridad que podemos lograr solo durante la compilación. Durante el tiempo de ejecución, el tipo de borrador borrará todas estas cosas y será un código de bytes normal, es decir, como si lo hiciéramos sin genéricos.