Jak uzyskać instancję klasy generyków typu T

Mam lekcję generyki, Foo<T>. W metodzie Foo chcę uzyskać instancję klasy typu T, ale po prostu nie mogę wywołać T.class.

Jaki jest preferowany sposób obejścia go za pomocą T.class?

 533
Author: Peter Mortensen, 2010-08-09

17 answers

Krótka odpowiedź jest taka, że w Javie nie ma sposobu na określenie typu runtime ogólnych parametrów typu. Sugeruję przeczytanie rozdziału o usuwaniu typów w Java Tutorial Po Więcej Szczegółów.

Popularnym rozwiązaniem jest przekazanie Class parametru type do konstruktora typu generycznego, np.

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
    }
}
 454
Author: Zsolt Török,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-09 07:09:48

Szukałem sposobu, aby to zrobić sam bez dodawania dodatkowej zależności do ścieżki klasowej. Po jakimś śledztwie okazało się, że na możliwe, o ile masz ogólny supertyp. Było to dla mnie OK, ponieważ pracowałem z Dao warstwą z ogólnym supertyp warstwy. Jeśli to pasuje do twojego scenariusza, to jest to najbardziej schludne podejście IMHO.

Większość przypadków użycia generyków, z którymi się spotkałem, ma jakiś rodzaj ogólnego supertypu, np. List<T> dla ArrayList<T> lub GenericDAO<T> dla DAO<T>, itd.

Pure Java solution

Artykuł uzyskiwanie dostępu do typów generycznych podczas pracy w Javie wyjaśnia, jak można to zrobić za pomocą czystej Javy.

Rozwiązanie sprężyny

Mój projekt wykorzystywał Spring co jest jeszcze lepsze, ponieważ Spring ma przydatną metodę znajdowania typu. Jest to dla mnie najlepsze podejście, ponieważ wygląda schludnie. Myślę, że gdybyś nie używał Springa, mógłbyś napisać własną metodę użytkową.

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 ";
    }
 182
Author: Ben Thurley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-02 11:17:15

Istnieje jednak mała luka: jeśli zdefiniujesz klasę Foo jako abstrakcyjną. Oznacza to, że musisz utworzyć klasę jako:

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

(zwróć uwagę na podwójne szelki na końcu.)

Teraz możesz pobrać typ T w czasie wykonywania:

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

Zauważ jednak, że mySuperclass musi być superklasą definicji klasy faktycznie definiującej ostateczny typ dla T.

Nie jest też zbyt elegancki, ale musisz zdecydować, czy wolisz new Foo<MyType>(){} czy new Foo<MyType>(MyType.class); w Twoim kodzie.


Na przykład:

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

Potem:

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 );
    }
}
 79
Author: bert bruynooghe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-07 11:37:59

Standardowym podejściem / obejściem / rozwiązaniem jest dodanie class obiektu do konstruktora(ów), np.:

 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();
    }
 }
 33
Author: Andreas_D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-09 17:37:20

Wyobraź sobie, że masz abstrakcyjną superklasę, która jest ogólna:

public abstract class Foo<? extends T> {}

I wtedy masz drugą klasę, która rozszerza Foo z ogólnym paskiem, który rozszerza T:

public class Second extends Foo<Bar> {}

Możesz uzyskać klasę Bar.class w klasie Foo, wybierając Type (od odpowiedzi Berta bruynooghe) i wnioskując ją za pomocą instancji Class:

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

Należy zauważyć, że ta operacja nie jest idealna, więc dobrym pomysłem jest buforowanie obliczonej wartości, aby uniknąć wielokrotnych obliczeń na tej podstawie. Jednym z typowych zastosowań jest w ogólnej implementacji DAO.

Ostateczna realizacja:

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

Zwracana wartość to Bar.Klasa wywołana z klasy Foo w innej funkcji lub z klasy Bar.

 17
Author: droidpl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-24 10:32:07

Oto rozwiązanie robocze:

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

Przypisy: może być używany tylko jako superclass

  1. musi być rozszerzona o klasę typed(Child extends Generic<Integer>)

lub

  1. musi być utworzona jako implementacja anonimowa (new Generic<Integer>() {};)
 10
