Ottieni il tipo di un parametro generico in Java con reflection


143

È possibile ottenere il tipo di un parametro generico?

Un esempio:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}

Risposte:


156

Un costrutto, una volta mi sono imbattuto in sembrava

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

Quindi sembra esserci un po 'di magia di riflessione intorno che purtroppo non capisco del tutto ... Mi dispiace.


2
Ma dichiaralo come astratto allora. quando si desidera utilizzarlo, quindi eseguirne la sottoclasse in linea.
Snicolas,

42
Anche se questo è molto vecchio e accettato per qualche motivo, ho annullato il voto perché semplicemente non risponde alla domanda. Sarebbe non dare "Spiderman" nell'esempio riportato nella domanda. È senza dubbio utile in alcune situazioni, ma non funziona per la domanda che è stata posta.
Jon Skeet,

15
Nessuno che è arrivato così lontano sta per accettare "Non si può fare" per una risposta. Siamo qui perché siamo disperati.
Addison,

14
Ottengo questa eccezione: Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType non sono sicuro di quale sia il vincolo.
Piatto

4
Come @Dish ho appena ricevuto unjava.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
Kenny Wyland l'

133

Voglio provare a scomporre la risposta di @DerMike per spiegare:

Innanzitutto, la cancellazione del tipo non significa che JDK elimini le informazioni sul tipo in fase di esecuzione. È un metodo per consentire alla verifica del tipo in fase di compilazione e alla compatibilità del tipo di runtime di coesistere nella stessa lingua. Come implica questo blocco di codice, il JDK conserva le informazioni sul tipo cancellato - non è semplicemente associato a cast e cose controllati.

In secondo luogo, ciò fornisce informazioni di tipo generico a una classe generica esattamente a un livello superiore dell'erarchia dal tipo concreto che viene verificato - vale a dire una classe padre astratta con parametri di tipo generico può trovare i tipi di calcestruzzo corrispondenti ai suoi parametri di tipo per un'implementazione concreta di se stessa che eredita direttamente da esso. Se questa classe fosse non astratta e istanziata, o l'implementazione concreta fosse inferiore di due livelli, ciò non funzionerebbe (anche se un po 'di jimmy potrebbe farla applicare a qualsiasi numero predeterminato di livelli oltre uno, o fino alla classe più bassa con X parametri di tipo generico, eccetera).

Comunque, sulla spiegazione. Ecco di nuovo il codice, diviso in righe per facilità di riferimento:

Classe 1 # genericParameter0OfThisClass = 
2 # (Classe)
3 # ((ParameterizedType)
4 # getClass ()
5 # .getGenericSuperclass ())
6 # .getActualTypeArguments () [0];

Lasciateci "essere" la classe astratta con tipi generici che contiene questo codice. Leggendolo all'incirca:

  • La riga 4 ottiene l'istanza di classe della classe concreta corrente. Questo identifica il tipo concreto del nostro discendente immediato.
  • La riga 5 ottiene il supertipo di quella classe come Tipo; Questi siamo noi. Dato che siamo un tipo parametrico, possiamo tranquillamente lanciarci su ParameterizedType (linea 3). La chiave è che quando Java determina questo oggetto Type, utilizza le informazioni di tipo presenti nel figlio per associare le informazioni di tipo ai nostri parametri di tipo nella nuova istanza ParameterizedType. Quindi ora possiamo accedere a tipi concreti per i nostri generici.
  • La riga 6 ottiene l'array di tipi mappato nei nostri generici, nell'ordine dichiarato nel codice di classe. Per questo esempio estraiamo il primo parametro. Questo ritorna come Tipo.
  • La riga 2 lancia il Tipo finale restituito a una Classe. Questo è sicuro perché sappiamo quali tipi sono in grado di prendere i nostri parametri di tipo generico e possiamo confermare che saranno tutti classi (non sono sicuro di come in Java si otterrebbe un parametro generico che non ha un'istanza di classe associato ad esso, in realtà).

... e basta. Quindi riponiamo in noi le informazioni sul tipo dalla nostra implementazione concreta e le usiamo per accedere a un handle di classe. potremmo raddoppiare getGenericSuperclass () e passare a due livelli, oppure eliminare getGenericSuperclass () e ottenere valori per noi stessi come un tipo concreto (avvertenza: non ho testato questi scenari, non mi sono ancora inventato).

Diventa difficile se i tuoi figli concreti sono un numero arbitrario di luppoli di distanza, o se sei concreto e non definitivo, e particolarmente difficile se ti aspetti che i tuoi figli (variabilmente profondi) abbiano i loro generici. Ma di solito è possibile progettare attorno a queste considerazioni, quindi questo ti dà la maggior parte del modo.

Spero che questo abbia aiutato qualcuno! Riconosco che questo post è antico. Probabilmente prenderò questa spiegazione e la terrò per altre domande.


3
Basta rispondere al tuo "Non sono sicuro di come in Java si potrebbe ottenere un parametro generico a cui non è associata un'istanza di classe, in realtà)": Questo è possibile se il parametro della classe generica è un " espressione jolly "(diciamo:? estende SomeClass) o una" variabile di tipo "(diciamo: <T> dove T proviene da una sottoclasse generica). in entrambi i casi, le istanze "Type" restituite non possono essere trasmesse a "Class" in quanto ciascuna VM potrebbe avere un'implementazione diversa per entrambe le quali implementano l'interfaccia Type ma non derivano direttamente da java.lang.Class.
Roberto Andrade,

19

In realtà l'ho fatto funzionare. Considera il seguente frammento:

Method m;
Type[] genericParameterTypes = m.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
     if( genericParameterTypes[i] instanceof ParameterizedType ) {
                Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)"

     }
 }

