Infer class type from a generic

Asked

Viewed 1,851 times

21

In the following class I want to assign the attribute clazz class type inferring it from the list given 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 proved to be wrong, because the value returned by the method was java.util.ArrayList.

How to infer the type of a bean informed in an Arraylist? It is possible to make this type of inference from the parameter, as in the example? If yes, how it would be? If not, which possibilities exist, or how to adjust this class so as to assign the correct value to the attribute clazz?

  • This question is exactly the same as on the Soen: http://stackoverflow.com/q/3403909/2766598

  • 1

    Hello, @Delfino. I took a look at the link. It seems to be the same thing, but it is not. Soen’s question is more comprehensive. Mine is more specific. I want to recover the type (T) from a List<T>.

  • 1

    Yes, indeed, the code structure is so similar that at first glance it looks the same. I even tried to find a solution for you, but it really doesn’t seem possible, I tried several ways, and as far as I went there is no way.

  • Your code makes no sense. T is a class type Parameter Foo and clazz is declared to be of this type type Parameter. Then you try to set to this variable a list type type Parameter, but the variable is not of the type list... It is difficult to understand its objective. Anyway, the Java Generic does not offer resource for you to resgar the type type Parameter at runtime.

  • @Caffé, perhaps you are correct, however the purpose of the code was to convey my need. I need to somehow receive a list of type Parameter and assign value to my clazz attribute. For suspecting that this is not possible I gave the dirty to adapt the code of the question.

  • The problem with your code not making sense is that you can’t understand exactly what you need; the object of the code has not been reached, its need has not been conveyed. Anyway, as demonstrated in my reply and explained the reason, infer the type of T using Generics features is not possible. If you want the type, you need to enter it in another parameter. For example: Foo(List<T> list, Class<T> type) { ... }.

  • @Caffé, the example you provided here in the commentary is precisely the type of change I suggested in case of impossibility of a solution using the code as it is. It would be interesting to complement this in his reply.

Show 2 more comments

4 answers

14


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

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

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

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

Done this you can use the strategy mentioned in Soen’s reply to infer the generic types of the superclass:

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

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

How to instantiate the class directly will throw an error in Runtime is worth making Foo 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 ArrayList???).


Of course this strategy has limitations... The main one is that <T> has to be a real kind. If you build Foo other generic type the Casts will fail.

Functional version in Ideone

  • I thought your answer was very creative. However, I find it difficult to use this type of writing: Foo<Integer> x = new Foo<Integer>(new Arraylist<Integer>()){}. It’s a bit strange.

  • 1

    It’s really weird, but you find examples out there; the most famous one I can think of is GenericType of JAX-RS.

9

Apparently it is not possible to catch the type of the list at runtime, what to do is to check if she has any objeto inserted and catch the type of it. 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, example:

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

And to call:

new Foo(String.class, list);
  • Maicon, your answer is good, but it contains a gap: if the list is empty it will not be possible to infer and assign Type to the attribute clazz.

  • Yes, so I warned in the reply that apparently it’s not possible, but if she has any elements it becomes possible.

  • Maicon made an adjustment on the question. You can adjust your answer so as to contemplate the changes.

  • @Geisonsantos I changed the answer, see if that’s what you’re looking for

7

Java does not maintain the type of Generic after the compilation (he makes such a "type Erasure"). In other words, the type of the parameter Generic (or "type Parameter") cannot be rescued at runtime.

There are some tricks that don’t work in all situations. So:

The usual way to read the type of a Generic at run time is not read the type of Generic at runtime, but to inform 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 code above, my intention is to use Generic not only to keep the typed Factory, with proper compile time checks, but also to take advantage of the informed type as type Parameter Factory to create an instance of this type. This doesn’t work because the guy from type Parameter T 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 little more code and some redundancy, I have my Factory well typed and with the ability to create instances not of the type of type Parameter, but rather the kind I reported on the Factory builder.

5

  • I believe it has already been well covered by all the answers the limitations of the type Erasure which is implemented by javac, i.e., type Generico has no way to be recovered directly.
  • My intention then will be to show an alternative implementation to get these values in a very objective way:

The comparison in question will always be true because in time compilation 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 Source from the subclass Mystringsubclass seeking the information that is kept in the compiled bytecode <1.5 or more in the other will be ignored> .

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

First we would meet the superclass and later, we find the type Enerico obtaining the argument of the superclass.

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

Remembering that this is a simplified demonstration, of a stylish hack to get the Generic type. However at API level the implementation would be more complex delivering the necessary coverage to several problematic scenarios.

The final recommendation is to use a great API for the Reflection of generic types: Generic type Reflection library for Java making you save a good time.

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

Browser other questions tagged

You are not signed in. Login or sign up in order to post.