Как мне получить экземпляр класса универсального типа T?
У меня есть класс generics, Foo<T>
. В методе Foo
я хочу получить экземпляр класса типа T
, но я просто не могу вызвать T.class
.
Какой предпочтительный способ обойти это с помощью T.class
?
Переведено автоматически
Ответ 1
Короткий ответ заключается в том, что в 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
}
}
Ответ 2
Я искал способ сделать это самостоятельно, не добавляя дополнительную зависимость к classpath. После некоторого исследования я обнаружил, что это возможно, если у вас есть универсальный супертип с неродовым подклассом. Для меня это было нормально, поскольку я работал со слоем DAO с универсальным супертипом layer. Если это соответствует вашему сценарию, то, ИМХО, это самый аккуратный подход.
Большинство универсальных вариантов использования, с которыми я сталкивался, имеют какой-то универсальный супертип, например, List<T>
for ArrayList<T>
или GenericDAO<T>
for DAO<T>
и т.д.
Чисто Java-решение
В статье Доступ к универсальным типам во время выполнения в Java объясняется, как вы можете это сделать, используя чистую Java.
@SuppressWarnings("unchecked")
public Class reflectClassType() {
return ((Class) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]);
}
У этого кода есть несколько ограничений. Он не будет работать во всех случаях, связанных с несколькими уровнями абстракции, или когда ваш тип представляет собой массив (например, int[]
). Для более полного решения, которое расширяет это для большего количества случаев, смотрите эту статью .
Еще раз обратите внимание, что этот метод работает только с подклассом, не являющимся универсальным / raw. Если вы попытаетесь сделать это с универсальным классом, аргументы типа останутся неразрешенными, останутся универсальные TypeVariableImpl
объекты, классом которых является null
. Например:
class Generic<T>{
@SuppressWarnings("unchecked")
public Class reflectClassType() {
return ((Class) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
class ChildGeneric<T> extends Generic<T>{}
class ChildString extends Generic<String>{}
// Error! Won't work, since the subclass is generic
new ChildGeneric<String>().reflectClassType();
// Works; we create an anonymous, trivial, non-generic subclass
new ChildGeneric<String>(){}.reflectClsasType();
// Works; we create an explicit non-generic subclass
new ChildString().reflectClassType(); // okay
Spring solution
Spring provides a utility class GenericTypeResolver, which implements a more robust version of this technique. It comes with its own subtle limitations. In general, it will only work if the called for a non-generic class.
My project was using Spring so is the best approach for me as it looks neatest. I guess if you weren't using Spring you could write your own utility method.
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 ";
}
Full code example
Some people are struggling in the comments to get this working so I wrote a small application to show both approaches in action.
https://github.com/benthurley82/generic-type-resolver-test
Note that in this code, the non-generic Foo
subclass has a generic superclass AbstractFoo
:
public class Foo extends AbstractFoo<GenericType>
which is what allows this method to work.
Ответ 3
There is a small loophole however: if you define your Foo
class as abstract.
That would mean you have to instantiate you class as:
Foo<MyType> myFoo = new Foo<MyType>(){};
(Note the double braces at the end.)
Now you can retrieve the type of T
at runtime:
Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
Note however that mySuperclass
has to be the superclass of the class definition actually defining the final type for T
.
It is also not very elegant, but you have to decide whether you prefer new Foo<MyType>(){}
or new Foo<MyType>(MyType.class);
in your code.
For example:
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();
}
}