web-dev-qa-db-de.com

Ruft den Typ eines generischen Parameters in Java mit Reflektion ab

Ist es möglich, den Typ eines generischen Parameters abzurufen?

Ein Beispiel:

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>());
    }
}
120
cimnine

Ein Konstrukt, auf das ich einst gestoßen bin, sah aus wie

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

Es scheint also eine Reflexionsmagie zu geben, die ich leider nicht ganz verstehe ... Sorry.

134
DerMike

Ich möchte versuchen, die Antwort von @DerMike aufzuschlüsseln, um Folgendes zu erklären:

Erstens bedeutet Typlöschung nicht, dass die Informationen des JDK eliminiert -typs zur Laufzeit. Dies ist eine Methode, mit der die Prüfung der Kompilierzeit und die Kompatibilität der Laufzeitarten in derselben Sprache möglich ist. Wie dieser Codeblock impliziert, behält das JDK die gelöschten Typinformationen bei - es ist einfach nicht mit geprüften Casts und Sachen verknüpft.

Zweitens liefert dies generische Typinformationen für eine generische Klasse, die genau eine Ebene höher ist als die Hierarchie vom konkreten geprüften Typ - d. H. Eine abstrakte übergeordnete Klasse mit generischen Typparametern kann die ihren Typparametern entsprechenden konkreten Typen für eine konkrete Implementierung von sich selbst finden, die direkt von ihr erbt. Wenn diese Klasse nicht abstrakt und instanziiert wäre oder die konkrete Implementierung zwei Ebenen tiefer wäre, würde dies nicht funktionieren (obwohl ein wenig Jimmy dazu führen könnte, dass sie auf eine vorbestimmte Anzahl von Ebenen über einer Ebene oder bis zu der niedrigsten Klasse anwendbar ist mit generischen X-Typ-Parametern usw.).

Auf jeden Fall weiter zur Erklärung. Hier ist noch einmal der Code, der zur besseren Übersicht in Zeilen aufgeteilt ist:

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

Sei 'uns' die abstrakte Klasse mit generischen Typen, die diesen Code enthalten. Lesen Sie dies ungefähr von innen nach außen:

  • Zeile 4 erhält die aktuelle Klasseninstanz der konkreten Klasse. Dies kennzeichnet den konkreten Typ unseres unmittelbaren Nachkommens . 
  • Zeile 5 erhält den Supertyp dieser Klasse als Typ. Das sind wir. Da wir ein parametrischer Typ sind, können wir uns sicher auf ParameterizedType (Zeile 3) festlegen. Der Schlüssel besteht darin, dass bei der Bestimmung dieses Type-Objekts durch Java die im untergeordneten Objekt vorhandenen Typinformationen verwendet werden, um den Typparametern der neuen ParameterizedType-Instanz Typinformationen zuzuordnen. So können wir jetzt auf konkrete Typen für unsere Generika zugreifen. 
  • Zeile 6 erhält das Array von Typen, das unseren Generics zugeordnet ist, in der Reihenfolge, die im Klassencode deklariert ist. Für dieses Beispiel ziehen wir den ersten Parameter heraus. Dies kommt wieder als Typ . 
  • In Zeile 2 wird der letzte in eine Klasse zurückgegebene Typ umgewandelt. Dies ist sicher, da wir wissen, welche Typen unsere generischen Typparameter verwenden können, und können bestätigen, dass sie alle Klassen sind. (Ich bin nicht sicher, wie man in Java einen generischen Parameter abrufen würde, der keine Klasseninstanz besitzt damit verbunden, eigentlich) .

... und das ist so ziemlich alles. Wir schieben also Typinformationen aus unserer eigenen konkreten Implementierung in uns zurück und verwenden sie, um auf einen Klassen-Handle zuzugreifen. Wir könnten getGenericSuperclass () verdoppeln und zwei Ebenen gehen oder getGenericSuperclass () eliminieren und Werte für uns als konkreten Typ erhalten (Achtung: Ich habe diese Szenarien nicht getestet, sie sind noch nicht für mich aufgetaucht).

Es wird schwierig, wenn Ihre konkreten Kinder eine beliebige Anzahl von Sprüngen entfernt sind, oder wenn Sie konkret und nicht endgültig sind, und vor allem, wenn Sie erwarten, dass eines Ihrer (unterschiedlich tiefen) Kinder eigene Generika hat. Normalerweise können Sie diese Überlegungen jedoch so gestalten, dass Sie am meisten davon profitieren.

