Java: come ottengo un valore letterale di class da un tipo generico?

In genere, ho visto persone usare la class letterale come questa:

Class cls = Foo.class; 

Ma cosa succede se il tipo è generico, ad esempio Elenco? Funziona bene, ma ha un avviso poiché List dovrebbe essere parametrizzato:

 Class cls = List.class 

Quindi perché non aggiungere un ? Bene, questo causa un errore di mancata corrispondenza di tipo:

 Class<List> cls = List.class 

Ho pensato che qualcosa del genere avrebbe funzionato, ma questo è solo un semplice errore di syntax:

 Class<List> cls = List.class 

Come posso ottenere staticamente una Class<List> , ad es. Usando il letterale di class?

Potrei usare @SuppressWarnings("unchecked") per eliminare gli avvisi causati dall’uso non parametrizzato di List nel primo esempio, Class cls = List.class , ma preferirei non farlo.

Eventuali suggerimenti?

    Non è ansible a causa del tipo di cancellazione .

    I generici di Java sono poco più dello zucchero sintattico per i cast di oggetti. Dimostrare:

     List list1 = new ArrayList(); List list2 = (List)list1; list2.add("foo"); // perfectly legal 

    L’unica istanza in cui le informazioni di tipo generico vengono mantenute in fase di esecuzione è con Field.getGenericType() se si interrogano i membri di una class tramite reflection.

    Tutto questo è il motivo per cui Object.getClass() ha questa firma:

     public final native Class< ?> getClass(); 

    La parte importante è la Class< ?> .

    Per dirla in un altro modo, dalle FAQ di Java Generics :

    Perché non esiste un valore letterale di class per tipi parametrici concreti?

    Perché il tipo parametrizzato non ha una rappresentazione esatta del tipo di runtime.

    Un letterale di class denota un object di Class che rappresenta un determinato tipo. Ad esempio, la class literal String.class denota l’object Class che rappresenta il tipo String ed è identico all’object Class restituito quando il metodo getClass viene richiamato su un object String . Un letterale di class può essere utilizzato per i controlli di tipo runtime e per la riflessione.

    I tipi parametrizzati perdono gli argomenti di tipo quando vengono tradotti in codice byte durante la compilazione in un processo chiamato cancellazione di tipi. Come effetto collaterale della cancellazione del tipo, tutte le istanze di un tipo generico condividono la stessa rappresentazione runtime, cioè quella del tipo grezzo corrispondente. In altre parole, i tipi parametrizzati non hanno la propria rappresentazione del tipo. Di conseguenza, non ha senso formare letterali di class come List.class , List.class e List< ?>.class , poiché non esistono oggetti di Class . Solo l’ List tipi non List ha un object Class che rappresenta il suo tipo di runtime. Si chiama List.class .

    Non ci sono valori letterali di class per i tipi parametrizzati, tuttavia esistono degli oggetti Type che definiscono correttamente questi tipi.

    Vedi java.lang.reflect.ParameterizedType – http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.html

    La libreria Gson di Google definisce una class TypeToken che consente di generare semplicemente tipi parametrizzati e di utilizzarla per spec json object con tipi parametrici complessi in modo generico e amichevole. Nel tuo esempio useresti:

     Type typeOfListOfFoo = new TypeToken>(){}.getType() 

    Intendevo pubblicare i collegamenti alle classi TypeToken e Gson javadoc ma Stack Overflow non mi consente di pubblicare più di un link poiché sono un nuovo utente, puoi facilmente trovarli utilizzando la ricerca di Google

    Puoi gestirlo con un doppio cast:

    @SuppressWarnings("unchecked") Class> cls = (Class>)(Object)List.class

    Per esporre la risposta di cletus, in fase di esecuzione viene rimosso tutto il record dei tipi generici. I generici vengono elaborati solo nel compilatore e vengono utilizzati per fornire ulteriore sicurezza del tipo. Sono in realtà solo una scorciatoia che consente al compilatore di inserire le tipografie nei luoghi appropriati. Ad esempio, in precedenza dovresti fare quanto segue:

     List x = new ArrayList(); x.add(new SomeClass()); Iterator i = x.iterator(); SomeClass z = (SomeClass) i.next(); 

    diventa

     List x = new ArrayList(); x.add(new SomeClass()); Iterator i = x.iterator(); SomeClass z = i.next(); 

    Ciò consente al compilatore di verificare il codice in fase di compilazione, ma in fase di runtime sembra ancora il primo esempio.

    Bene, come tutti sappiamo che viene cancellato. Ma può essere conosciuto in alcune circostanze in cui il tipo è esplicitamente menzionato nella gerarchia di classi:

     import java.lang.reflect.*; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; public abstract class CaptureType { /** * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type. * * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType} */ public Type getTypeParam() { Class< ?> bottom = getClass(); Map, Type> reifyMap = new LinkedHashMap<>(); for (; ; ) { Type genericSuper = bottom.getGenericSuperclass(); if (!(genericSuper instanceof Class)) { ParameterizedType generic = (ParameterizedType) genericSuper; Class< ?> actualClaz = (Class< ?>) generic.getRawType(); TypeVariable< ? extends Class>[] typeParameters = actualClaz.getTypeParameters(); Type[] reified = generic.getActualTypeArguments(); assert (typeParameters.length != 0); for (int i = 0; i < typeParameters.length; i++) { reifyMap.put(typeParameters[i], reified[i]); } } if (bottom.getSuperclass().equals(CaptureType.class)) { bottom = bottom.getSuperclass(); break; } bottom = bottom.getSuperclass(); } TypeVariable var = bottom.getTypeParameters()[0]; while (true) { Type type = reifyMap.get(var); if (type instanceof TypeVariable) { var = (TypeVariable< ?>) type; } else { return type; } } } /** * Returns the raw type of the generic type. * 

    For example in case of {@code CaptureType}, it would return {@code Class}

    * For more comprehensive examples, go through javadocs of {@link com.types.CaptureType} * * @return Class object * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType} * @see com.types.CaptureType */ public Class getRawType() { Type typeParam = getTypeParam(); if (typeParam != null) return getClass(typeParam); else throw new RuntimeException("Could not obtain type information"); } /** * Gets the {@link java.lang.Class} object of the argument type. *

    If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}

    * * @param type The type * @param type of class object expected * @return The Class object of the type * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object */ public static Class getClass(Type type) { if (type instanceof GenericArrayType) { Type componentType = ((GenericArrayType) type).getGenericComponentType(); Class< ?> componentClass = getClass(componentType); if (componentClass != null) { return (Class) Array.newInstance(componentClass, 0).getClass(); } else throw new UnsupportedOperationException("Unknown class: " + type.getClass()); } else if (type instanceof Class) { Class claz = (Class) type; return claz; } else if (type instanceof ParameterizedType) { return getClass(((ParameterizedType) type).getRawType()); } else if (type instanceof TypeVariable) { throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection"); } else throw new UnsupportedOperationException("Unknown class: " + type.getClass()); } /** * This method is the preferred method of usage in case of complex generic types. *

    It returns {@link com.types.TypeADT} object which contains nested information of the type parameters

    * * @return TypeADT object * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType} */ public TypeADT getParamADT() { return recursiveADT(getTypeParam()); } private TypeADT recursiveADT(Type type) { if (type instanceof Class) { return new TypeADT((Class< ?>) type, null); } else if (type instanceof ParameterizedType) { ArrayList generic = new ArrayList<>(); ParameterizedType type1 = (ParameterizedType) type; return new TypeADT((Class< ?>) type1.getRawType(), Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList())); } else throw new UnsupportedOperationException(); } } public class TypeADT { private final Class< ?> reify; private final List parametrized; TypeADT(Class< ?> reify, List parametrized) { this.reify = reify; this.parametrized = parametrized; } public Class< ?> getRawType() { return reify; } public List getParameters() { return parametrized; } }

    E ora puoi fare cose come:

     static void test1() { CaptureType t1 = new CaptureType() { }; equals(t1.getRawType(), String.class); } static void test2() { CaptureType> t1 = new CaptureType>() { }; equals(t1.getRawType(), List.class); equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class); } private static void test3() { CaptureType>> t1 = new CaptureType>>() { }; equals(t1.getParamADT().getRawType(), List.class); equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class); } static class Test4 extends CaptureType> { } static void test4() { Test4 test4 = new Test4(); equals(test4.getParamADT().getRawType(), List.class); } static class PreTest5 extends CaptureType { } static class Test5 extends PreTest5 { } static void test5() { Test5 test5 = new Test5(); equals(test5.getTypeParam(), Integer.class); } static class PreTest6 extends CaptureType { } static class Test6 extends PreTest6 { } static void test6() { Test6 test6 = new Test6(); equals(test6.getTypeParam(), Integer.class); } class X extends CaptureType { } class Y extends X { } class Z extends Y>>>> { } void test7(){ Z z = new Z<>(); TypeADT param = z.getParamADT(); equals(param.getRawType(), Map.class); List parameters = param.getParameters(); equals(parameters.get(0).getRawType(), Integer.class); equals(parameters.get(1).getRawType(), List.class); equals(parameters.get(1).getParameters().get(0).getRawType(), List.class); equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class); equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class); } static void test8() throws IllegalAccessException, InstantiationException { CaptureType type = new CaptureType() { }; equals(type.getRawType(), int[].class); } static void test9(){ CaptureType type = new CaptureType() { }; equals(type.getRawType(), String[].class); } static class SomeClass extends CaptureType{} static void test10(){ SomeClass claz = new SomeClass<>(); try{ claz.getRawType(); throw new RuntimeException("Shouldnt come here"); }catch (RuntimeException ex){ } } static void equals(Object a, Object b) { if (!a.equals(b)) { throw new RuntimeException("Test failed. " + a + " != " + b); } } 

    Maggiori informazioni qui . Ma ancora, è quasi imansible recuperare:

     class SomeClass extends CaptureType{} SomeClass claz = new SomeClass<>(); 

    dove viene cancellato.

    A causa del fatto che i letterali di Classe non hanno informazioni di tipo generico, penso che dovresti assumere che sarà imansible sbarazzarsi di tutti gli avvertimenti. In un certo senso, l’uso della Class è lo stesso che usare una raccolta senza specificare il tipo generico. Il meglio che potevo uscire era:

     private > List getList(Class cls) { List res = new ArrayList(); // "snip"... some stuff happening in here, using cls return res; } public > List> getList() { return getList(A.class); } 

    È ansible utilizzare un metodo di supporto per sbarazzarsi di @SuppressWarnings("unchecked") su una class.

     @SuppressWarnings("unchecked") private static  Class generify(Class< ?> cls) { return (Class)cls; } 

    Quindi potresti scrivere

     Class> cls = generify(List.class); 

    Altri esempi di utilizzo sono

      Class> cls; cls = generify(Map.class); cls = TheClass.>generify(Map.class); funWithTypeParam(generify(Map.class)); public void funWithTypeParam(Class> cls) { } 

    Tuttavia, dato che raramente è veramente utile, e l’utilizzo del metodo sconfigge il controllo del tipo del compilatore, non consiglierei di implementarlo in un luogo in cui è pubblicamente accessibile.