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

701

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

Какой предпочтительный способ обойти это, используя T.class?

robinmag
источник
2
Попробуйте ответы на этот вопрос. Я думаю, что это похоже. stackoverflow.com/questions/1942644/…
Эмиль
3
возможный дубликат класса Get общего типа во время выполнения
7hi4g0
1
возможный дубликат создания обобщенного класса в Java
matiasg
1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Богдан Шульга

Ответы:

570

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

Популярное решение этого заключается в передаче 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
    }
}
Жолт Тёрёк
источник
73
Этот ответ дает правильное решение, но неточно сказать, что нет способа найти универсальный тип во время выполнения. Оказывается, стирание типа намного сложнее, чем стирание. Мой ответ показывает, как получить классы универсального типа.
Бен Терли
3
@BenThurley Аккуратный трюк, но, насколько я вижу, он работает, только если есть универсальный супертип для него. В моем примере вы не можете получить тип T в Foo <T>.
Жолт Тёрёк
@webjockey Нет, ты не должен. Назначение typeParameterClassбез назначения по умолчанию в конструкторе прекрасно. Нет необходимости устанавливать его во второй раз.
Adowrath
Это первое решение, которое приходит на ум, но порой не вы создаете / инициируете объекты. Так что вы не сможете использовать конструктор. Например, при извлечении объектов JPA из базы данных.
Парамвир Сингх Карвал
234

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

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

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

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

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

Весеннее решение

Мой проект использовал 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 ";
    }
Бен Терли
источник
1
просьба разъяснить смысл из resolveTypeArgument аргументов
gstackoverflow
getClass () - это метод java.lang.Object, который будет возвращать класс конкретного объекта во время выполнения, это объект, для которого вы хотите разрешить тип. AbstractHibernateDao.class - это просто имя базового класса или суперкласса иерархии классов универсальных типов. Оператор импорта включен, так что вы сможете легко найти документы и проверить себя. Это страница docs.spring.io/spring/docs/current/javadoc-api/org/…
Бен Терли
Что такое пружинное решение с версией 4.3.6 и выше. Не работает с пружиной 4.3.6.
Эрлан
1
Ссылка в «Чистом Java-решении» не работает, она теперь blog.xebia.com/acessing-generic-types-at-runtime-in-java
Ник Брин,
1
@ AlikElzin-kilaka инициализируется в конструкторе с использованием класса Spring GenericTypeResolver.
Бен Терли
104

Однако есть небольшая лазейка: если вы определяете свой 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 );
    }
}
Bert Bruynooghe
источник
5
Это легко лучший ответ здесь! Кроме того, что бы это ни стоило, это стратегия, которую Google Guice использует для связывания классовTypeLiteral
ATG
14
Обратите внимание, что каждый раз, когда используется этот метод построения объекта, создается новый анонимный класс. Другими словами, два объекта aи bсозданный таким образом , будет как продлить тот же класс , но не имеют одинаковые классы экземпляра. a.getClass() != b.getClass()
Мартин Серрано
3
Есть один сценарий, в котором это не работает. Если Foo должен реализовать интерфейс, такой как Serializable, то анонимный класс не будет Serializable, если не создан экземпляр класса. Я попытался обойти его, создав сериализуемый фабричный класс, который создает анонимный класс, производный от Foo, но затем, по какой-то причине, getActualTypeArguments возвращает универсальный тип вместо фактического класса. Например: (новый FooFactory <MyType> ()). CreateFoo ()
Лиор Чага
38

Стандартный подход / обходной путь / решение заключается в добавлении 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();
    }
 }
Андреас Долк
источник
1
Но, кажется, не может @autowired в реальном использовании, каким-либо образом обойти?
Альфред Хуанг
@AlfredHuang Обходной путь должен был бы создать bean-компонент для класса, который делает это, а не полагается на автопроводку.
Калебж
20

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

public abstract class Foo<? extends T> {}

И затем у вас есть второй класс, который расширяет Foo универсальным Bar, расширяющим T:

public class Second extends Foo<Bar> {}

Вы можете получить класс Bar.classв классе Foo, выбрав Type(из ответа Берта Бруйнооха) и выведя его, используя 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.

droidpl
источник
1
toString().split(" ")[1]это была проблема, избегайте"class "
IgniteCoders
16

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

@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>() {};)
Ярослав Ковбас
источник
3
getTypeName вызывает toString, поэтому может быть заменен на .getActualTypeArguments () [0] .toString ();
Ярослав Ковбас
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>());
Рики Кларксон
источник
@ Рикки Кларксон: Я не понимаю, как этот завод должен возвращать параметризованные foos. Не могли бы вы объяснить, как вы получаете Foo <T> из этого? Мне кажется, это дает только непараметризованный Фу. Разве T в make10 не просто Foo здесь?
ib84
@ ib84 Я исправил код; Кажется, я упустил, что Foo был параметризован, когда я первоначально написал ответ.
Рикки Кларксон,
9

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

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

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

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
user1154664
источник
Да, это так, но я хотел бы оставить очень минимум, который необходимо сделать при расширении этого класса. Проверьте ответ
droidpl
5

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

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 __()
    {
    }
}
unknown6656
источник
3

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

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];
    }
}
Хуан Уррего
источник
2

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

Таким образом, делая класс абстрактным, он заставляет вас расширять его, удовлетворяя, таким образом, требованиям подкласса. (используя @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();
Риан Шутте
источник
2

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

private T t;

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

В этом случае вы можете просто создать следующий метод:

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

Надеюсь, поможет!

Pontios
источник
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

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

Там может быть три возможности,

Случай 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> {
}
Анил Агравал
источник
1

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

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
источник
0

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

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

Список <T> элементов;
в моем классе, и я проверяю, является ли тип класса "Местность"

if (items.get (0) instanceof Locality) ...

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

Кристин
источник
4
Что мне делать, если items.isEmpty () имеет значение true?
chaotic3quilibrium
0

Этот вопрос старый, но сейчас лучше всего использовать 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>
    }
}
Ваге Гарибян
источник
0

Я хотел передать T.class методу, который использует Generics

Метод readFile читает файл .csv, указанный в fileName, с полным путем. Могут быть файлы csv с различным содержанием, поэтому мне нужно передать класс файла модели, чтобы я мог получить соответствующие объекты. Поскольку это чтение CSV-файла, я хотел сделать в общем. По тем или иным причинам ни одно из вышеперечисленных решений не помогло мне. Мне нужно использовать, Class<? extends T> typeчтобы это работало. Я использую библиотеку opencsv для анализа файлов 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;
}

Вот как вызывается метод readFile

List<RigSurfaceCSV> rigSurfaceCSVDataList = readSurfaceFile(surfaceFileName, RigSurfaceCSV.class);
Мадху Томи
источник
-4

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

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

MyClass myClassInstance = MyClass.class.newInstance();
nikolai.serdiuk
источник