interfaces - metodos genericos java
Límite superior del tipo de retorno genérico-interfaz vs. clase-código sorprendentemente válido (2)
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 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 la
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 es?
El tipo inferido por su compilador antes de la asignación de
X
es
Integer & CharSequence
.
Este tipo se
siente
extraño, porque
Integer
es final, pero es un tipo perfectamente válido en Java.
Luego se lanza a
Integer
, lo cual está perfectamente bien.
Hay exactamente un valor posible para el tipo
Integer & CharSequence
:
null
.
Con la siguiente implementación:
<X extends CharSequence> X getCharSequence() {
return null;
}
La siguiente tarea funcionará:
Integer x = getCharSequence();
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
patrón anti diseño API
.
Nunca (casi) debe 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 los métodos como
Collections.emptyList()
, en cuyo caso el vacío de la lista (y el borrado de tipo genérico) es la razón por la cual cualquier inferencia para
<T>
funcionará:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
CharSequence
es una
interface
.
Por lo tanto, incluso si
SomeClass
no implementa
CharSequence
, sería perfectamente posible crear una clase
class SubClass extends SomeClass implements CharSequence
Por lo tanto puedes escribir
SomeClass c = getCharSequence();
porque el tipo inferido
X
es el tipo de intersección
SomeClass & CharSequence
.
Esto es un poco extraño en el caso de
Integer
porque
Integer
es final, pero
final
no juega ningún papel en estas reglas.
Por ejemplo puedes escribir
<T extends Integer & CharSequence>
Por otro lado,
String
no es una
interface
, por lo que sería imposible extender
SomeClass
para obtener un subtipo de
String
, porque java no admite la herencia múltiple para las clases.
Con el ejemplo de
List
, debe recordar que los genéricos no son covariantes ni contravariantes.
Esto significa que si
X
es un subtipo de
Y
,
List<X>
no es un subtipo ni un supertipo de
List<Y>
.
Como
Integer
no implementa
CharSequence
, no puede usar
List<Integer>
en su método
doCharSequence
.
Sin embargo, puede hacer que esto se compile
<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}
Si tiene un método que
devuelve
una
List<T>
como esta:
static <T extends CharSequence> List<T> foo()
tu puedes hacer
List<? extends Integer> list = foo();
Nuevamente, esto se debe a que el tipo inferido es
Integer & CharSequence
y este es un subtipo de
Integer
.
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, here está la parte de JLS donde explica cómo funcionan los límites de tipo. Puede incluir múltiples interfaces, p. Ej.
<T extends String & CharSequence & List & Comparator>
pero solo el primer enlace puede ser una no interfaz.