Как получить экземпляр класса обобщенного типа T?
У меня есть обобщённый класс <code>Foo<T></code>
. В методе этого класса я хочу получить экземпляр типа <code>T</code>
, но не могу использовать <code>T.class</code>
.
Каков предпочтительный способ обойти эту проблему и получить класс типа <code>T</code>
?
5 ответ(ов)
Существует небольшая уловка: если вы определите свой класс 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;
/**
* Захватывает и тихо игнорирует исключения стека при извлечении элемента.
*/
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 {
// Обратите внимание на фигурные скобки...
private Deque<String> stack = new SilentStack<String>(){};
public static void main( String args[] ) {
// Возвращает новый экземпляр 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();
}
}
Таким образом, при создании экземпляра класса Foo
, вы можете передать класс типа T
, что позволяет вам использовать его для создания новых экземпляров этого типа внутри метода newInstance()
.
Ваш вопрос касается реализации обобщённого абстрактного класса в Java и получения класса параметризованного типа. Давайте разберёмся подробнее, как это работает.
У вас есть абстрактный суперкласс Foo
, который принимает параметр типа T
:
public abstract class Foo<T> {}
Затем вы создаёте класс Second
, который расширяет Foo
, передавая Bar
как тип:
public class Second extends Foo<Bar> {}
Если вам нужно получить класс Bar.class
внутри класса Foo
, вы можете использовать обобщение и рефлексию для этого. Как вы указали, сначала мы получаем тип суперкласса и извлекаем фактические аргументы типа. Пример кода для выполнения этой задачи выглядит следующим образом:
Type mySuperclass = getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);
Тем не менее, важно учитывать, что такая операция может быть не оптимальна, особенно если вам нужно будет выполнять её многократно. Поэтому хорошей практикой будет кэшировать результат для избежания повторных вычислений. Это часто встречается, например, в реализации обобщённых DAO (Data Access Object).
Финальная реализация с кэшированием может выглядеть так:
public abstract class Foo<T> {
private Class<T> inferredClass;
public Class<T> getGenericClass(){
if(inferredClass == null){
Type mySuperclass = getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
String className = tType.toString().split(" ")[1];
inferredClass = Class.forName(className);
}
return inferredClass;
}
}
При вызове метода getGenericClass()
из класса Foo
или Bar
, он будет возвращать Bar.class
.
Таким образом, вы получите нужный класс, избегая при этом лишних затрат на вычисления в случае многократных вызовов. Надеюсь, это поможет вам понять, как правильно управлять обобщёнными типами в Java!
Вот рабочее решение:
@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("Класс не параметризован обобщенным типом!!! Пожалуйста, используйте extends <>");
}
}
ПРИМЕЧАНИЯ: Можно использовать только в качестве суперкласса
- Нужно расширять с типизированным классом (
Child extends Generic<Integer>
)
ИЛИ
- Нужно создать как анонимную имплементацию (
new Generic<Integer>() {};
)
У меня возникла проблема с абстрактным обобщённым классом. В данном конкретном случае решение оказалось проще:
abstract class Foo<T> {
abstract Class<T> getTClass();
//...
}
А затем в производном классе:
class Bar extends Foo<Whatever> {
@Override
Class<Whatever> getTClass() {
return Whatever.class;
}
}
Таким образом, при переопределении метода getTClass
в классе Bar
, необходимо указать правильный обобщённый тип, соответствующий классу Whatever
.
Разница между <? super T> и <? extends T> в Java
Как создать обобщённый массив в Java?
Является ли List<Собака> подклассом List<Животное>? Почему дженерики в Java не являются неявно полиморфными?
Что такое PECS (Producer Extends Consumer Super)?
Что значит 'synchronized'?