Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Java reflection to check a given class is implements Iterable<? extends T>, for any given T

I have a specific target type (decided at runtime), and an iterable class that I'm comparing to it. I'm trying to write a method that checks the generic parameters of the class, to see if it's an iterable of something that subclasses my target type. Examples:

Class<?> X = SomeObject.class;

matches(X, new ArrayList<SomeObject>()) -> true
matches(X, new ArrayList<SubclassOfSomeObject>()) -> true
matches(X, new ArrayList<SomeOtherObject>()) -> false
matches(X, new ArrayList()) -> true (I think?)
matches(X, new Iterable<SomeObject>() { ... }) -> true
matches(X, new ListOfSomeObjects()) -> true 
                       (where ListOfSomeObjects extends Iterable<SomeObject>)
like image 758
Tim Perry Avatar asked Feb 02 '13 01:02

Tim Perry


People also ask

How do you implement Reflection in Java?

In order to reflect a Java class, we first need to create an object of Class . And, using the object we can call various methods to get information about methods, fields, and constructors present in a class. class Dog {...} // create object of Class // to reflect the Dog class Class a = Class. forName("Dog");

What is Iterable <> in Java?

The Iterable interface was introduced in JDK 1.5. It belongs to java. lang package. In general, an object Implementing Iterable allows it to be iterated. An iterable interface allows an object to be the target of enhanced for loop(for-each loop).

Is Java Reflection can be used to inspect Java classes at runtime?

Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect" upon itself, and manipulate internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.

What is iterable and Iterator in Java?

Iterable doesn't store any iteration state. Iteration state is the pointer to the element in a collection which the iterator is going to return next. Iterator instance maintains the iteration state. This implies the user can check if the next element exists, move forward to the next element, etc.


1 Answers

Unfortunately, what you're trying to do is very involved, because of the combination of type erasure and limitations of the reflections API.

It's true that you can get the generic arguments of a superclass using a combination of Class.getGenericSuperclass and ParameterizedType.getActualTypeArguments. This is the mechanism that e.g. Guava's TypeToken class uses to capture generic type arguments. But what you're asking for here is the generic type argument of an interface that may have been implemented at any point in the inheritance chain - notwithstanding that interfaces can themselves inherit from each other while freely resolving or declaring new type parameters.

To demonstrate, take the following method:

static void inspect(Object o) {
    Type type = o.getClass();
    while (type != null) {
        System.out.print(type + " implements");
        Class<?> rawType =
                (type instanceof ParameterizedType)
                ? (Class<?>)((ParameterizedType)type).getRawType()
                : (Class<?>)type;
        Type[] interfaceTypes = rawType.getGenericInterfaces();
        if (interfaceTypes.length > 0) {
            System.out.println(":");
            for (Type interfaceType : interfaceTypes) {
                if (interfaceType instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType)interfaceType;
                    System.out.print("  " + parameterizedType.getRawType() + " with type args: ");
                    Type[] actualTypeArgs = parameterizedType.getActualTypeArguments();
                    System.out.println(Arrays.toString(actualTypeArgs));
                }
                else {
                    System.out.println("  " + interfaceType);
                }
            }
        }
        else {
            System.out.println(" nothing");
        }
        type = rawType.getGenericSuperclass();
    }
}

This will reflect on an object and climb its inheritance chain to report on its implemented interfaces and their generic arguments (if applicable).

Let's try it on the first case you listed:

inspect(new ArrayList<SomeObject>());

This prints:

class java.util.ArrayList implements:
  interface java.util.List with type args: [E]
  interface java.util.RandomAccess
  interface java.lang.Cloneable
  interface java.io.Serializable
java.util.AbstractList<E> implements:
  interface java.util.List with type args: [E]
java.util.AbstractCollection<E> implements:
  interface java.util.Collection with type args: [E]
class java.lang.Object implements nothing

You can see that the type parameter E has not been resolved. This is perfectly understandable given type erasure - at runtime the bytecode instructions corresponding to new ArrayList<SomeObject>() have no concept of SomeObject.

The case of the anonymous class is different:

inspect(new Iterable<SomeObject>() {
    @Override
    public Iterator<SomeObject> iterator() {
        throw new UnsupportedOperationException();
    }
});

Prints:

class sandbox.Main$1 implements:
  interface java.lang.Iterable with type args: [class sandbox.SomeObject]
class java.lang.Object implements nothing

Here, we have the type argument available at runtime since the anonymous class resolved the type parameter by implementing Iterable<SomeObject>. ListOfSomeObjects and any of its subclasses would work for the same reason.

Okay, so as long as some class in the inheritance chain resolves the type parameter E along the way, we can match against it? Unfortunately no, at least not with the method above:

inspect(new ArrayList<SomeObject>() { });

This prints:

class sandbox.Main$1 implements nothing
java.util.ArrayList<sandbox.SomeObject> implements:
  interface java.util.List with type args: [E]
  interface java.util.RandomAccess
  interface java.lang.Cloneable
  interface java.io.Serializable
java.util.AbstractList<E> implements:
  interface java.util.List with type args: [E]
java.util.AbstractCollection<E> implements:
  interface java.util.Collection with type args: [E]
class java.lang.Object implements nothing

You can see that the type argument for ArrayList is known to be SomeObject, but that's where it stops. There's no connecting relationship between the type parameters. The reason is this bit of code:

Class<?> rawType =
        (type instanceof ParameterizedType)
        ? (Class<?>)((ParameterizedType)type).getRawType()
        : (Class<?>)type;
Type[] interfaceTypes = rawType.getGenericInterfaces();

getGenericInterfaces is the only way to get type argument information for the interfaces, but that method is declared by Class, not Type. Whenever the method has a ParameterizedType instance, which holds the state representing its subclass's generic-ness, it's forced to call getRawType, which returns the Class singleton devoid of type argument information. It's a catch-22 that results in only being able to get the type arguments of interfaces implemented with concrete type arguments.

I don't know of any reflection API method that matches type arguments with the parameters they resolve. Theoretically one could write reflective code that climbed up the inheritance chain, located the class that implemented Iterable (or a subinterface) and then climbed back down until it matched the corresponding type argument. Unfortunately I can't think if how this would be implemented. Classes are free to declare type parameters with any name and in any order they want, so naive matching based on name or position is out. Maybe somebody else can contribute a solution.

like image 67
Paul Bellora Avatar answered Oct 04 '22 07:10

Paul Bellora