java - propiedades - jcheckbox
Tipo genérico anidado de Java (2)
¿Cómo se debe usar el tipo genérico Map<?, ? extends List<?>>
Map<?, ? extends List<?>>
lugar de un Map<?, List<?>>
más simple Map<?, List<?>>
para el siguiente método test()
?
public static void main(String[] args) {
Map<Integer, List<String>> mappy =
new HashMap<Integer, List<String>>();
test(mappy);
}
public static void test(Map<?, ? extends List<?>> m) {}
// Doesn''t compile
// public static void test(Map<?, List<?>> m) {}
Teniendo en cuenta que lo siguiente funciona, y que los tres métodos tienen el mismo tipo borrado de todos modos.
public static <E> void test(Map<?, List<E>> m) {}
Fundamentalmente, List<List<?>>
y List<? extends List<?>>
List<? extends List<?>>
tiene distintos argumentos de tipo.
En realidad, es el caso de que uno es un subtipo del otro, pero primero aprendamos más sobre lo que significan individualmente.
Comprender las diferencias semánticas.
En general, ¿el comodín ?
representa alguna "información faltante". Significa que "hubo una discusión de tipo aquí una vez, pero ya no sabemos qué es" . Y debido a que no sabemos qué es, se imponen restricciones sobre cómo podemos usar cualquier cosa que se refiera a ese tipo de argumento en particular.
Por el momento, simplifiquemos el ejemplo usando List
lugar de Map
.
Una
List<List<?>>
contiene cualquier tipo de Lista con cualquier argumento de tipo . Así que es decir:List<List<?>> theAnyList = new ArrayList<List<?>>(); // we can do this theAnyList.add( new ArrayList<String>() ); theAnyList.add( new LinkedList<Integer>() ); List<?> typeInfoLost = theAnyList.get(0); // but we are prevented from doing this typeInfoLost.add( new Integer(1) );
Podemos poner cualquier
List
en laList
detheAnyList
, pero al hacerlo hemos perdido el conocimiento de sus elementos .Cuando usamos
? extends
, laList
tiene algún subtipo específico de Lista, pero ya no sabemos qué es . Así que es decir:List<? extends List<Float>> theNotSureList = new ArrayList<ArrayList<Float>>(); // we can still use its elements // because we know they store Float List<Float> aFloatList = theNotSureList.get(0); aFloatList.add( new Float(1.0f) ); // but we are prevented from doing this theNotSureList.add( new LinkedList<Float>() );
Ya no es seguro agregar nada a
theNotSureList
, porque no sabemos el tipo real de sus elementos. ( ¿Era originalmente unaList<LinkedList<Float>>
? ¿O unaList<Vector<Float>>
? No lo sabemos.)Podemos juntarlos y tener una
List<? extends List<?>>
List<? extends List<?>>
. Ya no sabemos qué tipo deList
tiene, y tampoco sabemos el tipo de elemento de esasList
. Así que es decir:List<? extends List<?>> theReallyNotSureList; // these are fine theReallyNotSureList = theAnyList; theReallyNotSureList = theNotSureList; // but we are prevented from doing this theReallyNotSureList.add( new Vector<Float>() ); // as well as this theReallyNotSureList.get(0).add( "a String" );
Hemos perdido información sobre
theReallyNotSureList
, así como el tipo de elemento de laList
dentro de ella.(Pero puede observar que podemos asignarle cualquier tipo de listas que contengan listas ...)
Así que para descomponerlo:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
El Map
funciona de la misma manera, solo tiene más parámetros de tipo:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Por que ? extends
es necesario
Es posible que sepa que los tipos genéricos "concrete" tienen invariabilidad , es decir, List<Dog>
no es un subtipo de List<Animal>
incluso si la class Dog extends Animal
. En cambio, el comodín es cómo tenemos la covarianza , es decir, List<Dog>
es un subtipo de List<? extends Animal>
List<? extends Animal>
.
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
Entonces aplicando estas ideas a una List
anidada:
-
List<String>
es un subtipo deList<?>
PeroList<List<String>>
no es un subtipo deList<List<?>>
. Como se mostró anteriormente, esto nos impide comprometer la seguridad de los tipos al agregar elementos incorrectos a laList
. -
List<List<String>>
es un subtipo deList<? extends List<?>>
List<? extends List<?>>
, porque el carácter comodín delimitado permite la covarianza. Es decir,? extends
permite que el hecho de queList<String>
es un subtipo deList<?>
para ser considerado. List<? extends List<?>>
List<? extends List<?>>
es, de hecho, un supertipo compartido:List<? extends List<?>> ╱ ╲ List<List<?>> List<List<String>>
En revisión
-
Map<Integer, List<String>>
solo acepta laList<String>
como un valor. -
Map<?, List<?>>
acepta cualquierList
como un valor. -
Map<Integer, List<String>>
yMap<?, List<?>>
son tipos distintos que tienen una semántica separada. - Uno no se puede convertir al otro, para evitar que hagamos modificaciones de manera insegura.
Map<?, ? extends List<?>>
Map<?, ? extends List<?>>
es un supertipo compartido que impone restricciones seguras:Map<?, ? extends List<?>> ╱ ╲ Map<?, List<?>> Map<Integer, List<String>>
Cómo funciona el método genérico
Al usar un parámetro de tipo en el método, podemos afirmar que la List
tiene algún tipo concreto.
static <E> void test(Map<?, List<E>> m) {}
Esta declaración particular requiere que todas las List
en el Map
tengan el mismo tipo de elemento. No sabemos qué es ese tipo en realidad, pero podemos usarlo de manera abstracta. Esto nos permite realizar operaciones "a ciegas".
Por ejemplo, este tipo de declaración podría ser útil para algún tipo de acumulación:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
No podemos llamar a m
porque no sabemos cuál es su tipo de clave . Sin embargo, podemos manipular sus valores porque entendemos que todos son List
con el mismo tipo de elemento .
Sólo por diversión
Otra opción que la pregunta no discute es tener un carácter comodín acotado y un tipo genérico para la List
:
static <E> void test(Map<?, ? extends List<E>> m) {}
Podríamos llamarlo con algo como un Map<Integer, ArrayList<String>>
. Esta es la declaración más permisiva, si solo nos preocupamos por el tipo de E
También podemos usar límites para anidar parámetros de tipo:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
Esto es tanto permisivo acerca de lo que podemos pasarle, como permisivo acerca de cómo podemos manipular m
todo lo que contiene.
Ver también
- "Java Generics: ¿Qué es PECS?" por la diferencia entre
? extends
? extends
y? super
? super
- JLS 4.10.2. Subtipo entre clases y tipos de interfaz y JLS 4.5.1. Escriba Argumentos de tipos parametrizados para puntos de entrada a los detalles técnicos de esta respuesta.