Home > Back-end >  How to pass genericType?
How to pass genericType?

Time:09-13

class A<T> extends B<T> {}
class B<T> implements ParameterizedType {

    @Override
    public Type[] getActualTypeArguments() {
        Type superclass = this.getClass().getGenericSuperclass();
        System.out.println("A superClass:"   superclass);
        return superclass.getClass().getTypeParameters();
    }

    @Override
    public Type getRawType() {
        return null;
    }

    @Override
    public Type getOwnerType() {
        return null;
    }
}

public static void main(String[] args) {
    Action<String> action = new Action<>();
    action.getActualTypeArguments();

    B<String> b = new B<>();
    b.getActualTypeArguments();
}

the result is:

superClass:class java.lang.Object
superClass:Main$Action<T>

I want to get right result(java.lang.String), what can I do. I Try to change this.getClass().getSuperClass().getGenericSuperclass(); in class B, but the result also is java.lang.Object...

CodePudding user response:

B<String> b = new B<>();

It is impossible to get String out, that's what erasure means. No amount of futzing with .getActualTypeArguments() or getGenericType() let you do an end-run around this. If it was possible, don't you think there'd be considerably simpler APIs for this?

Bit for bit, in memory, there is no difference, whatsoever, between new B<String>() and new B<Integer>. Those generics are pretty much all things the compiler uses to link things together. It's the compiler that complains when you mess things up, java.exe has no idea what generics are.

Generics in signatures remain - they have to, as the compiler needs them to do its work, and the compiler often has only compiled code to work with (for example, to compile a hello world program, you need the java.lang.String class, but you don't need its source). As far as java.exe is concerned, these are comments - they exist solely for javac to look at it.

You can however, get at these with reflection, and that is what getGenericSuperclass() and all that jazz is about. So, while you cannot get the String out of B<String> b = new B<>(), you can get it out of the subtly different B<String> b = new B<>() {}. That's an anonymous inner class literal. If you don't know what they are, that's syntax sugar for:

// You can declare a class inside a method just fine
class $RandomName extends B<String> {
}
new $RandomName();

And, crucially, extends B<String>, that's signature stuff, and signatures can be read out with generics.

This principle is called a super type token. It means to pass along generics, you have to use this new Foo<TheActualGenericsYouWantToPass>() {} construct, ending in the braces, to ensure a new local anonymous class is made, as that then carries the generic type. The exact thing you put in it is then transfered, i.e. if you have:

public class Foo<T> {
  public void example() {
    new SuperTypeToken<T>() {};
  }
}

You're creating one that is literally just T, and not whatever T actually is, in other words, if you then do: new Foo<String>.example(), that type token is still literally T, it cannot be used to derive String (again, erasure, it's literally impossible, that information just does not exist at runtime and therefore cannot possibly be derived).

You can search the web for tutorials on how to make these, and various libraries, such as JSON parsing libraries, have TypeTokens (and documentation on how to use them), which are all implementations of this idea.

  • Related