En este ejemplo:
import java.util.*;
public class Example {
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
static void function(List<? extends Number> outer)
{
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
doesntCompile()
falla al compilar con:
Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
doesntCompile(new HashMap<Integer, List<Integer>>());
^
mientras compiles()
es aceptado por el compilador.
Esta respuesta explica que la única diferencia es que <? ...>
, a diferencia , le <T ...>
permite hacer referencia al tipo más adelante, lo que no parece ser el caso.
¿Cuál es la diferencia entre <? extends Number>
y <T extends Number>
en este caso y por qué no se compila primero?
Respuestas:
Al definir el método con la siguiente firma:
e invocandolo como:
En el jls §8.1.2 encontramos que (parte interesante en negrita por mí):
En otras palabras, el tipo
T
se compara con el tipo de entrada y se asignaInteger
. La firma se convertirá efectivamentestatic void compiles(Map<Integer, List<Integer>> map)
.Cuando se trata de
doesntCompile
método, jls define reglas de subtipo ( §4.5.1 , en negrita):Esto significa que de
? extends Number
hecho contieneInteger
o inclusoList<? extends Number>
contieneList<Integer>
, pero no es el caso deMap<Integer, List<? extends Number>>
yMap<Integer, List<Integer>>
. Se puede encontrar más sobre ese tema en este hilo SO . Aún puede hacer que la versión con?
comodín funcione declarando que espera un subtipo deList<? extends Number>
:fuente
? extends Number
más que? extends Numeric
. [2] Su afirmación de que "no es el caso para la Lista <? Extiende el Número> y la Lista <Integer>" es incorrecta. Como @VinceEmigh ya ha señalado, puede crear un métodostatic void demo(List<? extends Number> lst) { }
y llamarlo asídemo(new ArrayList<Integer>());
o asídemo(new ArrayList<Float>());
, y el código se compila y se ejecuta OK. ¿O tal vez estoy interpretando mal o entendiendo mal lo que usted dijo?List<? extends Number>
un parámetro tipo de todo el mapa, no a sí mismo. Muchas gracias por el comentario.List<Number>
no contieneList<Integer>
. Supongamos que tienes una funciónstatic void check(List<Number> numbers) {}
. Cuando invocar concheck(new ArrayList<Integer>());
él no se compila, debe definir el método comostatic void check(List<? extends Number> numbers) {}
. Con el mapa es igual pero con más anidamiento.Number
es un parámetro de tipo de lista y necesita agregarlo? extends
para que sea covariante,List<? extends Number>
es un parámetro de tipoMap
y también necesita? extends
covarianza.En la llamada:
T coincide con Integer, por lo que el tipo de argumento es a
Map<Integer,List<Integer>>
. No es el caso para el métododoesntCompile
: el tipo de argumento permaneceMap<Integer, List<? extends Number>>
sea cual sea el argumento real en la llamada; y eso no es asignable desdeHashMap<Integer, List<Integer>>
.ACTUALIZAR
En el
doesntCompile
método, nada te impide hacer algo como esto:Obviamente, no puede aceptar a
HashMap<Integer, List<Integer>>
como argumento.fuente
doesntCompile
Entonces, ¿cómo sería una llamada válida ? Solo curiosidad por eso.doesntCompile(new HashMap<Integer, List<? extends Number>>());
funcionaría, como lo haríadoesntCompile(new HashMap<>());
.HashMap<Integer, List<Integer>>
", ¿podría explicar por qué no se puede asignar?Ejemplo simplificado de demostración. El mismo ejemplo se puede visualizar como a continuación.
List<Pair<? extends Number>>
es un tipo de comodines de varios niveles, mientras queList<? extends Number>
es un tipo de comodín estándar.Las instancias concretas válidas del tipo comodín
List<? extends Number>
incluyenNumber
y cualquier subtipo deNumber
mientras que en caso deList<Pair<? extends Number>>
que sea un argumento tipo de argumento tipo y en sí mismo tenga una instanciación concreta del tipo genérico.Los genéricos son invariables, por lo que
Pair<? extends Number>
el tipo de comodín solo puede aceptarPair<? extends Number>>
. El tipo interno? extends Number
ya es covariante. Debe hacer que el tipo de cierre sea covariante para permitir la covarianza.fuente
<Pair<Integer>>
no funciona<Pair<? extends Number>>
pero sí funciona<T extends Number> <Pair<T>>
?T
vs?
. Parte del problema es que cuando Andrónico llega al punto vital de su explicación, se desvía a otro hilo que usa solo ejemplos triviales. Esperaba obtener una respuesta más clara y completa aquí.Le recomiendo que consulte la documentación de comodines genéricos, especialmente las pautas para el uso de comodines
Hablando francamente tu método #doesntCompile
y llama como
Es fundamentalmente incorrecto
Agreguemos implementación legal :
Está realmente bien, porque Double extiende Number, por lo que put también
List<Double>
está absolutamente bienList<Integer>
, ¿verdad?Sin embargo, ¿todavía supones que es legal pasar aquí
new HashMap<Integer, List<Integer>>()
de tu ejemplo?El compilador no lo cree, y está haciendo todo lo posible para evitar tales situaciones.
Intente hacer la misma implementación con el método #compile y el compilador obviamente no le permitirá poner una lista de dobles en el mapa.
Básicamente, no puede poner nada, pero
List<T>
es por eso que es seguro llamar a ese método connew HashMap<Integer, List<Integer>>()
onew HashMap<Integer, List<Double>>()
onew HashMap<Integer, List<Long>>()
onew HashMap<Integer, List<Number>>()
.En pocas palabras, estás tratando de hacer trampa con el compilador y se defiende bastante contra tal trampa.
NB: la respuesta publicada por Maurice Perry es absolutamente correcta. Simplemente no estoy seguro de que sea lo suficientemente claro, así que intenté (realmente espero haberlo logrado) para agregar una publicación más extensa.
fuente