Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
538 views
in Technique[技术] by (71.8m points)

generics - 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>)
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...