2

Say I have these classes:

class Container<T> {
  private List<T> list;
}

And I have an instance of Container, say

Container<?> instance = new Container<String>();

Is there a way to find out list's actual type parameter at run-time? I.e., that it's a List of String, rather than a List of java.lang.reflect.TypeVariable.

I've been looking at Java's reflection (field.getGenericType()), various libraries (Guava Reflect, Typetools, ClassMate, even ASM, etc.) but I can't figure it out.

I have no control over the Container type, so I can't add any fields to it.

I realize it may not be possible at all due to erasure, although I don't mind messing with bytecode if that makes it possible.

EDIT I over-simplified my initial question. I have an additional piece of information available; I also have this type:

class StringContainerContainer {
  private Container<String> container;
}

(And several more types like it, for different generic parameters.)

At run-time, I'm given the instance object mentioned above. With a little refactoring of my codebase, I can also get to the StringContainerContainer class (or whichever *ContainerContainer class is relevant), which I can use to get a java.lang.reflect.Field for container.

jqno
  • 13,931
  • 7
  • 52
  • 76

2 Answers2

2

Due to type erasure, you can't do it. The most you can do with reflection is get information about the declared type variables, not the actual types bound by an instance at run time. That information is thrown away by the compiler.

For more info, read the tutorial on Type Erasure and/or section 4.6 of the Java Language Specification, which discusses type erasure.

If you need run-time type information about the instance class, the best you can do is require that a Class object be passed at instance construction:

class Container<T> {
    private List<T> list;
    private Class<T> type;

    public Container(Class<T> type) {
        this.type = type;
    }
}
Ted Hopp
  • 222,293
  • 47
  • 371
  • 489
  • Hm, that's too bad. When I found out that in some cases you can get the type back, I was getting my hopes up :). BTW, I don't have control over Container, so adding a field to it is impossible, unfortunately. I'll update my question to reflect that. – jqno Jan 07 '16 at 22:14
2

With, your edit, yes, you can find out that String is the actual type parameter:

Field field = StringContainerContainer.class.getDeclaredField("container");
ParameterizedType gt = (ParameterizedType) field.getGenericType();
Class<?> arg = (Class<?>) gt.getActualTypeArguments()[0];
System.out.println(arg);

I should point out that this identifies the type argument of the container field of StringContainerContainer. Given how Container is written, that also happens to be the type argument of its list field, but that field isn't directly examined.

erickson
  • 249,448
  • 50
  • 371
  • 469