Come posso ottenere un'istanza di classe di tipo T generico?


700

Ho una classe di farmaci generici, Foo<T>. In un metodo di Foo, voglio ottenere l'istanza di classe di tipo T, ma non riesco proprio a chiamare T.class.

Qual è il modo preferito per aggirarlo usando T.class?


2
Prova le risposte in questa domanda. Penso che sia simile. stackoverflow.com/questions/1942644/…
Emil



1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga,

Risposte:


567

La risposta breve è che non è possibile scoprire il tipo di runtime di parametri di tipo generico in Java. Suggerisco di leggere il capitolo sulla cancellazione dei tipi nel Tutorial Java per maggiori dettagli.

Una soluzione popolare a questo è passare il Classparametro del tipo nel costruttore del tipo generico, ad es

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

73
Questa risposta fornisce una soluzione valida ma non è corretto affermare che non è possibile trovare il tipo generico in fase di esecuzione. Si scopre che la cancellazione del tipo è molto più complessa di una cancellazione generale. La mia risposta ti mostra come ottenere le classi di tipo generico.
Ben Thurley,

3
@BenThurley Trucco accurato, ma per quanto posso vedere funziona solo se esiste un supertipo generico da usare. Nel mio esempio, non è possibile recuperare il tipo di T in Foo <T>.
Zsolt Török,

@webjockey No, non dovresti. Assegnare typeParameterClasssenza un'assegnazione di default nel costruttore va benissimo. Non è necessario impostarlo una seconda volta.
Adowrath,

Questa è la prima soluzione che viene in mente, ma a volte non sei tu a creare / avviare oggetti. Quindi non sarai in grado di usare il costruttore. Ad esempio durante il recupero di entità JPA dal database.
Paramvir Singh Karwal,

233

Stavo cercando un modo per farlo da solo senza aggiungere una dipendenza extra al percorso di classe. Dopo alcune indagini ho scoperto che è possibile purché si disponga di un supertipo generico. Questo per me andava bene perché stavo lavorando con un livello DAO con un supertipo di livello generico. Se questo si adatta al tuo scenario, allora è l'approccio più pulito IMHO.

La maggior parte dei casi di uso di farmaci generici che ho incontrato hanno una sorta di supertipo generico, ad esempio List<T>per ArrayList<T>o GenericDAO<T>per DAO<T>, ecc

Soluzione Java pura

L'articolo Accesso ai tipi generici in fase di esecuzione in Java spiega come è possibile farlo utilizzando Java puro.

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

Soluzione di primavera

Il mio progetto utilizzava Spring, che è ancora meglio in quanto Spring ha un pratico metodo di utilità per trovare il tipo. Questo è l'approccio migliore per me in quanto sembra più pulito. Immagino che se non stavi usando Spring potresti scrivere il tuo metodo di utilità.

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

1
Si prega di chiarire il significato di resolveTypeArgument argomenti
gstackoverflow

getClass () è un metodo di java.lang.Object che restituirà la classe dell'oggetto specifico in fase di runtime, questo è l'oggetto per cui si desidera risolvere il tipo. AbstractHibernateDao.class è solo il nome della classe base o superclasse della gerarchia di classi di tipo generico. La dichiarazione di importazione è inclusa, quindi dovresti essere in grado di trovare facilmente i documenti e controllare te stesso. Questa è la pagina docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley,

Qual è la soluzione di primavera con versione 4.3.6 e successive. Non funziona con la primavera 4.3.6.
Erlan

1
Il collegamento in "Soluzione Pure Java" è interrotto, ora è blog.xebia.com/acessing-generic-types-at-runtime-in-java
Nick Breen,

1
@ AlikElzin-kilaka viene inizializzato nel costruttore utilizzando la classe Spring GenericTypeResolver.
Ben Thurley,

103

C'è comunque una piccola lacuna: se definisci la tua Fooclasse come astratta. Ciò significherebbe che devi istanziare la tua classe come:

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

(Nota le doppie parentesi graffe alla fine.)

Ora puoi recuperare il tipo di Truntime:

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

Si noti tuttavia che mySuperclassdeve essere la superclasse della definizione della classe che in realtà definisce il tipo finale per T.

Inoltre non è molto elegante, ma devi decidere se preferisci new Foo<MyType>(){}o new Foo<MyType>(MyType.class);nel tuo codice.


Per esempio:

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

Poi:

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

5
Questa è sicuramente la risposta migliore qui! Inoltre, per quello che vale, questa è la strategia che Google Guice utilizza per associare le lezioni conTypeLiteral
ATG

14
Si noti che ogni volta che viene utilizzato questo metodo di costruzione di oggetti, viene creata una nuova classe anonima. In altre parole, due oggetti ae bgenerato in questo modo si sia estendere la stessa classe, ma non hanno classi istanza identici. a.getClass() != b.getClass()
Martin Serrano,

3
C'è uno scenario in cui questo non funziona. Se Foo dovesse implementare un'interfaccia, come Serializable, la classe anonima non sarebbe Serializable a meno che non lo sia l'istanza della classe. Ho cercato di aggirare il problema creando una classe factory serializzabile che crea la classe anonima derivata da Foo, ma poi, per qualche motivo, getActualTypeArguments restituisce il tipo generico anziché la classe effettiva. Ad esempio: (nuovo FooFactory <MyType> ()). CreateFoo ()
Lior Chaga

38

Un approccio standard / soluzione alternativa / soluzione consiste nell'aggiungere un classoggetto ai costruttori, come:

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

1
Ma sembrava che non potesse essere @autowired nell'uso reale, un modo per aggirare?
Alfred Huang,

@AlfredHuang Il problema sarebbe creare un bean per la classe che fa questo e non fare affidamento sull'autowiring.
Calebj,

