Wie erhalte ich eine Klasseninstanz vom generischen Typ T?

700

Ich habe eine Generika-Klasse Foo<T>. In einer Methode von Foomöchte ich die Klasseninstanz vom Typ erhalten T, kann sie aber nicht aufrufen T.class.

Was ist der bevorzugte Weg, um es zu umgehen T.class?

Robinmag
quelle
2
Versuchen Sie Antworten in dieser Frage. Ich denke, es ist ähnlich. stackoverflow.com/questions/1942644/…
Emil
3
Mögliches Duplikat von Get generic type of class zur Laufzeit
7hi4g0
1
Mögliches Duplikat der Instanziierung einer generischen Klasse in Java
Matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga

Antworten:

567

Die kurze Antwort lautet, dass es in Java keine Möglichkeit gibt, den Laufzeittyp generischer Typparameter herauszufinden. Ich empfehle, das Kapitel über das Löschen von Typen im Java-Lernprogramm zu lesen, um weitere Informationen zu erhalten.

Eine beliebte Lösung hierfür besteht darin, den ClassParameter type vom Typ an den Konstruktor des generischen Typs zu übergeben, z

class Foo<T> {
    final Class<T> typeParameterClass;

    public Foo(Class<T> typeParameterClass) {
        this.typeParameterClass = typeParameterClass;
    }

    public void bar() {
        // you can access the typeParameterClass here and do whatever you like
    }
}
Zsolt Török
quelle
73
Diese Antwort bietet zwar eine gültige Lösung, es ist jedoch ungenau zu sagen, dass es zur Laufzeit keine Möglichkeit gibt, den generischen Typ zu finden. Es stellt sich heraus, dass das Löschen von Typen weitaus komplexer ist als das Löschen von Decken. Meine Antwort zeigt Ihnen, wie Sie den generischen Klassentyp erhalten.
Ben Thurley
3
@ BenThurley Ordentlicher Trick, aber soweit ich sehen kann, funktioniert er nur, wenn es einen generischen Supertyp gibt, den er verwenden kann. In meinem Beispiel können Sie den Typ von T in Foo <T> nicht abrufen.
Zsolt Török
@webjockey Nein, das solltest du nicht. Das Zuweisen typeParameterClassohne Standardzuweisung im Konstruktor ist vollkommen in Ordnung. Es ist nicht erforderlich, es ein zweites Mal einzustellen.
Adowrath
Dies ist die erste Lösung, die mir in den Sinn kommt, aber manchmal sind es nicht Sie, die Objekte erstellen / initiieren. Sie können den Konstruktor also nicht verwenden. Zum Beispiel beim Abrufen von JPA-Entitäten aus der Datenbank.
Paramvir Singh Karwal
233

Ich suchte nach einer Möglichkeit, dies selbst zu tun, ohne dem Klassenpfad eine zusätzliche Abhängigkeit hinzuzufügen. Nach einigen Nachforschungen stellte ich fest, dass dies möglich ist , solange Sie einen generischen Supertyp haben. Dies war für mich in Ordnung, da ich mit einer DAO- Ebene mit einem generischen Ebenen-Supertyp arbeitete. Wenn dies zu Ihrem Szenario passt, ist es meiner Meinung nach der beste Ansatz.

Die meisten Anwendungsfälle für Generika, auf die ich gestoßen bin, haben einen generischen Supertyp, z. B. List<T>für ArrayList<T>oder GenericDAO<T>für DAO<T>usw.

Reine Java-Lösung

Der Artikel Zugriff auf generische Typen zur Laufzeit in Java erklärt, wie Sie dies mit reinem Java tun können.

@SuppressWarnings("unchecked")
public GenericJpaDao() {
  this.entityBeanType = ((Class) ((ParameterizedType) getClass()
      .getGenericSuperclass()).getActualTypeArguments()[0]);
}

Federlösung

Mein Projekt verwendete Spring, was sogar noch besser ist, da Spring eine praktische Dienstprogrammmethode zum Finden des Typs hat. Dies ist der beste Ansatz für mich, da er am besten aussieht. Ich denke, wenn Sie Spring nicht verwenden würden, könnten Sie Ihre eigene Dienstprogrammmethode schreiben.

