Получение типа дженерика для создания объекта
Есть простой код:
package com.lampa;
import java.lang.reflect.ParameterizedType;
public class SuperApp {
private static abstract class People {
public People(String name) {
System.out.println(name);
}
}
private static class Man extends People {
public Man(String name) {
super(name);
}
}
private static class Factory<T extends People> {
@SuppressWarnings("unchecked")
public T create(String name) throws Exception {
ParameterizedType genericSuperclass = (ParameterizedType) this.getClass().getGenericSuperclass();
return (T) genericSuperclass.getActualTypeArguments()[0].getClass().getConstructor(String.class).newInstance(name);
}
}
public static void main(String[] args) throws Exception {
Factory<Man> factory = new Factory<>();
Man man = factory.create("Lampa");
}
}
Суть следующая: есть некий абстрактный класс People, от которого должны наследоваться реализации, типа класса Man. И есть фабрика, которая создает эти реализации Factory<T extends People>.
Почему я не могу получить тип дженерика вот тут (ParameterizedType) this.getClass().getGenericSuperclass() (получаю ошибку java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType)? И возможно ли поменять код так, чтобы лаконично осталось:
Factory<Man> factory = new Factory<>();
Man man = factory.create("Lampa");
Ответы (1 шт):
Почему я не могу получить тип дженерика вот тут (ParameterizedType) this.getClass().getGenericSuperclass() (получаю ошибку java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType)?
Это происходит потому, что у Factory родильский класс Object. Он не generic поэтому Object.class не реализует java.lang.reflect.ParameterizedType и соответственно Object.class нельзя к нему привести.
То что вы хотите напрямую сделать нельзя из-за того, как работают generic-и.
В runtime происходит процесс erasure или стирания. То есть информация о типе аргумента в Factory<Man> никак не представлена. Тип factory в runtime это всегда просто Factory и никакой информации о параметре T мы из объекта factory получить не можем в принципе.
Точно так же в самом классе Factory в runtime нет вообще никакой информации о том, как он был инстанциирован с конкретным типом аргумента.
Нужно или явно передавать класс, например:
private static class Factory<T extends People> {
private Class<T> clazz;
public Factory(Class<T> clazz) {
this.clazz = clazz;
}
public T create(String name) throws Exception {
return clazz.getConstructor(String.class).newInstance(name);
}
}
Либо явно определять подкласс:
private static class Factory<T extends People> {
@SuppressWarnings("unchecked")
public T create(String name) throws Exception {
ParameterizedType genericSuperclass =
(ParameterizedType) this.getClass().getGenericSuperclass();
Class<?> genericArgumentClass = (Class<?>)genericSuperclass.getActualTypeArguments()[0];
return (T) genericArgumentClass.getConstructor(String.class).newInstance(name);
}
}
private static class ManFactory extends Factory<Man> {}
Тогда можно сделать так и будет работать:
Factory<Man> factory = new ManFactory();
Man man = factory.create("Lampa");
Тут работает, потому что в runtime сохраняется информация о типе класса ManFactory включая то, что он унаследован от generic-а и какие фактические аргументы были использованы. Именно эта информация достается функцией getGenericSuperclass (и похожей getGenericInterfaces).