Hoffe das hat jemandem geholfen! Ich erkenne, dass dieser Beitrag uralt ist. Ich werde diese Erklärung wahrscheinlich ausschneiden und für andere Fragen aufbewahren.

123
Mark McKenna

Eigentlich habe ich das zum Laufen gebracht. Betrachten Sie den folgenden Ausschnitt:

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)"

     }
 }

Ich verwende jdk 1.6

20
Andrey Rodionov

Es gibt tatsächlich eine Lösung, indem Sie den "anonymous class" Trick anwenden und die Ideen der Super Type Tokens :

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 {
}

Der Trick liegt in der Erstellung einer anonymen Klasse, new ArrayList<SpiderMan>() {}, anstelle der ursprünglichen (einfachen) new ArrayList<SpiderMan>(). Die Verwendung einer anoymous-Klasse (wenn möglich) stellt sicher, dass der Compiler Informationen über das Typargument SpiderMan speichert, das dem Typparameter List<?> übergeben wird. Voilà!

Nein, das ist nicht möglich. Aufgrund von Abwärtskompatibilitätsproblemen basieren die Generics von Java auf der Typ-Löschung , d.h. Zur Laufzeit ist alles, was Sie haben, ein nicht generisches List-Objekt. Es gibt einige Informationen zu Typparametern zur Laufzeit, aber sie befinden sich in Klassendefinitionen (d. H. Sie können fragen " welchen generischen Typ verwendet die Definition dieses Feldes? "), nicht in Objektinstanzen.

6

Aufgrund der Typenlöschung besteht die einzige Möglichkeit, den Typ der Liste zu kennen, darin, den Typ als Parameter an die Methode zu übergeben:

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) {

    }

}
6
Jared Russell

Wie @bertolami ausgeführt hat, ist es nicht möglich, einen Variablentyp zu verwenden und seinen zukünftigen Wert (den Inhalt der Variablen typeOfList) abzurufen.

Trotzdem können Sie die Klasse als Parameter darauf übergeben:

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 );
    }
}

Das ist mehr oder weniger das, was Google tut, wenn Sie eine Klassenvariable an den Konstruktor von ActivityInstrumentationTestCase2 übergeben müssen.

5
Snicolas

Anhang zur Antwort von @ DerMike zum Abrufen des generischen Parameters einer parametrisierten Schnittstelle (mithilfe der #getGenericInterfaces () -Methode innerhalb einer Java-8-Standardmethode, um Doppelarbeit zu vermeiden)

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
}
5
Campa

Nein, das ist nicht möglich.

Sie können einen generischen Typ eines Felds erhalten, wenn eine Klasse die einzige Ausnahme von dieser Regel ist, und das ist sogar ein bisschen hack.

Siehe Typ des generischen Typs in Java für ein Beispiel dafür.

3
cletus

Die schnelle Antwort der Question lautet nein, Sie können es nicht, weil der generische Java-Typ gelöscht wird.

Die längere Antwort wäre, wenn Sie Ihre Liste folgendermaßen erstellt haben:

new ArrayList<SpideMan>(){}

In diesem Fall wird der generische Typ in der generischen Oberklasse der neuen anonymen Klasse oben beibehalten. 

Nicht dass ich empfehle, dies mit Listen zu tun, aber es ist eine Listener-Implementierung:

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

Und da sich die generischen Typen von Superklassen und Superschnittstellen zwischen JVMs ändern, ist die generische Lösung nicht so einfach, wie manche Antworten vermuten lassen.

Hier habe ich es jetzt gemacht.

0
TacB0sS

Ich habe dies für Methoden codiert, die Iterable<?...> akzeptieren oder zurückgeben. Hier ist der Code:

/**
 * 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
Ondra Žižka

Dies ist nicht möglich, da Generics in Java nur zur Kompilierzeit berücksichtigt werden. Die Java-Generics sind also nur eine Art Vorprozessor. Sie können jedoch die tatsächliche Klasse der Mitglieder der Liste erhalten.

0
bertolami

Sie können keinen generischen Parameter von einer Variablen erhalten. Sie können aber aus einer Methode oder einem Feld deklarieren:

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

Sie können den Typ eines generischen Parameters mit Reflektion wie in diesem Beispiel, das ich hier gefunden habe, erhalten:

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());
    }
}
0
madx

Nur für mich war das Lesen dieses Codeausschnitts hart, ich habe ihn nur in zwei lesbare Zeilen unterteilt:

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

Ich wollte eine Instanz des Type-Parameters erstellen, ohne Parameter für meine Methode zu haben:

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