9

I just learned about this fine looking syntax

Collections.<String>emptyList()

to get an empty List with elements which are supposedly of type String. Java's source looks like this:

public static final List EMPTY_LIST = new EmptyList<Object>();
:
public static final <T> List<T> emptyList() {
  return (List<T>) EMPTY_LIST;
}

Now if I code a method in that way where the generic type does not appear in the parameter list, is there any way how I can access the actual class that becomes T?

I'm saying, up to now my approach to code the same thing would have been

private <T> T get(String key, Class<T> clazz) {
  // here I can do whatever I want with clazz, e.g.:
  return clazz.cast(value);
}

If I removed the clazz-parameter I wouldn't be able to do the cast(). Obviously I could do

  return (T) value;

but that gives me the usual warning Type safety: Unchecked cast from Object to T. Ok, @SuppressWarnings("unchecked") helps here, but actually I want to do something with the intended return type of the method. If I add a local variable

T retValue;

I'd have to initialise it with something, null doesn't help. After I assign it like

@SuppressWarnings("unchecked")
T retValue = (T) value;

I could do, e.g.

retValue.getClass().getName()

but if the cast fails I end up with no information about T again.

Since Java (or at least my Java 6) does not have the generic info any more during runtime, I currently can't think of a way to do this. Is there a way? Or do I have to stick with my "old" approach here?

Please note that the example I lined out is very simple and doesn't make much sense. I want to do more complicated stuff here, but that's out of the scope.

Community
  • 1
  • 1
sjngm
  • 12,423
  • 14
  • 84
  • 114

5 Answers5

6

If you want the generic type at runtime you need to either have it as a field or create a sub-class of a type for a specific combination of types.

e.g.

List<String> list = new ArrayList<String>() {}; // creates a generic sub-type
final Class type = (Class) ((ParameterizedType) list.getClass()
                            .getGenericSuperclass()).getActualTypeArguments()[0];
System.out.println(type);

prints

class java.lang.String
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 1
    i.e. you can't get the generic type of a class, but can get the types of a super class (and super interfaces) – Peter Lawrey Dec 08 '11 at 20:20
  • @PeterLawrey: Whow, thanks. However, if I use `List list = new ArrayList() {};` it doesn't work. `getActualTypeArguments()[0]` is a `sun.reflect.generics.reflectiveObjects.TypeVariableImpl`. Does that make sense? – sjngm Dec 08 '11 at 20:58
  • Can you print `list.getClass()`? I suspect its ArrayList in your case. – Peter Lawrey Dec 08 '11 at 21:00
  • @PeterLawrey: Yes, it prints `class testpkg.GenTest$1`. So that's the new inline class. But yes, since it's subclassed, it's an `ArrayList`. – sjngm Dec 08 '11 at 21:02
  • @PeterLawrey: I have jdk1.6.0_18_x64 – sjngm Dec 08 '11 at 21:07
  • @PeterLawrey: I don't know if I was clear earlier, the `List...` works on my end as it did for you, but not the `List...` attempt. – sjngm Dec 08 '11 at 21:10
  • 1
    Unless you construct the original collection with a real type, it won't have a real parameter type. – Peter Lawrey Dec 08 '11 at 21:20
  • @PeterLawrey: Aww, crab. So I guess I can't get any closer than your solution. Well, thanks for the effort. – sjngm Dec 08 '11 at 21:25
4

You can't, unfortunately. All generics and type parameters are erased in runtime. So in runtime the type of your T is simply Object

korifey
  • 3,379
  • 17
  • 17
  • 2
    Agreed, I have several methods like `private T get(String key, Class clazz)` because of this. – Dave Dec 08 '11 at 19:11
2

retValue.getClass().getName() will always return the runtime type of the object and not the class name of the parameter type.

If you want to grab the parameter class, there's no other way than to use your first solution. (That is, pass the class object explicitly.)

biziclop
  • 48,926
  • 12
  • 77
  • 104
1

As you mentioned, Java generics are build time only. They are not used at run time.

Because of this, the old approach you were using will be your only way to accomplish this.

Alan Geleynse
  • 24,821
  • 5
  • 46
  • 55
-2

I find out that there is one solution for getting Class<?> from T:

public class Action<T>{
}

public Class<?> getGenericType(Object action) throws ClassNotFoundException{
   Type type =
   ((ParameterizedType)action.getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
   String sType[] = type.toString().split(" ");

   if(sType.length != 2)
      throw new ClassNotFoundException();

   return Class.forName(sType[1]);
}

The usage of code above:

Action<String> myAction = new Action<String>();

getGenericType(myAction);

I did not tested this with primitive types (int, byte, boolean).

I think that it is not very fast, but you do not have to pass Class<?> to constructor.

EDIT:

The usage above is not right, because generic superclass is not available for Action<String>. Generic super class will be available only for inherited class like class ActionWithParam extends Action<String>{}. This is reason why I changed my mind and now I suggest to pass class parameter to constructor, too. Thanks to newacct for correction.

Community
  • 1
  • 1
ivanesko
  • 1
  • 1
  • What is wrong? I know that there should be lenght checking and iteration in ((ParameterizedType)action.getClass().getGenericSuperclass()).getActualTypeArgum‌​ents() and comparsion of sType[0] with "class". I use this in my custom remote method invocation and it works. Please give me some hints if there is something other wrong. – ivanesko Mar 01 '14 at 20:57
  • I copied wrong part of my example. I have tested similar part of code and find out that it will work only for inherited classes (class example extends Action) and not for directly generic classes (new Action) as I wrote above. – ivanesko May 03 '14 at 19:36