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 Integerno 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); // errorIntentar 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:
CharSequencees uninterface. Por lo tanto, incluso siSomeClassno se implementaCharSequence, sería perfectamente posible crear una clasePor lo tanto puedes escribir
porque el tipo inferido
Xes el tipo de intersecciónSomeClass & CharSequence.Esto es un poco extraño en el caso de
IntegerporqueIntegeres final, perofinalno juega ningún papel en estas reglas. Por ejemplo puedes escribirPor otro lado,
Stringno es uninterface, por lo que sería imposible ampliarSomeClasspara obtener un subtipo deString, porque Java no admite la herencia múltiple para las clases.Con el
Listejemplo, debe recordar que los genéricos no son covariantes ni contravariantes. Esto significa que siXes un subtipo deY,List<X>no es ni un subtipo ni un supertipo deList<Y>. ComoIntegerno se implementaCharSequence, no puede usarloList<Integer>en sudoCharSequencemé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 & CharSequencey 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á, porqueIntegerno es una interfaz.Collections.emptyList()así comoOptional.empty(). Estas implementaciones devuelven una interfaz genérica, pero no almacenan nada.finalen tiempo de compilación estaráfinalen tiempo de ejecución.getCharSequence()promete devolver lo queXnecesite la persona que llama, que incluye devolver un tipo que se extiendeIntegere implementaCharSequencesi 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
XesInteger & CharSequence. Este tipo se siente extraño, porqueIntegeres 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 & CharSequencetipo: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