Как получить экземпляр класса дженериков типа T

У меня есть класс generics, Foo<T>. В методе Foo я хочу получить экземпляр класса типа T, но я просто не могу назвать T.class.

Каков предпочтительный способ обойти его с помощью T.class?

Ответ 1

Короткий ответ заключается в том, что в Java нет способа узнать тип общих параметров типа в среде исполнения. Я предлагаю прочитать главу о стирании стилей в Java Tutorial для более подробной информации.

Популярным решением для этого является передача Class параметра типа в конструктор родового типа, например.

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

Ответ 2

Я искал способ сделать это сам, не добавляя дополнительной зависимости от пути к классам. После некоторого расследования я обнаружил, что он возможен, если у вас есть общий супертип. Это было нормально для меня, так как я работал с DAO с универсальным уровнем супертипа. Если это соответствует вашему сценарию, то это самый опрятный подход IMHO.

Большинство дженериков используют случаи, с которыми я сталкивался, имеют какой-то общий супертип, например. List<T> для ArrayList<T> или GenericDAO<T> для DAO<T> и т.д.

Чистое решение Java

Статья Доступ к родовым типам во время выполнения в Java объясняет, как вы можете это сделать, используя чистую Java.

Spring решение

В моем проекте использовалась Spring, что даже лучше, так как Spring имеет удобный метод утилиты для поиска типа. Это лучший подход для меня, поскольку он выглядит аккуратно. Я думаю, если бы вы не использовали Spring, вы могли бы написать свой собственный метод утилиты.

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

Ответ 3

Однако существует небольшая лазейка: если вы определяете свой класс Foo как абстрактный. Это означало бы, что вы должны создать экземпляр класса:

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

(Обратите внимание на двойные фигурные скобки в конце.)

Теперь вы можете получить тип T во время выполнения:

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

Обратите внимание, что mySuperclass должен быть суперклассом определения класса, фактически определяющим конечный тип для T.

Это также не очень элегантно, но вы должны решить, предпочитаете ли вы new Foo<MyType>(){} или new Foo<MyType>(MyType.class); в своем коде.


Например:

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

Тогда:

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

Ответ 4

Стандартный подход/обходное решение/решение заключается в добавлении объекта class к конструктору (конструкторам), например:

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

Ответ 5

Представьте, что у вас есть абстрактный суперкласс, который является общим:

public abstract class Foo<? extends T> {}

И тогда у вас есть второй класс, который расширяет Foo с помощью общего бара, который расширяет T:

public class Second extends Foo<Bar> {}

Вы можете получить класс Bar.class в классе Foo, выбрав Type (из ответа bert bruynooghe) и вызывая его с помощью экземпляра 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);

Вы должны отметить, что эта операция не идеальна, поэтому рекомендуется кэшировать вычисленное значение, чтобы избежать множественных вычислений. Одно из типичных применений - это общая реализация DAO.

Окончательная реализация:

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

Возвращаемое значение - Bar.class при вызове из класса Foo в другой функции или из класса Bar.

Ответ 6

Вот рабочее решение:

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

ПРИМЕЧАНИЯ: Может использоваться только как суперкласс

  1. Должен быть расширен типизированным классом (Child extends Generic<Integer>)

ИЛИ

  1. Должен быть создан как анонимная реализация (new Generic<Integer>() {};)

Ответ 8

У меня была эта проблема в абстрактном родовом классе. В этом конкретном случае решение проще:

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

а затем производный класс:

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

Ответ 9

Лучший маршрут, чем класс, который предлагается другим, - это передать объект, который может сделать то, что вы сделали бы с классом, например, создать новый экземпляр.

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

Ответ 10

У меня есть (уродливое, но эффективное) решение для этой проблемы, которое я использовал недавно:

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

Ответ 12

Я нашел общий и простой способ сделать это. В моем классе я создал метод, который возвращает универсальный тип в соответствии с его положением в определении класса. Давайте предположим, что определение класса выглядит следующим образом:

public class MyClass<A, B, C> {

}

Теперь давайте создадим некоторые атрибуты для сохранения типов:

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)

    } 

Затем вы можете создать универсальный метод, который возвращает тип на основе индекса универсального определения:

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

Наконец, в конструкторе просто вызовите метод и отправьте индекс для каждого типа. Полный код должен выглядеть следующим образом:

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

Ответ 13

Как объяснялось в других ответах, для использования этого подхода ParameterizedType вам нужно расширить класс, но это похоже на дополнительную работу, чтобы сделать весь новый класс, который расширяет его...

Итак, делая абстрактное класс, он заставляет вас расширять его, тем самым удовлетворяя требование подкласса. (используя lombok @Getter).

@Getter
public abstract class ConfigurationDefinition<T> {

    private Class<T> type;
    ...

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

Теперь, чтобы расширить его, не определяя новый класс. (Обратите внимание, что {} на конце... расширен, но ничего не перезаписывайте - если вы этого не хотите).

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

Ответ 14

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

Ответ 15

Если вы расширяете или реализуете какой-либо класс/интерфейс, который использует общие элементы, вы можете получить общий тип родительского класса/интерфейса без изменения любого существующего класса/интерфейса вообще.

Возможны три возможности:

Случай 1 Когда ваш класс расширяет класс, который использует 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> {
}

Случай 2 Когда ваш класс реализует интерфейс, который использует 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){}
}

Случай 3 Когда ваш интерфейс расширяет интерфейс, который использует 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> {
}

Ответ 16

Это довольно просто. Если вам нужно из одного класса:

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

Ответ 17

На самом деле, я полагаю, у вас есть поле в вашем классе типа T. Если нет поля типа T, то какая точка имеет общий тип? Таким образом, вы можете просто сделать instanceof в этом поле.

В моем случае у меня есть

List<T> items;
в моем классе, и я проверяю, является ли тип класса "Locality" на
if (items.get(0) instanceof Locality) ...

Конечно, это работает только в том случае, если общее число возможных классов ограничено.

Ответ 18

Этот вопрос старый, но сейчас лучше всего использовать Google Gson.

Пример для кастома viewModel.

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

Класс общего типа

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

Ответ 19

Я использую обходное решение для этого:

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

MyClass myClassInstance = MyClass.class.newInstance();