Infer class type from a generic

21

In the following class I want to assign the clazz attribute to the class type by inferring it from the list entered in the constructor.

class Foo<T> {
    private Class<T> clazz;

    Foo(List<T> list) {
        //this.clazz = tipo da classe inferido a partir da lista
    }

    ...
}

My first attempt was to use the method list.getClass() which was wrong because the value returned by the method was java.util.ArrayList .

How to infer the type of an informed bean in an ArrayList? Is it possible to make this type of inference from the parameter, as in the example? If so, what would it be like? If not, what are the possibilities, ie how to adjust this class so as to assign the correct value to the attribute clazz ?

    
asked by anonymous 09.06.2015 / 18:01

4 answers

14

One possible trick is to swap an instance of your class:

Foo<Integer> x = new Foo<Integer>(new ArrayList<Integer>());

By a subclass of it (in this case an anonymous class):

Foo<Integer> x = new Foo<Integer>(new ArrayList<Integer>()){}; 
//                                             Repare no {} ^

Once you've done this you can use the strategy in SOen to infer generic superclass types:

this.clazz = (Class<T>) ((ParameterizedType) getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0];

In your case, by definition, the type of the superclass will be the type of the list.

How to instantiate the class directly will throw an error in runtime worth rendering abstract%:

abstract class Foo<T> {
    private final Class<T> clazz;

    public Foo(List<T> list) {
        this.clazz = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
    }
}

Alternatively we can use the same strategy with the list:

this.clazz = (Class<T>) ((ParameterizedType) list.getClass()
        .getGenericSuperclass()).getActualTypeArguments()[0];
// ... 
List<Integer> t = new ArrayList<Integer>(){};
FooII<Integer> x = new FooII<>(t);

But this seems even more confusing (anonymous subclasses of Foo ???).

Of course this strategy has limitations ... The main one is that ArrayList has to be a real type. If you build <T> passing another generic type the casts will fail.

Ideone functional version

    
16.06.2015 / 17:15
9

Apparently it is not possible to get type of the list at runtime, what you should do is check if it has some objeto inserted and get its type. Example:

private Class clazz;

Foo(List<T> list) {
    this.clazz = (list != null && !list.isEmpty()) ? list.get(0).getClass() : null;
}

Example 1

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("1");
    Foo f = new Foo(list); // lista populada
    System.out.println(f.getClazz());
}
  

class java.lang.String

-

Example 2

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    Foo f = new Foo(list); // lista vazia
    System.out.println(f.getClazz());
}
  

null



UPDATE

Another solution is to pass an extra parameter in the constructor, for example:

Foo(Class<T> typeClass, List<T> list) {
    this.clazz = typeClass;
    // ...
}

And to call:

new Foo(String.class, list);
    
09.06.2015 / 18:41
7
  

Java does not maintain the generic type after compilation (it does the same as "type erasure" ). In other words, the generic parameter type (or "type parameter" ) can not be redeemed at runtime.

There are some tricks that do not work in all situations. Then:

  

The usual way to read the type of a generic at runtime is not to read generic type at runtime but to report this type in a parameter.

For example, it would be nice if this code worked:

class AbstractFactory<T> {
    public T Create() {
        return T.newInstance(); // isso não funciona!!
    }
}
class CarroFactory extends AbstractFactory<Carro> {
}
...
CarroFactory carroFactory = new CarroFactory();
Carro carro = carroFactory.Create();

In the above code, my intention is to use generic not just to keep the factory typed, with due compile-time checks, / em> of the factory to create an instance of this type. This does not work because the type parameter T type will be lost after compilation.

So I have to do this:

class AbstractFactory<T> {
    private Class<T> type;
    public AbstractFactory(Class<T> type) {
        this.type = type; // no constutor eu peço o tipo do type parameter
    }
    public T Create() {
        // com o tipo que eu pedi no construtor eu posso criar uma instância
        return type.newInstance();
    }
}
class CarroFactory extends AbstractFactory<Carro> {
    // para poupar o consumidor desta tarefa,
    // eu informo aqui o tipo do type parameter
    public CarroFactory() {
        super(Carro.class);
    }
}
...
CarroFactory carroFactory = new CarroFactory();
Carro carro = carroFactory.Create();

Now, with a bit more code and some redundancy, I have my well-typed factory with the ability to create instances not of type type parameter , but of the type I reported in builder of the factory.

    
16.06.2015 / 16:29
5
  • I think the limitations of type erasure have been well covered by all the answers. which is implemented by javac, ie the generic type can not be retrieved directly.
  • My intention is then to show an alternative implementation to get these values quite objectively:
  

The comparison in question will always be true since in   compiling both generic types are erased by the compiler, however String.class! = Integer.class

assert new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass();
  

Now looking at this structure, it would be possible to get the type   from the subclass MyStringSubClass looking for the   information that is kept in the compiled bytecode < 1.5 or higher, in the other   will be ignored > .

class MyGenericClass<T> { }
class MyStringSubClass extends MyGenericClass<String> { }
  

First we would find the superclass and later,    We found the generic type getting the superclass argument .

ParameterizedType parameterizedType = (ParameterizedType) subClass.getGenericSuperclass();
Class<?> genericType = parameterizedType.getActualTypeArguments()[0];
assert genericType == String.class;

Remembering that this is a simplified demonstration of an elegant hack to get the generic type. But at the API level the implementation would be more complex delivering the necessary coverage to various problem scenarios.

The final recommendation is to use an optimal reflection API for generic types: Generic type reflection library for Java saving you a lot of time.

No more more a link with a high-level coverage of the subject by Rafael Winterhalter.

    
17.06.2015 / 02:23