Este es un ejemplo del mundo real de una API de biblioteca de terceros, pero simplificado.
Compilado con Oracle JDK 8u72
Considere estos dos métodos:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
Ambos informan una advertencia de "lanzamiento no verificado". Entiendo por qué. Lo que me desconcierta es por qué puedo llamar
Integer x = getCharSequence();
y se compila? El compilador debe saber que Integer
no se implementa CharSequence
. La llamada a
Integer y = getString();
da un error (como se esperaba)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
¿Alguien puede explicar por qué este comportamiento se consideraría válido? ¿Cómo sería útil?
El cliente no sabe que esta llamada no es segura: el código del cliente se compila sin previo aviso. ¿Por qué la compilación no advierte sobre eso / emite un error?
Además, ¿en qué se diferencia de este ejemplo?
<X extends CharSequence> void doCharSequence(List<X> l) {
}
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
Intentar pasar List<Integer>
da un error, como se esperaba:
method doCharSequence in class generic.GenericTest cannot be applied to given types; required: java.util.List<X> found: java.util.List<java.lang.Integer> reason: inference variable X has incompatible bounds equality constraints: java.lang.Integer upper bounds: java.lang.CharSequence
Si eso se informa como un error, ¿por qué Integer x = getCharSequence();
no?
Integer x = getCharSequence();
se compilará, pero el lanzamiento en el RHSInteger x = (Integer) getCharSequence();
falla la compilaciónRespuestas:
CharSequence
es uninterface
. Por lo tanto, incluso siSomeClass
no se implementaCharSequence
, sería perfectamente posible crear una clasePor lo tanto puedes escribir
porque el tipo inferido
X
es el tipo de intersecciónSomeClass & CharSequence
.Esto es un poco extraño en el caso de
Integer
porqueInteger
es final, perofinal
no juega ningún papel en estas reglas. Por ejemplo puedes escribirPor otro lado,
String
no es uninterface
, por lo que sería imposible ampliarSomeClass
para obtener un subtipo deString
, porque Java no admite la herencia múltiple para las clases.Con el
List
ejemplo, debe recordar que los genéricos no son covariantes ni contravariantes. Esto significa que siX
es un subtipo deY
,List<X>
no es ni un subtipo ni un supertipo deList<Y>
. ComoInteger
no se implementaCharSequence
, no puede usarloList<Integer>
en sudoCharSequence
método.Sin embargo, puede hacer que esto se compile
Si tiene un método que devuelve algo
List<T>
como esto:tu puedes hacer
Nuevamente, esto se debe a que el tipo inferido es
Integer & CharSequence
y este es un subtipo deInteger
.Los tipos de intersección ocurren implícitamente cuando especifica múltiples límites (por ejemplo
<T extends SomeClass & CharSequence>
).Para obtener más información, aquí está la parte de JLS donde explica cómo funcionan los límites de tipo. Puede incluir múltiples interfaces, p. Ej.
pero solo el primer límite puede ser una no interfaz.
fuente
&
en la definición genérica. +1<T extends String & List & Comparator>
está bien pero<T extends String & Integer>
no lo está, porqueInteger
no es una interfaz.Collections.emptyList()
así comoOptional.empty()
. Estas implementaciones devuelven una interfaz genérica, pero no almacenan nada.final
en tiempo de compilación estaráfinal
en tiempo de ejecución.getCharSequence()
promete devolver lo queX
necesite la persona que llama, que incluye devolver un tipo que se extiendeInteger
e implementaCharSequence
si la persona que llama lo necesita y, según esta promesa, es correcto permitir asignar el resultadoInteger
. Es el métodogetCharSequence()
que está roto ya que no cumple su promesa, pero eso no es culpa del compilador.El tipo inferido por su compilador antes de la asignación para
X
esInteger & CharSequence
. Este tipo se siente extraño, porqueInteger
es final, pero es un tipo perfectamente válido en Java. Luego se lanza aInteger
, lo cual está perfectamente bien.Hay exactamente un posible valor para el
Integer & CharSequence
tipo:null
. Con la siguiente implementación:La siguiente tarea funcionará:
Debido a este posible valor, no hay razón para que la asignación sea incorrecta, incluso si obviamente es inútil. Una advertencia sería útil.
El verdadero problema es la API, no el sitio de la llamada.
De hecho, recientemente escribí en un blog sobre este anti patrón de diseño de API . Nunca debería (casi) diseñar un método genérico para devolver tipos arbitrarios porque (casi) nunca puede garantizar que se entregará el tipo inferido. Una excepción son métodos como
Collections.emptyList()
, en el caso de que el vacío de la lista (y el borrado de tipo genérico) sea la razón por la cual funcionará cualquier inferencia<T>
:fuente