12

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.

Community
  • 1
  • 1
naumcho
  • 18,671
  • 14
  • 48
  • 59
  • 4
    See this: http://stackoverflow.com/questions/1942644/get-generic-type-of-java-util-list – Rob Hruska Sep 10 '10 at 19:41
  • You could use [Reflection](http://download.oracle.com/javase/tutorial/reflect/index.html) – Aillyn Sep 10 '10 at 19:44
  • What's the functional requirement? There may be better solutions than fiddling with reflection. – BalusC Sep 10 '10 at 20:03
  • No, reflection will not do it, due to type erasure. – StaxMan Sep 10 '10 at 20:36
  • 1
    @StaxMan - Actually, reflection *will* work, because you can retrieve information about the declared type of the instance. Your comment on Andy's answer is correct, though: if all you have is the instance, you cannot get the type information from it. But it's likely the instance was declared *somewhere* (class member, method signature, etc.), and that declaration could be used reflectively to ascertain the type. Technically, the values aren't guaranteed, though. – Rob Hruska Sep 10 '10 at 20:49
  • The question asked how to get the type "from an instance." – Andy Thomas Sep 10 '10 at 22:22

3 Answers3

17

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.

Community
  • 1
  • 1
Rob Hruska
  • 118,520
  • 32
  • 167
  • 192
  • Reflection can be used at runtime on values referenced in a map. – Andy Thomas Sep 10 '10 at 19:59
  • 4
    Noted should be that this only returns the **declared** generic types (as in `Map map` part), not the **instantiated** generic types (as in `new HashMap()` 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 Sep 10 '10 at 20:07
  • @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 Sep 10 '10 at 20:18
7

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
  • 84,978
  • 11
  • 107
  • 151
  • 3
    This is not accurate; there are answers giving the exact code needed to do it, a couple different ways – Michael Mrozek Sep 10 '10 at 20:02
  • 2
    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 Sep 10 '10 at 20:36
2

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
  • 113,358
  • 34
  • 211
  • 239