Как получить экземпляр класса generics типа T

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

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

17 ответов


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

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

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

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

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

чистое решение на Java

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

весной решением

мой проект был с помощью Весна что еще лучше, поскольку 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 ";
    }

однако есть небольшая лазейка: если вы определяете свой 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 );
    }
}

стандартный подход/решение/решение-добавить 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();
    }
 }

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

public abstract class Foo<? extends T> {}

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

public class Second extends Foo<Bar> {}

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


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

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

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

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

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

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

вы не можете этого сделать из-за стирания типа. См. также вопрос переполнения стека Java generics-тип стирания-когда и что происходит.


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

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

возможно:

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

вам нужны две функции из svn / trunk/dao/src/main/java/com/googlecode/genericdao/ dao / DAOUtil.java.

дополнительные пояснения см. В разделе отражающие дженериков.


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

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

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

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

как объясняется в других ответах, использовать это 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();

   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 Когда ваш класс расширяет класс, который использует 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 Когда ваш класс реализует интерфейс, который использует Дженерики!--4-->

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

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

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

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

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


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

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

MyClass myClassInstance = MyClass.class.newInstance();