12

I have a POJO specified as: MyClass<U>, where U is the generic type parameter. I am trying to write a utility method which accepts a class reference Class<T> and populates a map of type Map<String, T> (accepts the map to populate).

This method is implemented like:

static void populateMap(Map<String, T> map, Class<T> type) {

    ...

    // Parses into the specified type and returns an object of that type.
    T obj = parse(..., type);
    map.put (key, obj);
    ...

    return map;
}

This compiles fine. In my caller, I attempt to populate a map with any MyClass instance (irrespective of type) as the value. Hence I use the following code:

// Loses type information
Map<String, MyClass<?>> m = new HashMap<>();
populateMap(m, MyClass.class);

This does not compile. Compilation error:

The method populate(Map<String,T>, Class<T>) in the type ... is not applicable for the arguments (Map<String,MyClass<?>>, Class<MyClass>)

How can I fix this?

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Neel
  • 2,100
  • 5
  • 24
  • 47
  • Don't you think that while declaring the map you need the actual types rather generics wildcards? – Juned Ahsan Sep 22 '13 at 04:40
  • You should be using T everywhere for that type. Can you paste complete class? – Lokesh Sep 22 '13 at 04:43
  • @JunedAhsan No, I should be able to create a map containing any type of MyClass instances. This is why I have specified the wildcard. – Neel Sep 22 '13 at 04:45
  • @Lokesh Which class do you want to look at? The class containing populate Map is a simple util class. The method accepts the map as a composition. – Neel Sep 22 '13 at 04:48

2 Answers2

6

In this case it should be safe to do an unchecked cast to Class<MyClass<?>>:

// This is okay because we're switching to a type with an unbounded wildcard -
// the behaviors of Class.newInstance and Class.cast are still safe.
@SuppressWarnings("unchecked") 
Class<MyClass<?>> classWithNarrowedType =
        (Class<MyClass<?>>)(Class<?>)MyClass.class;
populateMap(m, classWithNarrowedType);

This is a crufty solution, especially if you have many call sites like this, but there's no getting around the fact that class literals are parameterized with raw types, making their use as factories of parameterized types like MyClass<T> inherently awkward.

A potentially cleaner solution would decouple populateMap from the use of class literals:

interface Parser<T> {

    T parse();
}

static void populateMap(Map<String, T> map, Parser<T> parser) { ... }

...

Map<String, MyClass<?>> m = new HashMap<>();
Parser<MyClass<?>> myClassParser = new Parser<MyClass<?>>() {
    @Override
    public MyClass<?> parse() {
        return parse(..., MyClass.class);
    }
};
populateMap(m, myClassParser);

As an aside I recommend a more flexible signature (see What is PECS (Producer Extends Consumer Super)? for more info):

static void populateMap(Map<String, ? super T> map, Parser<T> parser)
Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
2

Because of type erasure, there's no such thing as a Class object representing a generic type, you can only use a raw type such as MyClass (with no generic parameter).

One possible workaround is exceptionally ugly: declare or cast m as Map<String, MyClass> and prepare to face a tsunami of warnings and errors (errors can be fixed by casting, and become multiple warnings).
For a probably better workaround, refer to Paul's answer :)

Also see Getting a java.lang.Class with generics

Community
  • 1
  • 1
  • "you can only use a raw type such as MyClass" Why not an unbounded-wildcard parameterized type like `MyClass>`? Which is by the way a reified type. – newacct Sep 23 '13 at 06:30
  • @newacct I don't think you can call `MyClass>` a reified type; even if generics were reified, this has a wildcard. As for why you can only get the class of a raw type and not of a generic type with wildcard (which could have the same result)... that's how they designed the language. But as Paul Bellora showed, there is a trick to cast the class to the desired type. – aditsu quit because SE is EVIL Sep 23 '13 at 13:45
  • I meant a "reifiable type" http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.7 "... * It is a parameterized type in which all type arguments are unbounded wildcards". But yeah, I think it's just how they designed it. – newacct Sep 23 '13 at 20:47