How to get the Generic Type Parameter?

2019-04-11 18:30发布

Simply:

public static class MyClass<T> {
    // i don't want to keep an instance of T, if it is not necessary.
    // and it is not nice, not neat.

    // Or, let's say, the member are in the form of :
    ArrayList<T> mArrayList = new ArrayList<T>();
    // the problem of getting the generic type parameter is still present.
}

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>();
    getParamType( myObject );
}

private static final <T> void getParamType(final MyClass<T> _myObject) {
    System.out.println(_myObject.getClass().getTypeParameters()[0]);    // T
    System.out.println(((T) new Object()).getClass());                  // class java.lang.Object
}

How to let the code print class java.lang.Integer?

i know quite a few of stackoverflow threads are asking (and answering) about this. Yet they couldn't solve this question.

  • i don't know why some need to call getGenericSuperclass() - as there is no inheritance involved in this simple case.
  • And i can't cast it to ParameterizedType as well.

.

System.out.println((ParameterizedType) _myObject.getClass());
// Compile Error: Cannot cast from Class<capture#11-of ? extends TreeTest2.MyClass> to ParameterizedType

System.out.println((ParameterizedType) _myObject.getClass().getGenericSuperclass());
// Runtime Exception: java.lang.ClassCastException

Based on @Thomas's guide, i have found a work-around way to get class java.lang.Integer.

First, we create an anonymous (it need to be anonymous) sub-class of MyClass<T> in the testing code. (Which is weird. Why it only support sub-classes?)

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>() {};  // Anonymous sub-class
    getParamType( myObject );
}

Then we can use the getGenericSuperclass() method to get a Type then cast it to ParameterizedType and afterwards uses getActualTypeArguments():

private static final <T> void getParamType(final MyClass<T> _myObject) {
    System.out.println( ((ParameterizedType) _myObject.getClass().getGenericSuperclass()).getActualTypeArguments()[0] );
}

It perfectly prints class java.lang.Integer.

This is not-so-good because the testing codes should simulate the actual situation, where users most likely won't keep creating meaningless sub-classes.

This approach is based on the idea of the TypeReference class. But i don't really know how to use it. I have tried class MyClass<T> extends TypeReference<T>. But i still have to create sub-class of MyClass<T> to have TypeReference.getType() prints class java.lang.Integer.

Please help, and thanks for any inputs, as the best approach is not here yet.


A further question based on the above workaround: Why only anonymous sub-class works?

public static class SubMyClass<T> extends MyClass<T>{}

@Test
public final void test() {
    MyClass<Integer> myObject = new MyClass<Integer>() {};  // Anonymous sub-class
    getParamType( myObject );               // class java.lang.Integer

    MyClass<Integer> mySubObject = new SubMyClass<Integer>();   // named sub-class
    getParamType( mySubObject );            // T
}

(MyClass and getParamType() unchanged.)

4条回答
你好瞎i
2楼-- · 2019-04-11 18:44

Java has a misguided feature called Type Erasure that specifically prevents you from doing that.

Generic parameter information does not exist at runtime.

Instead, you can manually accept a Class<T>.

查看更多
祖国的老花朵
3楼-- · 2019-04-11 18:50

To learn the value of T you'll need to capture it in a type definition by subclassing MyClass:

class MyStringClass extends MyClass<String> {}

You can also do this with an anonymous class if you want:

MyClass<String> myStringClass = new MyClass<String>{}();

To resolve the value of T, you can use TypeTools:

Class<?> stringType = TypeResolver.resolveRawArgument(MyClass.class, myStringClass.getClass());
assert stringType == String.class;
查看更多
一纸荒年 Trace。
4楼-- · 2019-04-11 19:01

This is sort of difficult, because Java deliberately can't do that ("type erasure").

The work-around is called super type tokens. There are also some threads on SO about that (like this one or that one).

查看更多
男人必须洒脱
5楼-- · 2019-04-11 19:04

When you have a question like this, you should ask yourself, how would you do it without Generics? Because any Generics program can be converted into an equivalent program without Generics. (This conversion is called type erasure.) So if you cannot do it without Generics, you cannot do it with Generics either.

Your program without Generics looks like this:

public static class MyClass {
    ArrayList mArrayList = new ArrayList();
}

@Test
public final void test() {
    MyClass myObject = new MyClass();
    Integer result = getParamType( myObject ); // how would you implement getParamType()?
}
查看更多
登录 后发表回答