Author: Yaroslav Kovbas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-17 08:55:59

Miałem ten problem w abstrakcyjnej klasie generycznej. W tym konkretnym przypadku rozwiązanie jest prostsze:

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

I Później Klasa pochodna:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
 8
Author: user1154664,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-05 23:36:38

Nie możesz tego zrobić z powodu typu kasowania. Zobacz także pytanie o przepełnienie stosuJava generics - type erasure-kiedy i co się dzieje.

 8
Author: Konrad Garus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:30

Lepszą drogą niż klasa, którą sugerują inni, jest przejście do obiektu, który może zrobić to, co zrobiłbyś z klasą, np. utworzyć nową instancję.

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>());
 7
Author: Ricky Clarkson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-14 13:04:00

To możliwe:

class Foo<T> {
  Class<T> clazz = (Class<T>) DAOUtil.getTypeArguments(Foo.class, this.getClass()).get(0);
}

Potrzebujesz dwóch funkcji z svn/trunk/dao/src/main/java/com/googlecode/genericdao/dao/ DAOUtil.java .

Aby uzyskać więcej wyjaśnień, zobacz refleksyjne leki generyczne.

 4
Author: Peter Tseng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-27 08:33:03

Mam (brzydkie, ale skuteczne) rozwiązanie tego problemu, z którego ostatnio korzystałem:

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 __()
    {
    }
}
 4
Author: Unknown6656,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-07 06:20:34

Znalazłem na to ogólny i prosty sposób. W mojej klasie stworzyłem metodę, która zwraca typ generyczny zgodnie z jego pozycją w definicji klasy. Przyjmijmy taką definicję klasy:

public class MyClass<A, B, C> {

}

Teraz stwórzmy kilka atrybutów do utrzymywania typów:

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)

    } 

Następnie można utworzyć metodę generyczną, która zwraca typ na podstawie indeksu definicji generycznej:

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

Na koniec, w konstruktorze wystarczy wywołać metodę i wysłać indeks dla każdego Typ. Pełny kod powinien wyglądać następująco:

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];
    }
}
 3
Author: Juan Urrego,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-03 14:21:01

Jak wyjaśniono w innych odpowiedziach, aby użyć tego podejścia ParameterizedType, musisz rozszerzyć klasę, ale wydaje się to dodatkową pracą, aby stworzyć zupełnie nową klasę, która ją rozszerza...

Więc, uczynienie klasy abstrakcyjną zmusza cię do jej rozszerzenia, spełniając w ten sposób wymóg podklasowania. (za pomocą lombok ' s @Getter).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

Teraz rozszerzyć go bez definiowania nowej klasy. (Zwróć uwagę na {} na końcu... rozszerzone, ale nie nadpisuj niczego-chyba, że chcesz).

private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
 2
Author: Riaan Schutte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-20 03:01:24
   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
Author: Abel ANEIROS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-02 12:45:55

Jeśli rozszerzasz lub implementujesz dowolną klasę / interfejs, która używa generycznych, możesz uzyskać ogólny typ nadrzędnej klasy / interfejsu, bez modyfikowania żadnej istniejącej klasy/interfejsu.

Mogą być trzy możliwości,

Przypadek 1 Kiedy twoja klasa rozszerza klasę, która używa generyków

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

Przypadek 2 Kiedy twoja klasa implementuje interfejs, który używa 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){}
}

Przypadek 3 Gdy twój interfejs jest rozszerzenie interfejsu używającego 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
Author: Anil Agrawal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-05 10:43:57

Właściwie, przypuszczam, że masz pole w swojej klasie typu T. Jeśli nie ma pola typu T, jaki jest sens posiadania typu ogólnego? Możesz więc po prostu zrobić instanceof na tym polu.

W moim przypadku mam

List<T> items;
w mojej klasie, a ja sprawdzam czy typ klasy to "local" by
if (items.get(0) instanceof Locality) ...

Oczywiście działa to tylko wtedy, gdy całkowita liczba możliwych klas jest ograniczona.

 0
Author: Christine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 11:51:32

Używam do tego obejścia:

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

MyClass myClassInstance = MyClass.class.newInstance();
 -4
Author: nikolai.serdiuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-27 08:45:27