Sto usando jdk 1.6


12
-1: questo non risolve il problema nella domanda; ti dà solo il tipo dichiarato nella firma del metodo, non il tipo di runtime effettivo.
Michael Borgwardt,

5
Potrebbe non rispondere alla domanda del PO, ma è una tecnica utile.
dnault,

3
Questa è la risposta a una domanda completamente diversa.
Dawood ibn Kareem,

Puoi chiamare m.getParameterTypes () per ottenere tutti i tipi effettivi. Restituirebbe "Elenco" per l'esempio.
Lee Meador

Se genericParameterTypes [i] non è un'istanza di ParameterizedType, potrebbe essere una classe. Ciò significa che un particolare argomento del metodo non è affatto parametrizzato.
Lee Meador

18

In realtà esiste una soluzione, applicando il trucco della "classe anonima" e le idee dei token Super Type :

public final class Voodoo {
    public static void chill(final List<?> aListWithSomeType) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        System.out.println(aListWithSomeType.getClass().getGenericSuperclass());
        System.out.println(((ParameterizedType) aListWithSomeType
            .getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]);
    }
    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>() {});
    }
}
class SpiderMan {
}

Le bugie trucco nella creazione di una classe anonima , new ArrayList<SpiderMan>() {}, al posto di quella originale (semplice) new ArrayList<SpiderMan>(). L'uso di una classe anoyema (se possibile) assicura che il compilatore conservi informazioni sull'argomento type SpiderMandato al parametro type List<?>. Ecco !


Ciò risponde indirettamente alla domanda: il problema originale non ha soluzione. Affinché il tipo mantenga i suoi parametri generici, deve essere incorporato in un altro tipo, ad esempio una sottoclasse. Questo esempio mostra come è necessario modificare il codice chiamante per consentire il recupero del parametro di tipo generico in chill ().
Florian F,

Cordiali saluti, il primo collegamento è morto
Ashvin Sharma il

@AshvinSharma Credo che lo stesso materiale sia disponibile qui: rgomes.info/using-typetokens-to-retrieve-generic-parameters
Yann-Gaël Guéhéneuc

7

A causa della cancellazione del tipo, l'unico modo per conoscere il tipo di elenco sarebbe passare il tipo come parametro al metodo:

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}

7

Appendice alla risposta di @ DerMike per ottenere il parametro generico di un'interfaccia parametrizzata (usando il metodo #getGenericInterfaces () all'interno di un metodo predefinito Java-8 per evitare la duplicazione):

import java.lang.reflect.ParameterizedType; 

