Як отримати екземпляр класу загального типу T?


700

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

Який найкращий спосіб обійти його за допомогою T.class?


2
Спробуйте відповіді на це запитання. Я думаю, що це схоже. stackoverflow.com/questions/1942644/…
Еміль


1
можливий дублікат Instantiating родового класу на Java
matiasg

1
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Богдан Шульга

Відповіді:


569

Коротка відповідь полягає в тому, що немає можливості дізнатися тип виконання параметрів загального типу в 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>.
Zsolt Török

@webjockey Ні, не слід. Призначення typeParameterClassбез конструктивного призначення в конструкторі бездоганно. Не потрібно встановлювати це вдруге.
Adowrath

Це перше рішення, яке спадає на думку, але іноді не ви будете створювати / ініціювати об’єкти. Таким чином, ви не зможете використовувати конструктор. Наприклад, під час отримання об'єктів JPA з бази даних.
Paramvir Singh Karwal

234

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

У більшості випадків використання дженериків, з якими я стикався, є якийсь загальний супертип, наприклад, 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 він ініціалізується в конструкторі за допомогою весняного класу GenericTypeResolver.
Бен Терлі

103

Однак є невелика лазівка: якщо ви визначите свій 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 );
    }
}

5
Це легко найкраща відповідь тут! Крім того, для чого це варто, це стратегія, яку Google Guice використовує для прив’язки класівTypeLiteral
ATG

14
Зауважте, що кожного разу, коли використовується цей метод побудови об'єкта, створюється новий анонімний клас. Іншими словами, два об'єкти aі bстворений таким чином , буде як продовжити той же клас , але не мають однакові класи примірника. a.getClass() != b.getClass()
Мартін Серрано

3
Є один сценарій, коли це не працює. Якщо Foo повинен реалізувати інтерфейс, такий як Serializable, то анонімний клас не був би Serializable, якщо клас інстанціювання не є. Я спробував її обійти, створивши серіалізаційний заводський клас, який створює анонімний клас, похідний від Foo, але потім, чомусь, getActualTypeArguments повертає загальний тип замість фактичного класу. Наприклад: (новий FooFactory <MyType> ()). CreateFoo ()
Lior Chaga

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 Завданням було б створити боб для класу, який це робить, а не покладатися на автопровід.
Калеб

20

Уявіть, у вас є загальний абстрактний суперклас:

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.


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 викликаєString, тому може бути замінений .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>());

@ Ricky Clarkson: Я не бачу, як ця фабрика повинна повертати параметризовані foos. Чи можете ви поясніть, як ви можете вийти з цього Foo <T>? Мені здається, це дає лише непаметризований Фоо. Чи не Т в make10 просто Foo тут?
ib84

@ ib84 Я виправив код; Здається, я пропустив, що Фоо був параметризований, коли я написав відповідь спочатку.
Рікі Кларксон

9

У мене була ця проблема в абстрактному родовому класі. У цьому конкретному випадку рішення простіше:

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

і пізніше похідного класу:

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

Так, але я хотів би залишити дуже мінімум, що потрібно зробити під час розширення цього класу. Перевір відповідь
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 __()
    {
    }
}


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

Сподіваюся, це допомагає!


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

0

Насправді, я думаю, у вас є поле у ​​вашому класі типу T. Якщо немає поля типу T, який сенс мати загальний тип? Отже, ви можете просто зробити instanceof у цьому полі.

У моєму випадку у мене є

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

якщо (items.get (0) instanceof Locality) ...

Звичайно, це працює лише в тому випадку, якщо загальна кількість можливих класів обмежена.


4
Що робити, якщо items.isEmpty () є істинним?
хаотичний3рівноваги

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

Я хотів передати Т.клас методу, який використовує Generics

Метод readFile читає .csv файл, визначений fileName з fullpath. Можуть бути файли 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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.