polimorfismo metodos interfaces genericos ejemplos clase abstracta java generics java-8

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.