3

I would like to request for an item class of collection (kind of specific reflection). But regarding to type erasure it seems not possible and also regarding to some topics I've read here on stack. There are some workarounds (here), but I'm curious if somebody know how is it done for example by DWR:

http://directwebremoting.org/dwr/documentation/server/configuration/dwrxml/signatures.html

or in case that there is some better workaround it would be great.

Let's say we have something like:

public String foo(List<String> possibleFoos) {

and all I need to is find out that parameter possibleFoos is list of Strings, not just List

Community
  • 1
  • 1
Radim
  • 164
  • 1
  • 4
  • 18

1 Answers1

6

While it's true that Java will erase types at runtime (thus turning List<String> into just List), in many cases it actually does store the generic type in runtime allowing you to restore the information lost to erasure. You can retrieve the actual generic types for these:

  • Method arguments (your case)
  • Method return types
  • Field types
  • Super classes/interfaces

This means if you simply have an object of type List, there's nothing you can do to get it's generic type (object.getClass() will get you List and that's it) - it's been permanently lost. But, if you're trying to figure out the generic type for a method argument or any of the above, you usually can by using reflection. As your case doesn't involve type variables or other complications, it's pretty straightforward to get the actual type:

ParameterizedType listArgument = (ParameterizedType) ClassDeclaringFoo.class.getMethod("foo", List.class).getGenericParameterTypes()[0];
Type listType = listArgument.getActualTypeArguments()[0]; //This will be a Class representing String, the type of the List

If you had a more parameters and a map instead:

public String foo(Object bar, Map<String, Number> possibleFoos) { ... }

The code would be similar:

ParameterizedType mapArgument = (ParameterizedType) ClassDeclaringFoo.class.getMethod("foo", Object.class, Map.class).getGenericParameterTypes()[1]; //1 is the index of the map argument
Type keyType = mapArgument.getActualTypeArguments()[0]; //This will be a Class representing String, the type of the Map key
Type valType = mapArgument.getActualTypeArguments()[1]; //This will be a Class representing Number, the type of the Map value

It's safe to assume this is what DWR is using as well, as the types are method arguments.

Similar methods are available for other listed cases:

  • Class.getMethod(...).getGenericReturnType() will get you the real return type
  • Class.getField(fieldName).getGenericType() will get you the real type of the field
  • Class.getGenericSuperClass() will get you the real super type
  • Class.getGenericInterfaces() will get you the real interface types

Equivalent methods exist allowing access to AnnotatedType (generic type plus annotations on the type usage) introduced in Java 8:

  • Class.getMethod(...).getAnnotatedParameterTypes()
  • Class.getMethod(...).getAnnotatedReturnType()
  • Class.getField(fieldName).getAnnotatedType()
  • Class.getAnnotatedSuperClass()
  • Class.getAnnotatedInterfaces()

Now, this is all dandy when your case as simple as in the example. But, imagine if your example looked like this:

public T foo(List<T> possibleFoos) {...}

In this case, getGenericParameterTypes()[0].getActualTypeArguments()[0] would give you T which is rather useless. To resolve what T stands for, you'd have to look into the class definition, and perhaps super classes, while keeping track of how the type variables are named in each class, as the names could be different in each.

To make working with generic type reflection easier, you can use a wonderful library called GenTyRef that does the hard work for you, and if you need support for AnnotatedTypes, you can use my fork called GeAnTyRef (both are in Maven Central). They also include a type factory, that allows you to construct instances of (Annotated)Type, which you can not easily do using normal Java API. There's also a handy super type token implementation allowing you to get an (Annotated)Type literal.

With those, you can do everything with generic types that Java allows without the hassle I explained above:

  • GenericTypeReflector#getExactParameterTypes( ... )
  • GenericTypeReflector#getExactReturnType( ... )
  • GenericTypeReflector#getExactFieldType( ... )
  • GenericTypeReflector#getExactSuperType( ... )

And many more operations, like figuring out if one Type is a super type of another (similar to Class#isAssignableFrom but for generic types), resolving specific type variables etc.

kaqqao
  • 12,984
  • 10
  • 64
  • 118
  • Thank you for your comprehensive answer, that's exactly what I wanted to know! – Radim Oct 24 '16 at 14:29
  • I've used the "hassle" version and it works fine for the list, but it doesn't work for map, because getParameterizedType returns class object. Since I've got a better insight regarding to your previous post I've tried to search again how to act with map, but I didn't find anything. Can you help me once more please? – Radim Oct 25 '16 at 07:58
  • 1
    @Radim I've updated my answer to include an example that has a Map as the second argument. Is that what you're after? – kaqqao Oct 25 '16 at 09:04
  • unfortunately no. Problem is that I'm working with type Parameter. What I do with the list is (ParametrizedType)parameter.getParameterizedType. But getParameterizedType returns a Class object instead of Type for map. I'm using it because I'm iterating over parameters of matched method. – Radim Oct 25 '16 at 09:33
  • Regarding to your post I'm assuming that I will have to switch from classic parameters to generic parameters and iterate over it, I'll try to do some workaround and let you know. – Radim Oct 25 '16 at 09:39
  • Well I've tried as it is written in your example and it doesn't work with map. If you will try to cast ParameterizedType to map, you will get an exception because of casting Class.. – Radim Oct 25 '16 at 09:45
  • Well I found out that the problem was somewhere else, sorry and thank you for help. – Radim Oct 25 '16 at 09:50
  • 2
    You can't use Parameter directly. You have to get the return type by index from getActualTypeArguments. Anyway, glad you solved it. – kaqqao Oct 25 '16 at 11:52