0

I am trying to get the Class<?> for the generic type used by an interface at run time.

So if I have the following

public class A<T> implements B<Z> {}

public interface B<T> extends C<Z> {}

public interface C<T> {}

public class Z {}

A temp = new A<MyClass>();

I want to be able to get the Class<?> of the first (or later) generic of B, C, or D, given the instance temp.

Right now I have this, but it only works for interfaces this instance's class directly implements. I want to modify it to work regardless of how this instance inherited the target interface.

/**
 * Finds this instances implementation of the interfaceClass, and returns
 * the associated generic type
 *
 * @param instance
 *            the instance to extract from
 * @param interfaceClass
 *            The class this instance implements
 * @param paramIndex
 *            Index of the generic class to get the class of
 * @return The class of the generic type
 * @throws ClassNotFoundException
 */
public static <T> Class<?> ParameterizedClass(final T instance, final Class<T> interfaceClass, final int paramIndex)
        throws ClassNotFoundException {
    final Type[] sooper = instance.getClass().getGenericInterfaces();
    for (final Type t : sooper) {
        if (!(t instanceof ParameterizedType)) {
            continue;
        }

        final ParameterizedType type = ((ParameterizedType) t);
        if (type.getRawType().getTypeName().equals(interfaceClass.getTypeName())) {
            return Class.forName(type.getActualTypeArguments()[paramIndex].getTypeName());
        }
    }

    return null;
}

With the code I have now, and the example classes, I get the following results

ParameterizedClass(new A<Integer>, B.class, 0); // returns Class<Z>

ParameterizedClass(new A<Integer>, C.class, 0); // returns null, but I want Class<Z>

This is what I mean by directly implements. I can't find how to read the inherited classes.

Tezra
  • 8,463
  • 3
  • 31
  • 68
  • What do you mean exactly be 'directly implements'? Do you know about [erasure](https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html)? Have you read: https://stackoverflow.com/questions/3437897 ? – Jorn Vernee Jan 10 '18 at 18:23
  • ```Class A ... ``` is not proper Java; please make it Java syntax correct. – Valentin Ruano Jan 10 '18 at 18:59
  • @JornVernee I know about erasure. But the information to reconstruct it still lives in the metadata. I added an example usage showing what I mean by it only working on "directly implemented". Inherited interfaces don't show up in this. – Tezra Jan 10 '18 at 19:03
  • @ValentinRuano I was in a hurry earlier and thought people would understand the shorthand. I replaced it with the actual example class definitions – Tezra Jan 10 '18 at 19:09
  • Find my fix for you second example call below. Working with ParameterizedType is a bit sketchy stuff, I'm sure that there is plenty of edge cases for what the code above won't work. You really need to do this? In general is best to avoid reflection whenever possible – Valentin Ruano Jan 10 '18 at 20:51

3 Answers3

0

In order to fix your second call so that it returns Class<Z> I guess you need to enquire the middle interface inheritance, in this case B's to get that Class<Z>.

You can do that with a recursion like this in the second loop:

public static Class<?> parameterizedClass(final Class<?> root, final Class<?> target, final int paramIndex)
        throws ClassNotFoundException {

    final Type[] sooper = root.getGenericInterfaces();
    for (final Type t : sooper) {
        if (!(t instanceof ParameterizedType)) {
            continue;
        }
        final ParameterizedType type = ((ParameterizedType) t);
        if (type.getRawType().getTypeName().equals(target.getTypeName())) {
            return Class.forName(type.getActualTypeArguments()[paramIndex].getTypeName());
        }
    }
    for (final Class<?> parent : root.getInterfaces()) {
        final Class<?> result = parameterizedClass(parent, target, paramIndex);
        if (result != null) {
            return result;
        }
    }
    return null;
}

public static Class<?> parameterizedClass(final Object object, final Class<?> target, final int paramIndex)
        throws ClassNotFoundException {
    return parameterizedClass(object.getClass(), target, paramIndex);
}
Valentin Ruano
  • 2,726
  • 19
  • 29
  • I verified this in the test program. (suggested edit to fix typos I had to fix for it to run) This fixes my primary concern, and I'll accept it if a better answer doesn't show up in a day or two. In my case, this is fine, because the types are explicitly stated in the class definition. (but the system is setup in a way that the compiler can't verify the relationship, so I need to verify at runtime during setup that it still holds true) – Tezra Jan 10 '18 at 21:07
0

You can use my utility class GenericUtil, it has the following method

public static Type[] getGenericTypes(Type sourceType, Class<?> targetClass)

For you case:

System.out.println(Arrays.toString(GenericUtil.getGenericTypes(A.class, B.class)));
System.out.println(Arrays.toString(GenericUtil.getGenericTypes(A.class, C.class)));
System.out.println(Arrays.toString(GenericUtil.getGenericTypes(B.class, C.class)));
System.out.println(Arrays.toString(GenericUtil.getGenericTypes(A.class, Z.class)));

will output

[class xdean.stackoverflow.java.reflection.Q48193539$Z]
[class xdean.stackoverflow.java.reflection.Q48193539$Z]
[class xdean.stackoverflow.java.reflection.Q48193539$Z]
[]

You can find its implemation on github. It has detailed javadoc and it can resolve most complex situation. For example:

interface I1<A> {
}

interface I2<A, B> {
}

class C5<A> implements I1<I2<A, Object>> {
}

//return Type[]{I2<A, Object> (ParameterziedType)}
GenericUtil.getGenericTypes(C5.class, I1.class);

You can find more complex situation in its test.

Dean Xu
  • 4,438
  • 1
  • 17
  • 44
-1

In java in theory this is not possible at runtime due to "erasure". I experienced that you can do actually but only for the first level and only with sun provided classes, so not in android for example. At the second level you will only find your "T"(or whatever) label.

Henning Luther
  • 2,067
  • 15
  • 22