import org.springframework.core.GenericTypeResolver;

public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{

    @Autowired
    private SessionFactory sessionFactory;

    private final Class<T> genericType;

    private final String RECORD_COUNT_HQL;
    private final String FIND_ALL_HQL;

    @SuppressWarnings("unchecked")
    public AbstractHibernateDao()
    {
        this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
        this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
        this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
    }
Ben Thurley
quelle
1
Bitte klären Sie die Bedeutung der Argumente von resolveTypeArgument
gstackoverflow
getClass () ist eine Methode von java.lang.Object, die zur Laufzeit die Klasse des bestimmten Objekts zurückgibt. Dies ist das Objekt, für das Sie den Typ auflösen möchten. AbstractHibernateDao.class ist nur der Name der Basisklasse oder Superklasse der generischen Typklassenhierarchie. Die Importanweisung ist enthalten, damit Sie die Dokumente leicht finden und selbst überprüfen können. Dies ist die Seite docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
Was ist die Federlösung mit Version 4.3.6 und höher. Es funktioniert nicht mit Feder 4.3.6.
Erlan
1
Der Link in "Pure Java Solution" ist defekt, es ist jetzt blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen
1
@ AlikElzin-kilaka wird im Konstruktor mit der Spring-Klasse GenericTypeResolver initialisiert.
Ben Thurley
103

Es gibt jedoch eine kleine Lücke: Wenn Sie Ihre FooKlasse als abstrakt definieren. Das würde bedeuten, dass Sie Ihre Klasse instanziieren müssen als:

Foo<MyType> myFoo = new Foo<MyType>(){};

(Beachten Sie die doppelten Klammern am Ende.)

Jetzt können Sie den Typ von Tzur Laufzeit abrufen :

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];

Beachten Sie jedoch, dass mySuperclassdies die Oberklasse der Klassendefinition sein muss, die den endgültigen Typ für definiert T.

Es ist auch nicht sehr elegant, aber Sie müssen entscheiden, ob Sie es bevorzugen new Foo<MyType>(){}oder new Foo<MyType>(MyType.class);in Ihrem Code.


Zum Beispiel:

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

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;

/**
 * Captures and silently ignores stack exceptions upon popping.
 */
public abstract class SilentStack<E> extends ArrayDeque<E> {
  public E pop() {
    try {
      return super.pop();
    }
    catch( NoSuchElementException nsee ) {
      return create();
    }
  }

  public E create() {
    try {
      Type sooper = getClass().getGenericSuperclass();
      Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];

      return (E)(Class.forName( t.toString() ).newInstance());
    }
    catch( Exception e ) {
      return null;
    }
  }
}

Dann:

public class Main {
    // Note the braces...
    private Deque<String> stack = new SilentStack<String>(){};

    public static void main( String args[] ) {
      // Returns a new instance of String.
      String s = stack.pop();
      System.out.printf( "s = '%s'\n", s );
    }
}
Bert Bruynooghe
quelle
5
Dies ist mit Sicherheit die beste Antwort hier! Für das, was es wert ist, ist dies die Strategie, die Google Guice zum Binden von Klassen mitTypeLiteral
ATG
14
Beachten Sie, dass jedes Mal, wenn diese Methode der Objektkonstruktion verwendet wird, eine neue anonyme Klasse erstellt wird. Mit anderen Worten, zwei Objekte aund berstellt diese Weise werden beide die gleiche Klasse erweitern, haben aber nicht identische Instanz Klassen. a.getClass() != b.getClass()
Martin Serrano
3
Es gibt ein Szenario, in dem dies nicht funktioniert. Wenn Foo eine Schnittstelle wie Serializable implementieren sollte, wäre die anonyme Klasse nur dann serialisierbar, wenn die Klasse instanziiert. Ich habe versucht, dies zu umgehen, indem ich eine serialisierbare Factory-Klasse erstellt habe, die die von Foo abgeleitete anonyme Klasse erstellt. Aus irgendeinem Grund gibt getActualTypeArguments jedoch den generischen Typ anstelle der tatsächlichen Klasse zurück. Zum Beispiel: (neue FooFactory <MyType> ()). CreateFoo ()
Lior Chaga
38