20

Immagina di avere una superclasse astratta generica:

public abstract class Foo<? extends T> {}

E poi hai una seconda classe che estende Foo con una barra generica che estende T:

public class Second extends Foo<Bar> {}

Puoi ottenere la classe Bar.classnella classe Foo selezionando la Type(dalla risposta bert bruynooghe) e inferendola usando Classistanza:

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

Devi notare che questa operazione non è l'ideale, quindi è una buona idea memorizzare nella cache il valore calcolato per evitare calcoli multipli su questo. Uno degli usi tipici è l'implementazione DAO generica.

L'implementazione finale:

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

Il valore restituito è Bar.class quando viene richiamato dalla classe Foo in un'altra funzione o dalla classe Bar.


1
toString().split(" ")[1]quello era il problema, evita"class "
IgniteCoders

16

Ecco una soluzione funzionante:

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

NOTE: può essere utilizzato solo come superclasse

  1. Deve essere esteso con la classe digitata ( Child extends Generic<Integer>)

O

  1. Deve essere creato come implementazione anonima ( new Generic<Integer>() {};)

3
getTypeName chiama toString, quindi può essere sostituito da .getActualTypeArguments () [0] .toString ();
Yaroslav Kovbas,


9

Un percorso migliore rispetto alla Classe suggerito dagli altri è quello di passare un oggetto in grado di fare ciò che avresti fatto con la Classe, ad esempio creare una nuova istanza.

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: non vedo come questa fabbrica dovrebbe restituire foos parametrizzati. Potresti spiegare come ottenere Foo <T> da questo? Mi sembra che ciò dia solo Foo non parametrizzato. La T in make10 non è semplicemente Foo qui?
ib84,

@ ib84 ho corretto il codice; Sembra che mi sia perso il fatto che Foo fosse parametrizzato quando ho scritto la risposta in origine.
Ricky Clarkson,

9

Ho avuto questo problema in una classe generica astratta. In questo caso particolare, la soluzione è più semplice:

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

e in seguito la classe derivata:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}

Sì lo è, ma vorrei lasciare un minimo che deve essere fatto durante l'estensione di questa classe. Controlla la risposta di
Droidpl

5

Ho una soluzione (brutta ma efficace) per questo problema, che ho usato di recente:

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 __()
    {
    }
}


3

Ho trovato un modo generico e semplice per farlo. Nella mia classe ho creato un metodo che restituisce il tipo generico in base alla sua posizione nella definizione della classe. Assumiamo una definizione di classe come questa:

public class MyClass<A, B, C> {

}

Ora creiamo alcuni attributi per persistere nei tipi:

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)

    } 

Quindi è possibile creare un metodo generico che restituisce il tipo basato sull'indice della definizione generica:

   /**
     * 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];
    }

Infine, nel costruttore basta chiamare il metodo e inviare l'indice per ciascun tipo. Il codice completo dovrebbe apparire come:

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

2

Come spiegato in altre risposte, per utilizzare questo ParameterizedTypeapproccio, è necessario estendere la classe, ma sembra un lavoro extra per creare una classe completamente nuova che la estenda ...

Quindi, rendendo astratta la classe ti costringe ad estenderla, soddisfacendo così il requisito di sottoclasse. (usando @Getter di lombok).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

Ora per estenderlo senza definire una nuova classe. (Nota il {} alla fine ... esteso, ma non sovrascrivere nulla, a meno che tu non voglia).

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

2

Presumo che, dato che hai una classe generica, avresti una variabile del genere:

private T t;

(questa variabile deve assumere un valore nel costruttore)

In tal caso puoi semplicemente creare il seguente metodo:

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

Spero che sia d'aiuto!


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

1

Se si sta estendendo o implementando qualsiasi classe / interfaccia che utilizza generici, è possibile ottenere il tipo generico di classe / interfaccia genitore, senza modificare alcuna classe / interfaccia esistente.

Potrebbero esserci tre possibilità,

Caso 1 Quando la tua classe estende una classe che utilizza Generics

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

Caso 2 Quando la tua classe sta implementando un'interfaccia che utilizza Generics

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

Caso 3 Quando l'interfaccia sta estendendo un'interfaccia che utilizza Generics

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

1

È piuttosto semplice. Se hai bisogno all'interno della stessa classe:

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

0

In realtà, suppongo che tu abbia un campo nella tua classe di tipo T. Se non c'è un campo di tipo T, che senso ha avere un tipo generico? Quindi, puoi semplicemente fare un'istanza di quel campo.

Nel mio caso, ho un

Elencare <T> elementi;
nella mia classe e controllo se il tipo di classe è "Località" di

if (items.get (0) instanceof Località) ...

Naturalmente, questo funziona solo se il numero totale di classi possibili è limitato.


4
Cosa devo fare se items.isEmpty () è vero?
chaotic3quilibrium

0

Questa domanda è vecchia, ma ora il migliore è usare google Gson.

Un esempio per personalizzare viewModel.

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

Classe di tipo generico

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

0

Volevo passare T.class a un metodo che utilizza Generics

Il metodo readFile legge un file .csv specificato da fileName con percorso completo. Ci possono essere file CSV con contenuti diversi, quindi devo passare la classe del file modello in modo da poter ottenere gli oggetti appropriati. Dato che sto leggendo il file CSV, ho voluto fare in modo generico. Per qualche ragione o nessuna delle soluzioni di cui sopra ha funzionato per me. Devo usare Class<? extends T> typeper farlo funzionare. Uso la libreria opencsv per analizzare i file CSV.

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

Ecco come viene chiamato il metodo readFile

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);

-4

Sto usando una soluzione alternativa per questo:

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

MyClass myClassInstance = MyClass.class.newInstance();
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.