Como obter uma instância de classe de genéricos Tipo T
Foo
, Eu quero obter a instância de classe do tipo T, mas eu simplesmente não posso ligar T.class
.
Qual é a maneira preferida de contornar isso usando T.class
?
17 answers
A resposta curta é, que não há maneira de descobrir o tipo de tempo de execução de Parâmetros de tipo genérico em Java. Sugiro ler o capítulo sobre o apagamento do tipo no Tutorial Java para mais detalhes.
Uma solução popular para isto é passar o Class
do parâmetro do tipo para o construtor do tipo genérico, por exemplo
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
}
}
A maioria dos genéricos usa casos que encontrei têm algum tipo de supertipo Genérico, por exemplo List<T>
Para ArrayList<T>
ou GenericDAO<T>
para DAO<T>
, etc.
Solução Java pura
O artigo a aceder aos tipos genéricos ao executar em Java explica como você pode fazer isso usando Java puro.
Solução de Primavera
O meu projecto estava a usarA Primavera o que é ainda melhor porque a primavera tem um método de utilidade útil para encontrar o tipo. Esta é a melhor abordagem para mim como ele parece melhor. Acho que se não estivesses a usar a Primavera podias escrever o teu próprio método de utilidade.
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<MyType> myFoo = new Foo<MyType>(){};
(Note o duplo aparelho no final.)
Agora você pode recuperar o tipo de T
em tempo de execução:
Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
Note no entanto que mySuperclass
tem de ser a superclasse da definição da classe que define realmente o tipo final para T
.
Também não é muito elegante, mas você tem que decidir se prefere {[[8]} ou No seu código.
Por exemplo:
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;
}
}
}
Depois:
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 );
}
}
Uma abordagem-padrão / solução é adicionar um objecto class
ao(S) construtor (s), como:
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();
}
}
Imagine que você tem uma superclasse abstrata que é genérica:
public abstract class Foo<? extends T> {}
E então você tem uma segunda classe que se estende Foo com uma barra genérica que se estende T:
public class Second extends Foo<Bar> {}
Você pode obter a classe Bar.class
na classe Foo seleccionando a Type
(a partir da resposta de bert bruynooghe) e inferindo-a usando Class
instância:
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);
Você tem que notar que esta operação não é ideal, então é uma boa idéia cache o valor calculado para evitar vários cálculos sobre isso. Um dos típicos os usos estão na implementação genérica DAO.
A execução final:
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;
}
}
O valor devolvido é a barra.classe quando invocada a partir da classe Foo noutra função ou da classe de barras.
Aqui está uma solução de trabalho:
@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 <> ");
}
}
Notas: só pode ser usado como superclasse
- tem de ser alargado com classe tipada(
Child extends Generic<Integer>
)
OU
- Tem de ser criado como implementação anónima.(
new Generic<Integer>() {};
)
abstract class Foo<T> {
abstract Class<T> getTClass();
//...
}
E mais tarde na classe derivada:
class Bar extends Foo<Whatever> {
@Override
Class<T> getTClass() {
return Whatever.class;
}
}
Uma rota melhor do que a classe que os outros sugeriram é passar por um objeto que pode fazer o que você teria feito com a classe, por exemplo, criar uma nova instância.
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);
}
Necessita de duas funções de svn/trunk/dao/src/main/java/com/googlecode/genericdao/dao/ DAOUtil.java .
Para mais explicações, ver genéricos reflectores.
Tenho uma solução (feia mas eficaz) para este problema, que usei recentemente:
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> {
}
Agora vamos criar alguns atributos para persistir os tipos:
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)
}
Então poderá criar um método genérico que devolve o tipo com base no índice da definição genérica:
/**
* 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];
}
Finalmente, no construtor basta ligar para o método e enviar o índice para tipo. O código completo deve ser parecido com:
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];
}
}
Como explicado em outras respostas, para usar esta abordagem {[[2]}, você precisa estender a classe, mas isso parece um trabalho extra para fazer toda uma nova classe que a estende...
Então, tornando a classe abstrata, obriga-nos a estendê-la, satisfazendo assim a exigência de subclassificação. (usando o @Getter de lombok).@Getter
public abstract class ConfigurationDefinition<T> {
private Class<T> type;
...
public ConfigurationDefinition(...) {
this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
...
}
}
Agora estendê-la sem definir uma nova classe. (Anote o {} no final... extended, but don't overwrite anything-unless you want to).
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
}
}
Se estiver a extender ou a implementar qualquer classe/interface que esteja a usar genéricos , poderá obter o tipo genérico de classe/interface pai, sem modificar nenhuma classe / interface existente.
Pode haver três possibilidades.Caso 1 Quando a sua classe está a estender uma classe que está a usar genéricos
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> {
}
Caso 2 Quando a sua classe está a implementar uma interface que está a usar genéricos
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){}
}
Caso 3 Quando interface está estendendo uma interface que está usando 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> {
}
No meu caso, tenho um
Na minha turma, e eu verifico se o tipo de classe é "localidade" porif (items.get(0) instanceof Locality) ...
Claro, isto só funciona se o número total de classes possíveis for limitado.
class MyClass extends Foo<T> {
....
}
MyClass myClassInstance = MyClass.class.newInstance();