Ein Standardansatz / eine Problemumgehung / eine Standardlösung besteht darin class, den Konstruktoren ein Objekt hinzuzufügen , wie z.

 public class Foo<T> {

    private Class<T> type;
    public Foo(Class<T> type) {
      this.type = type;
    }

    public Class<T> getType() {
      return type;
    }

    public T newInstance() {
      return type.newInstance();
    }
 }
Andreas Dolk
quelle
1
Aber es schien nicht @autowired in der tatsächlichen Verwendung, eine Möglichkeit, um zu umgehen?
Alfred Huang
@AlfredHuang Die Lösung besteht darin, eine Bean für die Klasse zu erstellen, die dies tut, und sich nicht auf das automatische Verdrahten zu verlassen.
Calebj
20

Stellen Sie sich vor, Sie haben eine abstrakte Oberklasse, die generisch ist:

public abstract class Foo<? extends T> {}

Und dann haben Sie eine zweite Klasse, die Foo mit einem generischen Balken erweitert, der T erweitert:

public class Second extends Foo<Bar> {}

Sie können die Klasse Bar.classin der Foo-Klasse erhalten, indem Sie die Type(aus der Antwort von bert bruynooghe) auswählen und mithilfe der folgenden ClassInstanz ableiten :

Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);

Sie müssen beachten, dass diese Operation nicht ideal ist. Daher empfiehlt es sich, den berechneten Wert zwischenzuspeichern, um Mehrfachberechnungen zu vermeiden. Eine der typischen Anwendungen ist die generische DAO-Implementierung.

Die endgültige Implementierung:

public abstract class Foo<T> {

    private Class<T> inferedClass;

    public Class<T> getGenericClass(){
        if(inferedClass == null){
            Type mySuperclass = getClass().getGenericSuperclass();
            Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
            String className = tType.toString().split(" ")[1];
            inferedClass = Class.forName(className);
        }
        return inferedClass;
    }
}

Der zurückgegebene Wert ist Bar.class, wenn er von der Foo-Klasse in einer anderen Funktion oder von der Bar-Klasse aufgerufen wird.

droidpl
quelle
1
toString().split(" ")[1]das war das Problem, vermeiden Sie die"class "
IgniteCoders
16

Hier ist eine funktionierende Lösung:

@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
    try {
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
        Class<?> clazz = Class.forName(className);
        return (Class<T>) clazz;
    } catch (Exception e) {
        throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
    }
} 

HINWEISE: Kann nur als Oberklasse verwendet werden

  1. Muss mit typed class ( Child extends Generic<Integer>) erweitert werden

ODER

  1. Muss als anonyme Implementierung erstellt werden ( new Generic<Integer>() {};)
Jaroslaw Kowbas
quelle
3
getTypeName ruft toString auf und kann daher durch .getActualTypeArguments () [0] .toString () ersetzt werden.
Jaroslaw Kowbas
9

Eine bessere Route als die von den anderen vorgeschlagene Klasse besteht darin, ein Objekt zu übergeben, das das kann, was Sie mit der Klasse getan hätten, z. B. eine neue Instanz zu erstellen.

interface Factory<T> {
  T apply();
}

<T> void List<T> make10(Factory<T> factory) {
  List<T> result = new ArrayList<T>();
  for (int a = 0; a < 10; a++)
    result.add(factory.apply());
  return result;
}

class FooFactory<T> implements Factory<Foo<T>> {
  public Foo<T> apply() {
    return new Foo<T>();
  }
}

List<Foo<Integer>> foos = make10(new FooFactory<Integer>());
Ricky Clarkson
quelle
@ Ricky Clarkson: Ich sehe nicht ein, wie diese Fabrik parametrisierte Foos zurückgeben soll. Könnten Sie bitte erklären, wie Sie Foo <T> daraus machen? Es scheint mir, dass dies nur nicht parametrisiertes Foo ergibt. Ist das T in make10 hier nicht einfach Foo?
ib84
@ ib84 Ich habe den Code behoben. Ich habe anscheinend übersehen, dass Foo parametrisiert wurde, als ich die Antwort ursprünglich schrieb.
Ricky Clarkson
9

Ich hatte dieses Problem in einer abstrakten generischen Klasse. In diesem speziellen Fall ist die Lösung einfacher:

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

