Tengo TypeToken
clase utilizada para representar algún tipo genérico como esto:
TypeToken<List<String>> listOfStrings = new TypeToken<List<String>> {}
Y esto funciona bien, TypeToken
es sólo class TypeToken<T> {}
con el método simple para obtener ese tipo.
Ahora quería crear métodos sencillos de tipo común como List
para el uso más dinámico:
TypeToken<List<? extends Number>> numbers = list(extendsType(Number.class))
el uso de:
public static <T> TypeToken<? extends T> extendsType(Class<T> type) {return null;}
public static <T> TypeToken<List<T>> list(TypeToken<T> type) {return null;}
(Volver nulos, ya que sólo estoy preguntando por el compilador no la lógica)
Pero por alguna razón esto no funciona la forma en que se puede esperar: (como código que esperaba que fuera válida no se compila, y el código que esperaba para ser inválida hace compilación)
class TypeToken<X> {
static <T> TypeToken<? extends T> extendsType(Class<T> type) {return null;}
static <T> TypeToken<List<T>> list(TypeToken<T> type) {return null;}
static void wat() {
TypeToken<List<? extends Number>> a = new TypeToken<List<? extends Number>>() {}; // valid
TypeToken<List<? extends Number>> b = list(extendsType(Number.class)); // invalid, why?
TypeToken<? extends List<? extends Number>> c = list(extendsType(Number.class)); // valid, why?
}
}
Lo que estoy haciendo mal aquí? Y lo que está causando los genéricos a comportarse de esta manera?
Estoy usando JDK 11, pero también probado esto en JDK 8
error del compilador:
error: incompatible types: no instance(s) of type variable(s) T#1,CAP#1,T#2 exist so that TypeToken<List<T#1>> conforms to TypeToken<List<? extends Number>>
TypeToken<List<? extends Number>> b = list(extendsType(Number.class)); // invalid, why?
^
where T#1,T#2 are type-variables:
T#1 extends Object declared in method <T#1>list(TypeToken<T#1>)
T#2 extends Object declared in method <T#2>extendsType(Class<T#2>)
where CAP#1 is a fresh type-variable:
CAP#1 extends T#2 from capture of ? extends T#2
Creo que en el núcleo de esta cuestión que ya ha sido pedido en una forma parecida varias veces. Pero no estoy seguro, porque esta es una de las constelaciones en las que es particularmente difícil para envolver la cabeza de uno en torno al concepto.
Tal vez uno puede imaginar que de esa manera:
- El
extendsType
método aquí devuelve unaTypeToken<? extends Number>
En la llamada al
list
método, el?
es capturado en el parámetro de tipoT
. Esto puede capturar (en términos generales) se concibe como una nueva variable de tipo - algo asíX extends Number
, y el método devuelve unaTypeToken<List<X>>
Ahora bien, el
TypeToken<List<X>>
no es asignable aTypeToken<List<? extends Number>>
, a partir de las restricciones habituales. Tal vez esta tabla, con<===
lo que indica la transferibilidad y<=/=
que indica no transferible del mismo, ayudará a:Number <=== Integer TypeToken< Number> <=/= TypeToken<Integer> TypeToken<? extends Number> <=== TypeToken<Integer> List<? extends Number> <=== List<X> TypeToken< List<? extends Number>> <=/= TypeToken<List<X>> TypeToken<? extends List<? extends Number>> <=== TypeToken<List<X>>
Así que al final, la respuesta a la pregunta que existen relaciones entre los super-subtipo instancias de tipos genéricos en el famoso Genéricos AYUDA Angelika Langer es probablemente una vez más los más relevantes aquí:
las relaciones entre los subtipos de Super instancias de tipos genéricos están determinadas por dos aspectos ortogonales.
Por un lado, está la relación de herencia entre un supertipo y un subtipo.
...
Por otro lado, existe una relación basada en los argumentos de tipo. El requisito previo es que al menos uno de los argumentos de tipo en cuestión es un comodín. Por ejemplo,
Collection<? extends Number>
es un supertipo deCollection<Long>
, porque el tipoLong
es un miembro de la familia tipo que el comodín" ? extends Number "
denota.
Creo que la idea de la captura de ser un "nuevo tipo de X
sonidos" convincente, a pesar de que hay una cierta handwaving involucrados: Esto sucede algo "implícita" en el proceso de resolución, y no es visible en el código, pero me pareció muy útil, a en cierta medida, cuando escribí una biblioteca de tipos donde todas estas preguntas se le ocurrió ...
Actualizada para elaborar esta nueva, refiriéndose al comentario:
He mencionado que los tipos no son asignables "a partir de las restricciones habituales". Ahora se podría argumentar acerca de dónde estas "limitaciones" vienen. Pero básicamente siempre tienen la misma razón: Los tipos no son asignables, porque si fueran asignable, el programa no sería un tipo seguro. (Lo que significa que sería posible provocar una ClassCastException
de un modo u otro).
Explicando dónde se pierde la seguridad de tipos aquí (y por qué una ClassCastException
podría ser causado) implica algunas contorsiones. Voy a tratar de mostrar aquí, basado en el ejemplo que se hizo referencia en el comentario. Desplazarse hacia abajo para tl; dr para un ejemplo que tiene la misma estructura, pero es mucho más simple.
El ejemplo del comentario fue el siguiente:
import java.util.*;
import java.io.*;
class Ideone
{
public static class LinkTypeToken<T> extends TypeToken<List<T>> {}
public static class TypeToken<T> {}
public static void main (String[] args) throws java.lang.Exception {
LinkTypeToken<Number> listA = null;
TypeToken<List<Number>> listB = listA;
LinkTypeToken<? extends Number> listC = null;
TypeToken<? extends List<? extends Number>> listD = listC;
// error: incompatible types: LinkTypeToken<CAP#1> cannot be
// converted to TypeToken<List<? extends Number>>
// TypeToken<List<? extends Number>> listE = listC;
// ^
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Number from capture of ? extends Number
TypeToken<List<? extends Number>> listE = listC;
}
}
La línea que causa el error de compilación aquí hace porque si era posible asignar estos tipos, entonces uno podría hacer lo siguiente:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
class IdeoneWhy
{
public static class ArrayListTypeToken<T> extends TypeToken<ArrayList<T>>
{
ArrayList<T> element = null;
void setElement(ArrayList<T> element)
{
this.element = element;
}
}
public static abstract class TypeToken<T>
{
abstract void setElement(T element);
}
public static void main(String[] args)
{
ArrayListTypeToken<? extends Number> listC = new ArrayListTypeToken<Integer>();
TypeToken<? extends List<? extends Number>> listD = listC;
// This is not possible:
//TypeToken<List<? extends Number>> listE = listC;
// But let's enforce it with a brutal cast:
TypeToken<List<? extends Number>> listE =
(TypeToken<List<? extends Number>>)(Object)listC;
// This throws a ClassCastException
listE.setElement(new LinkedList<Integer>());
}
}
Por lo que el hecho de que una TypeToken<List<? extends Number>>
no es asignable a partir de un TypeToken<? extends List<? extends Number>>
hecho sólo sirve el propósito de prevenir una ClassCastException
.
tl; dr :
La variante más sencilla es la siguiente:
import java.util.ArrayList;
import java.util.List;
public class WhySimpler
{
public static void main(String[] args)
{
List<Float> floats = new ArrayList<Float>();
// This is not possible
//List<Number> numbers = floats;
// Let's enforce it with a brutal cast:
List<Number> numbers = (List<Number>)(Object)floats;
Integer integer = 123;
// This is possible, because Integer is a Number:
numbers.add(integer);
// Now, we ended up placing an Integer into a list that
// may only contain Float values.
// So this will cause a ClassCastException:
Float f = floats.get(0);
}
}
A la inversa, cuando el tipo se declara como List<? extends Number>
, a continuación, la asignación es posible, porque no es posible colarse un tipo no válido en la lista de un tal:
List<Float> floats = new ArrayList<Float>();
List<? extends Number> numbers = floats;
numbers.add(someInteger); // This is not possible
En resúmen: Se trata de la seguridad de tipos.