programacion genericas generica clases java generics unbounded-wildcard

java - genericas - Irregularidades con el tipo genérico comodín(?)



java generics (4)

Creo que el tipo ? En genéricos es un tipo desconocido específico . Lo que significa que declarar, digamos, una lista de ese tipo nos impediría agregar cualquier tipo de objeto en ella.

List<?> unknownList; unknownList.add(new Object()); // This is an error.

El compilador da un error como se esperaba.

Pero cuando el tipo desconocido es un genérico de segundo nivel, al compilador no parece importarle.

class First<T> {} List<First<?>> firstUnknownList; // All these three work fine for some reason. firstUnknownList.add(new First<>()); firstUnknownList.add(new First<Integer>()); firstUnknownList.add(new First<String>());

Pensé que probablemente al compilador no le importan los parámetros genéricos en el segundo nivel, pero no es el caso,

List<First<Integer>> firstIntegerList; firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.

Entonces, ¿por qué el compilador nos permite agregar cualquier tipo de elemento cuando solo un elemento desconocido (y por lo tanto nada) es aceptable en el segundo ejemplo?

Nota: Compilador Java 1.8


Creo que el tipo? En genéricos es un tipo desconocido específico.

Esto es un poco inexacto. Sí, un tipo de comodín representa un tipo desconocido, pero puede representar diferentes tipos en diferentes momentos:

List<?> list = new ArrayList<String>(); list = new ArrayList<Integer>();

El único invariante es que una expresión cuyo tipo contenga un comodín siempre producirá un valor cuyo tipo se ajuste a ese comodín. Dado que cada valor tiene un tipo que no es solo un comodín, se podría decir que comodín significa un (más) tipo "específico" en cualquier momento.


Cambiaré la First interfaz a la interfaz de Box

Box<?> uknownBox caja gris con algo en ella

Box<Apple> appleBox caja Box<Apple> appleBox con manzana

List<Box<Apple>> appleBoxList muchas cajas con manzanas

List<Box<?>> uknownBoxList muchos cuadros grises desconocidos

appleBoxList.add(new Box<Orange>()) - no se puede agregar cuadro con naranjas a la lista de cuadros de apple

unknownBoxList.add(new Box<?>()) : no sabemos qué hay en esas casillas grises, al agregar una caja gris desconocida más no cambia nada

unknownBoxList.add(new Box<Orange>()) - same rules when you add specific boxes unknownBoxList.add(new Box<Apple>()) - since you are not allowed to ''open'' them

unknownBoxList = appleBoxList esto no se compila para evitar agregar cuadros grises (posiblemente no de apple) a la lista de apple box. Por eso la operación anterior es legal.


Puede agregar cualquier cosa a una List<T> que pueda almacenar en una referencia de tipo T :

T item = ... List<T> list = new ArrayList<>(); list.add(item);

First<?> Es un supertipo de First<T> ; para que pueda almacenar una referencia a un First<T> en una variable de tipo First<?> :

First<?> first = new First<String>();

Entonces, sustituyendo T por la First<?> Arriba:

First<?> item = new First<String>(); List<First<?>> list = new ArrayList<>(); list.add(item);

Todo lo que está sucediendo en el ejemplo de OP es que se omite el item variable temporal:

firstUnknownList.add(new First<String>());

Sin embargo, si lo hace con el ejemplo firstIntegerList :

First<Integer> item = new First<String>(); // Compiler error. List<First<Integer>> list = new ArrayList<>(); list.add(item);

está claro por qué eso no está permitido: no puedes hacer la asignación del item .

También es posible ver que no se puede hacer nada inseguro con el contenido de esa lista.

Si agrega un par de métodos a la interfaz:

interface First<T> { T producer(); void consumer(T in); }

Ahora, considere lo que puede hacer con los elementos que agregó a la lista:

for (First<?> first : firstUnknownList) { // OK at compile time; OK at runtime unless the method throws an exception. Object obj = first.producer(); // OK at compile time; may fail at runtime if null is not an acceptable parameter. first.consumer(null); // Compiler error - you can''t have a reference to a ?. first.consumer(/* some maybe non-null value */); }

así que en realidad no hay nada que puedas hacer con los elementos de esa lista que podrían violar la seguridad de los tipos (siempre que no hagas nada deliberado para violarlos, como usar tipos sin formato). Puede demostrar que los métodos genéricos de productor / consumidor son igualmente seguros o prohibidos por el compilador.

Así que no hay razón para no permitirte hacer esto.


Se trata de subtipo / supertipo-relaciones.

List<?> Es una lista que contiene elementos de un tipo desconocido (pero específico). Nunca se sabe qué tipo está contenido exactamente en esta lista. Por lo tanto, no puede agregarle objetos, porque pueden ser del tipo incorrecto:

List<Integer> ints = new ArrayList<Integer>(); List<?> unknowns = ints; // It this worked, the list would contain a String.... unknowns.add("String"); // ... and this would crash with some ClassCastException Integer i = ints.get(0);

También puede estar claro que puedes hacer

List<Number> numbers = null; Integer integer = null; numbers.add(integer);

Esto funciona porque Number es un supertipo verdadero de Integer . No viola el tipo de seguridad para agregar un objeto de un tipo más específico a una lista.

El punto clave con respecto al segundo ejemplo es:

First<?> Es un supertipo de cada First<T>

Siempre puedes hacer algo como

First<Integer> fInt = null; First<Integer> fString = null; First<?> f = null; f = fInt; // works f = fString; // works

Por lo tanto, la razón por la cual puede agregar una First<String> a una List<First<?>> es la misma razón por la que puede agregar un Integer a una List<Number> : el elemento que desea agregar es verdadero. Subtipo de los elementos que se esperan en la lista.