und später die abgeleitete Klasse:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
user1154664
quelle
Ja, aber ich möchte ein Minimum belassen, das bei der Erweiterung dieser Klasse getan werden muss. Überprüfen Sie die Antwort von
Droidpl
5

Ich habe eine (hässliche, aber effektive) Lösung für dieses Problem, die ich kürzlich verwendet habe:

import java.lang.reflect.TypeVariable;


public static <T> Class<T> getGenericClass()
{
    __<T> ins = new __<T>();
    TypeVariable<?>[] cls = ins.getClass().getTypeParameters(); 

    return (Class<T>)cls[0].getClass();
}

private final class __<T> // generic helper class which does only provide type information
{
    private __()
    {
    }
}
unbekannt6656
quelle
3

Ich habe einen generischen und einfachen Weg gefunden, dies zu tun. In meiner Klasse habe ich eine Methode erstellt, die den generischen Typ entsprechend seiner Position in der Klassendefinition zurückgibt. Nehmen wir eine Klassendefinition wie diese an:

public class MyClass<A, B, C> {

}

Erstellen wir nun einige Attribute, um die Typen beizubehalten:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;

// Getters and setters (not necessary if you are going to use them internally)

    } 

Anschließend können Sie eine generische Methode erstellen, die den Typ basierend auf dem Index der generischen Definition zurückgibt:

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {
        // To make it use generics without supplying the class type
        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }

Schließlich rufen Sie im Konstruktor einfach die Methode auf und senden den Index für jeden Typ. Der vollständige Code sollte folgendermaßen aussehen:

public class MyClass<A, B, C> {

    private Class<A> aType;

    private Class<B> bType;

    private Class<C> cType;


    public MyClass() {
      this.aType = (Class<A>) getGenericClassType(0);
      this.bType = (Class<B>) getGenericClassType(1);
      this.cType = (Class<C>) getGenericClassType(2);
    }

   /**
     * Returns a {@link Type} object to identify generic types
     * @return type
     */
    private Type getGenericClassType(int index) {

        Type type = getClass().getGenericSuperclass();

        while (!(type instanceof ParameterizedType)) {
            if (type instanceof ParameterizedType) {
                type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
            } else {
                type = ((Class<?>) type).getGenericSuperclass();
            }
        }

        return ((ParameterizedType) type).getActualTypeArguments()[index];
    }
}
Juan Urrego
quelle
2

Wie in anderen Antworten erklärt, um dies zu verwenden ParameterizedType , müssen Sie die Klasse erweitern, Ansatz scheint jedoch zusätzliche Arbeit zu sein, um eine ganz neue Klasse zu erstellen, die sie erweitert ...

Wenn Sie die Klasse abstrakt machen, müssen Sie sie erweitern, um die Unterklassenanforderungen zu erfüllen. (mit @Getter von lombok).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

    public ConfigurationDefinition(...) {
        this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        ...
    }
}

Nun, um es zu erweitern, ohne eine neue Klasse zu definieren. (Beachten Sie das {} am Ende ... erweitert, aber überschreiben Sie nichts - es sei denn, Sie möchten).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
Riaan Schutte
quelle
2

Ich gehe davon aus, dass Sie, da Sie eine generische Klasse haben, eine solche Variable haben würden:

private T t;

(Diese Variable muss im Konstruktor einen Wert annehmen.)

In diesem Fall können Sie einfach die folgende Methode erstellen:

Class<T> getClassOfInstance()
{
    return (Class<T>) t.getClass();
}

Ich hoffe es hilft!

Pontios
quelle
1
   public <T> T yourMethodSignature(Class<T> type) {

        // get some object and check the type match the given type
        Object result = ...            

        if (type.isAssignableFrom(result.getClass())) {
            return (T)result;
        } else {
            // handle the error
        }
   }
Abel ANEIROS
quelle
1

Wenn Sie eine Klasse / Schnittstelle erweitern oder implementieren, die Generika verwendet, erhalten Sie möglicherweise den generischen Typ der übergeordneten Klasse / Schnittstelle, ohne eine vorhandene Klasse / Schnittstelle zu ändern.

Es könnte drei Möglichkeiten geben,

