2

I am trying to get the generic type of a class or interface implementation. I am aware that this has some risks and quirks, but I'm trying to understand what is possible.

Here's my example code:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.LinkedList;

public class Main {

    public interface MyInterface<T> {
    }

    public static class BaseImpl<T> implements MyInterface<T> {
    }

    public static class TypedImpl extends BaseImpl<Integer> {
    }

    public static void main(String[] args) throws Exception {
        LinkedList<MyInterface<Integer>> instances = new LinkedList<>();

        // 1. anonymous class from interface
        instances.add(new MyInterface<Integer>() {
        });

        // 2. class extending interface
        instances.add(new BaseImpl<Integer>());

        // 3. class with pre-defined generic type
        instances.add(new TypedImpl());

        for (MyInterface<Integer> instance : instances) {
            System.out.println("----------------------------------------------");

            Class clazz = instance.getClass();
            Type genericSuper = clazz.getGenericSuperclass();
            Type[] genericInterfaces = clazz.getGenericInterfaces();
            Class target = null;

            System.out.println("class: " + clazz.getName());
            System.out.println("generic super: " + genericSuper);
            System.out.println("generic interfaces: " + Arrays.asList(genericInterfaces));

            // attempt to 'extract' generic type
            if (genericSuper instanceof ParameterizedType) {
                target = getGeneric((ParameterizedType) genericSuper);
            } else if (genericInterfaces.length > 0) {
                for (Type genericInterface : genericInterfaces) {
                    if (genericInterface instanceof ParameterizedType) {
                        target = getGeneric((ParameterizedType) genericInterface);

                        if (null != target) {
                            break;
                        }
                    }
                }
            }

            System.out.println("TARGET: " + target);
        }
    }

    // attempt to get type argument
    public static Class getGeneric(ParameterizedType type) {
        if (MyInterface.class.isAssignableFrom((Class) type.getRawType())) {
            Type typeArg = type.getActualTypeArguments()[0];

            try {
                return (Class) typeArg;
            } catch (ClassCastException e) {
                System.out.println("cast exception for '" + typeArg + "'");
            }
        }

        return null;
    }

}

The output of this is:

----------------------------------------------
class: Main$1
generic super: class java.lang.Object
generic interfaces: [Main.Main$MyInterface<java.lang.Integer>]
TARGET: class java.lang.Integer
----------------------------------------------
class: Main$BaseImpl
generic super: class java.lang.Object
generic interfaces: [Main.Main$MyInterface<T>]
cast exception for 'T'
TARGET: null
----------------------------------------------
class: Main$TypedImpl
generic super: Main.Main$BaseImpl<java.lang.Integer>
generic interfaces: []
TARGET: class java.lang.Integer

So, my goal is to have the value of the target variable be Integer.class. For the anonymous class (#1) and the explicitly typed implementation (#3) I am able to find the target as expected. Why does it work in these instances and not in the case of #2 (BaseImpl instance)? Is there another way to accomplish this? (I am aware of the common workaround of passing the target Class via the implementing class's constructor, however I'm interested in doing this dynamically)

I have consulted the following sources:

Thanks a lot in advance,

Abel

Community
  • 1
  • 1
abelcookingfox
  • 245
  • 1
  • 2
  • 6
  • You seem to be looking for http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/ResolvableType.html – EpicPandaForce Sep 22 '15 at 12:30

2 Answers2

1

From http://tutorials.jenkov.com/java-reflection/generics.html (Bolds are mine)

Using Java Generics typically falls into one of two different situations:

Declaring a class/interface as being parameterizable. Using a parameterizable class. When you write a class or interface you can specify that it should be paramerizable. This is the case with the java.util.List interface. Rather than create a list of Object you can parameterize java.util.List to create a list of say String.

When runtime inspecting a parameterizable type itself, like java.util.List, there is no way of knowing what type is has been parameterized to. This makes sense since the type can be parameterized to all kinds of types in the same application. But, when you inspect the method or field that declares the use of a parameterized type, you can see at runtime what type the paramerizable type was parameterized to. In short:

You cannot see on a type itself what type it is parameterized to a runtime, but you can see it in fields and methods where it is used and parameterized. Its concrete parameterizations in other words.

Particular cases are explained on the page

Andres
  • 9,722
  • 4
  • 37
  • 57
0

In cases 1 and 3 the type is part of the reflection data on the classes Main$1 and Main$TypedImpl, thus you can access it.

In case 2 you only have instance data which due to type erasure doesn't contain any type information. Hence you can't access the generic type here.

To further explain:

At runtime the system can't distguish between BaseImpl<Integer> i; and let's say BaseImpl<String> s;, i.e. due to type erasure both variables i and s are of type BaseImpl.

In contrast TypedImpl t; is known to be of type TypedImpl and using reflection you can extract the generic type provided in the extends or implements statement.

Side note, since you're trying to learn what's possible:

If you'd define BaseImpl as BaseImpl<T extends Number> implements MyInterface<T> you could extract the upper bound for T from the reflection data on class BaseImpl, i.e. you'd be able to know that T must be Number or a subtype (or implementation in case of interfaces).

Thomas
  • 80,843
  • 12
  • 111
  • 143