comportamiento genérico inesperado con los tipos genéricos de anidación TypeToken

GotoFinal:

Tengo TypeTokenclase utilizada para representar algún tipo genérico como esto:
TypeToken<List<String>> listOfStrings = new TypeToken<List<String>> {}
Y esto funciona bien, TypeTokenes 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 Listpara 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
Marco13:

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 extendsTypemétodo aquí devuelve unaTypeToken<? extends Number>
  • En la llamada al listmétodo, el ?es capturado en el parámetro de tipo T. 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 a TypeToken<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 de Collection<Long>, porque el tipo Longes 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 Xsonidos" 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 ClassCastExceptionde un modo u otro).

Explicando dónde se pierde la seguridad de tipos aquí (y por qué una ClassCastExceptionpodrí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.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=175972&siteId=1
Recomendado
Clasificación