public class ParametrizedStuff {

@SuppressWarnings("unchecked")
interface Awesomable<T> {
    default Class<T> parameterizedType() {
        return (Class<T>) ((ParameterizedType)
        this.getClass().getGenericInterfaces()[0])
            .getActualTypeArguments()[0];
    }
}

static class Beer {};
static class EstrellaGalicia implements Awesomable<Beer> {};

public static void main(String[] args) {
    System.out.println("Type is: " + new EstrellaGalicia().parameterizedType());
    // --> Type is: ParameterizedStuff$Beer
}

Questo non è quello che chiedeva il post originale. Qui, hai creato una sottoclasse di Awesomeable<Beer>. In tal caso, le informazioni sul tipo vengono conservate. Se passi new Awesomable<Beer> ()al metodo non funzionerà.
Florian F,

@FlorianF Se si passa Awesomable<Beer>al volo senza la definizione esplicita di una sottoclasse concreta come EstrellaGaliciain questo caso, si sta ancora uscendo dal tipo parametrizzato: l'ho eseguito subito: System.out.println("Type is: " + new Awesomable<Beer>() {}.parameterizedType());---> Tipo è: ParameterizedStuff $ Beer
Campa

6

No, non è possibile. A causa di problemi di compatibilità al ribasso, i generici di Java si basano sulla cancellazione del tipo , ad esempio in fase di esecuzione, tutto ciò che hai è un Listoggetto non generico . Ci sono alcune informazioni sui parametri di tipo in fase di runtime, ma risiede nelle definizioni di classe (cioè puoi chiedere " che tipo generico usa la definizione di questo campo? "), Non nelle istanze di oggetto.


Sebbene java potesse archiviarlo come metadati durante il runtime, non è vero? Speravo che sarebbe successo. Sfortuna.
cimnine,

1
@ user99475: No. Ho ragione e Andrey ha torto. Si riferisce alle informazioni sul tipo che menziono nella seconda parte della mia risposta, ma non è quello che la domanda chiede.
Michael Borgwardt,

5

Come sottolineato da @bertolami, non è possibile per noi un tipo di variabile e ottenere il suo valore futuro (il contenuto della variabile typeOfList).

Tuttavia, puoi passare la classe come parametro su di essa in questo modo:

public final class voodoo {
    public static void chill(List<T> aListWithTypeSpiderMan, Class<T> clazz) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = clazz;
    }

    public static void main(String... args) {
        chill(new List<SpiderMan>(), Spiderman.class );
    }
}

È più o meno quello che fa Google quando devi passare una variabile di classe al costruttore di ActivityInstrumentationTestCase2 .



1

Puoi ottenere il tipo di un parametro generico con reflection come in questo esempio che ho trovato qui :

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Home<E> {
    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass(){
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>{}
    private static class StringBuilderHome extends Home<StringBuilder>{}
    private static class StringBufferHome extends Home<StringBuffer>{}   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }
}

come faresti la stessa cosa per ottenere V di Home <K, V>?
Rafael Sanches,

1

La risposta rapida alla domanda è no è impossibile, a causa della cancellazione del tipo generico Java.

La risposta più lunga sarebbe che se hai creato la tua lista in questo modo:

new ArrayList<SpideMan>(){}

Quindi, in questo caso, il tipo generico viene conservato nella superclasse generica della nuova classe anonima sopra.

Non che consiglio di farlo con gli elenchi, ma è un'implementazione di listener:

new Listener<Type>() { public void doSomething(Type t){...}}

E poiché estrapolando i tipi generici di superclassi e super interfacce cambia tra le JVM, la soluzione generica non è così semplice come potrebbero suggerire alcune risposte.

Qui è ora che l'ho fatto.


0

Questo è impossibile perché i generici in Java vengono considerati solo al momento della compilazione. Pertanto, i generici Java sono solo una sorta di pre-processore. Tuttavia, è possibile ottenere la classe effettiva dei membri dell'elenco.


Sì, è quello che sto facendo ora. Ma ora voglio sapere il tipo anche se l'elenco è vuoto. Ma quattro ragazzi non possono sbagliarsi. Grazie a tutti)!
cimnine,

19
Questo non è vero. Mentre è complicato puoi vedere nel prossimo post che ParameterizedType ti permette di farlo.
Edmondo1984,

1
D'accordo, si può fare. L'esempio di DerMike lo delinea (ha funzionato per me).
mac,

LOL a "quattro ragazzi non possono sbagliarsi". Questa risposta è corretta
Dawood ibn Kareem,

0

Ho codificato questo per metodi che prevedono di accettare o restituire Iterable<?...>. Ecco il codice:

