tags:

views:

133

answers:

3

Possible Duplicate:
Get generic type of java.util.List

I have a Map and I want to get the type of T from an instance of that Map. How can I do that?

e.g. I want to do something like:

Map<String, Double> map = new HashMap<String, Double>();
...
String vtype = map.getValueType().getClass().getName(); //I want to get Double here

Of course there's no such 'getValueType()' function in the API.

+6  A: 

You can't get it from the instance, because in Java generics the type parameter is available only at compile time, not at run time.

This is known as type erasure. A more formal definition is provided in the JLS.

Andy Thomas-Cramer
This is not accurate; there are answers giving the exact code needed to do it, a couple different ways
Michael Mrozek
Actually this is 100% correct for this particular case: if all you have is said instance, there is no type information to find, nothing at all. Map in question is of type HashMap, since type parameters here are discarded after compilation (they induce necessary type casts in byte code).
StaxMan
+2  A: 

If your Map is a declared field, you can do the following:

import java.lang.reflect.*;
import java.util.*;

public class Generic {
    private Map<String, Number> map = new HashMap<String, Number>();

    public static void main(String[] args) {
        try {
            ParameterizedType pt = (ParameterizedType)Generic.class.getDeclaredField("map").getGenericType();
            for(Type type : pt.getActualTypeArguments()) {
                System.out.println(type.toString());
            }
        } catch(NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

The above code will print:

class java.lang.String
class java.lang.Number

However, if you simply have an instance of the Map, e.g. if it were passed to a method, you cannot get the information at that time:

public static void getType(Map<String, Number> erased) {
    // cannot get the generic type information directly off the "erased" parameter here
}

You could grab the method signature (again, using reflection like my example above) to determine the type of erased, but it doesn't really get you anywhere (see edit below).

Edit:

BalusC's comments below should be noted. You really shouldn't need to do this; you already declared the Map's type, so you're not getting any more information than you already have. See his answer here.

Rob Hruska
Reflection can be used at runtime on values referenced in a map.
Andy Thomas-Cramer
Noted should be that this only returns the **declared** generic types (as in `Map<String, Integer> map` part), not the **instantiated** generic types (as in `new HashMap<String, Integer>()` part). As pointed in my answer in the linked duplicate; there's no point of figuring them that way if you've already declared them yourself in the code...
BalusC
@BalusC - Agreed; your comment on the question about there being design alternatives is probably the better direction to take this question. The code in my answer gives the OP no more information than s/he already knows at compile-time.
Rob Hruska
+1  A: 

Although correct answers have been given, there is one more alternative which has not yet been pointed out. Basically fields and methods are not the only places where generic type information can live: super-class declaration also has this information.

This means that if your Map is actually a sub-type like:

class MyStringMap extends HashMap<String,String> { }

then it is possible to figure out generic type parameters that were used, by calling 'getGenericSuperclass' (on 'instance.getClass()'), and then accessing actual type parameters assigned. It does get quite involved since one has to traverse type hierarchy to ensure parameters get properly bound to Map (may be aliased etc), but it can be done. And this is the way "super token" is used to pass generic declaration like:

  new TypeToken<Map<String, Double>>() { }

which is used by many Java frameworks (Jersey, Guice, Jackson and many more)

Problem here is that although this does allow determining nominal types of an instance, it only works if there is actual non-generic sub-class of generic type, and I don't know of a way to enforce this requirement (calling code might find it odd that it must create a somewhat bogus Map sub-class just for this purpose).

StaxMan