Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I call .class on a generic type in Java?

Tags:

java

generics

I was wondering if there is a way in Java to do something like

Class c = List<String>.class;
Class c2 = List<Date>.class;

I need something like this to create a map that stores the class name (with generic type) and the corresponding object which I can later lookup. For example,

Map<Class, Object> dataMap = new HashMap<Class, Object>();
dataMap.put(c, listOfStrings);
dataMap.put(c2, listOfDates);

Is this not possible because of type erasure during runtime ?

like image 973
Rahul Avatar asked Apr 21 '12 08:04

Rahul


People also ask

Can a class be generic in Java?

A Generic class simply means that the items or functions in that class can be generalized with the parameter(example T) to specify that we can add any type as a parameter in place of T like Integer, Character, String, Double or any other user-defined type.

Can classes be generic?

A generic class can be a base class to other generic or non-generic classes or abstract classes. A generic class can be derived from other generic or non-generic interfaces, classes, or abstract classes.

Can you cast to a generic type Java?

The Java compiler won't let you cast a generic type across its type parameters because the target type, in general, is neither a subtype nor a supertype.


2 Answers

You can't to it quite like this, but you can achieve your overall aim using the same approach as Guice does with TypeLiteral. If you're using Guice already, I suggest you use that directly - otherwise, you might want to create your own similar class.

Essentially the idea is that subclasses of a generic type which specify type arguments directly retain that information. So you write something like:

TypeLiteral literal = new TypeLiteral<List<String>>() {};

Then you can use literal.getClass().getGenericSuperclass() and get the type arguments from that. TypeLiteral itself doesn't need to have any interesting code (I don't know whether it does have anything in Guice, for other reasons).

like image 150
Jon Skeet Avatar answered Sep 28 '22 07:09

Jon Skeet


No, it is not possible. You can't even refer to List<String>.class in your code - it results in a compilation error. There is only one single class object for List, and it is called List.class.

Is this not possible because of type erasure during runtime ?

Correct.

Btw this is a generic type, not an annotated type.

Update

On second thought, you can have something fairly close to your Map above, by tweaking Josh Bloch's typesafe heterogenous container (published in Effective Java 2nd Ed., Item 29) a bit:

public class Lists {
    private Map<Class<?>, List<?>> lists =
            new HashMap<Class<?>, List<?>>();

    public <T> void putList(Class<T> type, List<T> list) {
        if (type == null)
            throw new NullPointerException("Type is null");
        lists.put(type, list);
    }

    public <T> List<T> getList(Class<T> type) {
        return (List<T>)lists.get(type);
    }
}

The cast in getList is unchecked, giving a warning, but I am afraid we can't avoid that. However, we know that the value stored for class X must be a List<X>, as this is guaranteed by the compiler. So I think the cast is safe (if you play by the rules, that is - i.e. never call putList with a plain nongeneric Class parameter), thus it can be suppressed using @SuppressWarnings("unchecked").

And you can use it like this:

Lists lists = new Lists();
List<Integer> integerList = new ArrayList<Integer>();
List<String> stringList = new ArrayList<String>();
...

lists.putList(Integer.class, integerList);
lists.putList(String.class, stringList);
List<Integer> storedList = lists.getList(Integer.class);

assertTrue(storedList == integerList);
like image 41
Péter Török Avatar answered Sep 28 '22 08:09

Péter Török