/**
 * Assuming the given method returns or takes an Iterable<T>, this determines the type T.
 * T may or may not extend WindupVertexFrame.
 */
private static Class typeOfIterable(Method method, boolean setter)
{
    Type type;
    if (setter) {
        Type[] types = method.getGenericParameterTypes();
        // The first parameter to the method expected to be Iterable<...> .
        if (types.length == 0)
            throw new IllegalArgumentException("Given method has 0 params: " + method);
        type = types[0];
    }
    else {
        type = method.getGenericReturnType();
    }

    // Now get the parametrized type of the generic.
    if (!(type instanceof ParameterizedType))
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);
    ParameterizedType pType = (ParameterizedType) type;
    final Type[] actualArgs = pType.getActualTypeArguments();
    if (actualArgs.length == 0)
        throw new IllegalArgumentException("Given method's 1st param type is not parametrized generic: " + method);

    Type t = actualArgs[0];
    if (t instanceof Class)
        return (Class<?>) t;

    if (t instanceof TypeVariable){
        TypeVariable tv =  (TypeVariable) actualArgs[0];
        AnnotatedType[] annotatedBounds = tv.getAnnotatedBounds();///
        GenericDeclaration genericDeclaration = tv.getGenericDeclaration();///
        return (Class) tv.getAnnotatedBounds()[0].getType();
    }

    throw new IllegalArgumentException("Unknown kind of type: " + t.getTypeName());
}

0

Non è possibile ottenere un parametro generico da una variabile. Ma puoi da una dichiarazione di metodo o campo:

Method method = getClass().getDeclaredMethod("chill", List.class);
Type[] params = method.getGenericParameterTypes();
ParameterizedType firstParam = (ParameterizedType) params[0];
Type[] paramsOfFirstGeneric = firstParam.getActualTypeArguments();

Questo mi dà un'eccezione nel thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl
Lluis Martinez

'params' sono 'Type' che ha diverse sottointerfacce. TypeVariableImpl è uno utilizzato per gli argomenti del metodo e indica il nome del generico all'interno di <> s, non la classe che rappresenta.
Lee Meador

0

Solo per me leggere questo frammento di codice è stato difficile, l'ho diviso in 2 righe leggibili:

// assuming that the Generic Type parameter is of type "T"
ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

Volevo creare un'istanza del parametro Type senza avere alcun parametro nel mio metodo:

publc T getNewTypeInstance(){
    ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
    Class<T> c =(Class<T>)p.getActualTypeArguments()[0];

    // for me i wanted to get the type to create an instance
    // from the no-args default constructor
    T t = null;
    try{
        t = c.newInstance();
    }catch(Exception e){
        // no default constructor available
    }
    return t;
}

0

Ecco un altro trucco. Utilizzare un array vararg generico

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}

0

Ho notato che molte persone si inclinano verso la getGenericSuperclass()soluzione:

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

Tuttavia, questa soluzione è soggetta a errori. Sarà non funziona correttamente se ci sono farmaci generici nei discendenti. Considera questo:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

Quale tipo avrà Bar.persistentClass? Class<Integer>? No, lo sarà Class<Double>. Ciò accadrà perché getClass()restituisce sempre la classe più alta, che è Barin questo caso, e la sua superclasse generica è Foo<Double>. Quindi, il tipo di argomento sarà Double.

Se hai bisogno di una soluzione affidabile che non guasta, posso suggerire due.

  1. Usa Guava. Si ha una classe che è stato fatto proprio per questo scopo: com.google.common.reflect.TypeToken. Gestisce bene tutte le custodie angolari e offre alcune funzionalità più piacevoli. Il rovescio della medaglia è una dipendenza aggiuntiva. Dato che hai usato questa classe, il tuo codice sembrerebbe semplice e chiaro, in questo modo:
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. Usa il metodo personalizzato di seguito. Implementa una logica notevolmente semplificata simile alla classe Guava, menzionata sopra. Tuttavia, non garantirei che sia soggetto a errori. Tuttavia risolve il problema con i discendenti generici.
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}

-1

Uso:

Class<?> typeOfTheList = aListWithTypeSpiderMan.toArray().getClass().getComponentType();

Questo è sbagliato. toArray()ritorna Object[]. Non puoi e non dovresti dipendere dal fatto che è un sottotipo particolare.
Radiodef,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.