Fall 1 Wenn Ihre Klasse eine Klasse erweitert, die Generics verwendet

public class TestGenerics {
    public static void main(String[] args) {
        Type type = TestMySuperGenericType.class.getGenericSuperclass();
        Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
        for(Type gType : gTypes){
            System.out.println("Generic type:"+gType.toString());
        }
    }
}

class GenericClass<T> {
    public void print(T obj){};
}

class TestMySuperGenericType extends GenericClass<Integer> {
}

Fall 2 Wenn Ihre Klasse eine Schnittstelle implementiert, die Generics verwendet

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

class TestMySuperGenericType implements GenericClass<Integer> {
    public void print(Integer obj){}
}

Fall 3 Wenn Ihre Schnittstelle eine Schnittstelle erweitert, die Generika verwendet

public class TestGenerics {
    public static void main(String[] args) {
        Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
        for(Type type : interfaces){
            Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
            for(Type gType : gTypes){
                System.out.println("Generic type:"+gType.toString());
            }
        }
    }
}

interface GenericClass<T> {
    public void print(T obj);
}

interface TestMySuperGenericType extends GenericClass<Integer> {
}
Anil Agrawal
quelle
1

Das ist ziemlich einfach. Wenn Sie aus derselben Klasse benötigen:

Class clazz = this.getClass();
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
try {
        Class typeClass = Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName() );
        // You have the instance of type 'T' in typeClass variable

        System.out.println( "Class instance name: "+  typeClass.getName() );
    } catch (ClassNotFoundException e) {
        System.out.println( "ClassNotFound!! Something wrong! "+ e.getMessage() );
    }
Gana
quelle
0

Ich nehme an, Sie haben ein Feld in Ihrer Klasse vom Typ T. Wenn es kein Feld vom Typ T gibt, was bringt es, einen generischen Typ zu haben? Sie können also einfach eine Instanz von in diesem Feld ausführen.

In meinem Fall habe ich eine

<T> Elemente auflisten;
in meiner Klasse, und ich überprüfe, ob der Klassentyp "Lokalität" von ist

if (items.get (0) Instanz der Lokalität) ...

Dies funktioniert natürlich nur, wenn die Gesamtzahl der möglichen Klassen begrenzt ist.

Christine
quelle
4
Was mache ich, wenn items.isEmpty () wahr ist?
chaotisches Gleichgewicht
0

Diese Frage ist alt, aber jetzt ist es am besten, Google zu verwenden Gson.

Ein Beispiel, um benutzerdefinierte zu erhalten viewModel.

Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);

Generische Typklasse

class GenericClass<T>(private val rawType: Class<*>) {

    constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))

    fun getRawType(): Class<T> {
        return rawType as Class<T>
    }
}
Vahe Gharibyan
quelle
0

Ich wollte T.class an eine Methode übergeben, die Generika verwendet

Die Methode readFile liest eine durch den Dateinamen angegebene CSV-Datei mit dem vollständigen Pfad. Es kann CSV-Dateien mit unterschiedlichen Inhalten geben, daher muss ich die Modelldateiklasse übergeben, damit ich die entsprechenden Objekte erhalten kann. Da dies das Lesen einer CSV-Datei ist, wollte ich dies generisch tun. Aus irgendeinem Grund hat keine der oben genannten Lösungen für mich funktioniert. Ich muss es verwenden Class<? extends T> type, damit es funktioniert. Ich benutze die opencsv-Bibliothek zum Parsen der CSV-Dateien.

private <T>List<T> readFile(String fileName, Class<? extends T> type) {

    List<T> dataList = new ArrayList<T>();
    try {
        File file = new File(fileName);

        Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        Reader headerReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        CSVReader csvReader = new CSVReader(headerReader);
        // create csv bean reader
        CsvToBean<T> csvToBean = new CsvToBeanBuilder(reader)
                .withType(type)
                .withIgnoreLeadingWhiteSpace(true)
                .build();

        dataList = csvToBean.parse();
    }
    catch (Exception ex) {
        logger.error("Error: ", ex);
    }

    return dataList;
}

So wird die readFile-Methode aufgerufen

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
Madhu Tomy
quelle
-4

Ich verwende eine Problemumgehung dafür:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
nikolai.serdiuk
quelle