0

I've done some research before posting but with no concrete answer.

Is it possible to get Actual Type Arguments for the super class which get its parametrized type from its child? for example..

GenericSubclass<T> extends GenericClass<T>

By instantiating GenericSubclass as:

GenericSubclass<String> genericClass = new GenericSubclass<>();

and executing the below returns "T"

Type type = ((ParameterizedType)this.getClass().
           getGenericSuperclass()).getActualTypeArguments()[0];

But what I need is java.lang.String. Any comments are appreciated

Radiodef
  • 35,285
  • 14
  • 78
  • 114
  • 1
    What is your concrete use case? Maybe you could achieve it without reflection, e.g. by passing `Class` to your `GenericClass` from the subclass – Lino Jul 17 '18 at 18:54
  • A general rule of thumb is: *when needing to use reflection, then you probably have a design flaw or are over complicating things* – Lino Jul 17 '18 at 18:56
  • I'm aware of such solution but if it is possible i would like to avoid constructor modification by passing Class around. – Dimitrios Posnakidis Jul 17 '18 at 19:00
  • This is then probably not possible due to type erasure of java generics. Any information of *what actually* the type was is lost at runtime – Lino Jul 17 '18 at 19:08
  • Thanks @Lino. I guess there some limitations still in java – Dimitrios Posnakidis Jul 17 '18 at 19:15
  • You're welcome. Type erasure is one of the things that is just a limitation in java, but the workaround with `Class` is probably the best way to go, as it doesn't rely on "slow" reflection – Lino Jul 17 '18 at 19:16
  • @Lino huh, I never liked that "rule", most of java stuff just sits on huge pile of reflections – GotoFinal Jul 17 '18 at 23:36

1 Answers1

1

I'm just going to assume that anyone reading this answer already knows about type erasure. If you don't, you should follow the Type Erasure tutorial, and I'd also recommend reading through the Q&A Java generics type erasure: when and what happens.

An explanation of type erasure provides some helpful background, but it doesn't actually explain what getActualTypeArguments() does and why the code in the question doesn't work.


For a Class c, c.getGenericSuperclass() effectively gets the type in the extends clause of c.

So, for example, if we had this (using a real class):

public class ArrayList<E> extends AbstractList<E> {...}

And we said ArrayList.class.getGenericSuperclass(), what we get is a parameterized type representing AbstractList<E>. Again, this is the type in the extends clause.

If we had this:

ArrayList<Long> al = new ArrayList<Long>();

al.getClass() returns ArrayList.class, so al.getClass().getGenericSuperclass() again returns AbstractList<E>.

The "actual" type argument to AbstractList<E> is the type variable E, so if we said:

Type actualTypeArgument =
    ((ParameterizedType)
        al.getClass()
          .getGenericSuperclass())
          .getActualTypeArguments() [0];

Then actualTypeArgument refers to the type variable E, declared by ArrayList. (And in fact, it's the case that actualTypeArgument == ArrayList.class.getTypeParameters()[0] is true.)


What's usually done while using this idiom is to create an anonymous subclass any time you create an instance of the type whose type argument you want to capture:

ArrayList<Long> al = new ArrayList<Long>() {};
//                                    Note ^^

That implicitly declares a class like this:

class AnonymousArrayList1 extends ArrayList<Long> {}

(Except that the anonymous class has no name. Also, we could declare a subclass like that explicitly, instead of using an anonymous class.)

Now when we call al.getClass() we get AnonymousArrayList1.class whose generic superclass is ArrayList<Long>. Now you can get the actual type argument to ArrayList<Long>, which is Long.

(Here's a short program which demonstrates all of the above.)


As for allowing a level of indirection, it's possible, but the code is more complicated. You're also still bound by the rule above that an actual class has to be provided as a type argument in the extends clause.

Here's an example of this which allows any number of interim superclasses:

package mcve;

import java.lang.reflect.*;

public abstract class KnowMyType<T> {
    public KnowMyType() {
        System.out.println(getMyType());
    }

    @SuppressWarnings("unchecked")
    private Class<T> getMyType() {
        try {
            return (Class<T>) findMyType(getClass(), null);
        } catch (RuntimeException x) {
            throw new IllegalArgumentException("illegal type argument", x);
        }
    }

    private Type findMyType(Class<?> plainClass, Type genericClass) {
        Class<?> plainSuper = plainClass.getSuperclass();
        Type genericSuper = plainClass.getGenericSuperclass();

        Type t;

        if (plainSuper == KnowMyType.class) {
            t = ((ParameterizedType) genericSuper).getActualTypeArguments()[0];
        } else {
            t = findMyType(plainSuper, genericSuper);
        }

        if (t instanceof TypeVariable<?>) {
            TypeVariable<?>[] vars = plainClass.getTypeParameters();

            for (int i = 0; i < vars.length; ++i) {
                if (t == vars[i]) {
                    t = ((ParameterizedType) genericClass).getActualTypeArguments()[i];
                    break;
                }
            }
        }

        return t;
    }
}

That will let you do this:

class KnowMyTypeSub<T> extends KnowMyType<T> {}
new KnowMyTypeSub<Long>() {}; // constructor prints 'class java.lang.Long'

Guava's TypeToken also does stuff like this automatically.

Radiodef
  • 35,285
  • 14
  • 78
  • 114