How to use Java reflection to test this class implements Iterable <? extends T> for any given T

I have a certain type of goal (solved at runtime) and a repeating class that I compare with it. I am trying to write a method that checks the general parameters of a class to see if it is iterable by 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>) 
+4
source share
2 answers

Unfortunately, what you are trying to do is very much related to the combination of type erasure and the limitations of the reflection APIs.

It is true that you can get the general arguments of a superclass using a combination of Class.getGenericSuperclass and ParameterizedType.getActualTypeArguments . This is the mechanism that, for example, the Guava TypeToken class uses to capture general type arguments. But what you are asking here is an argument of a general type of interface that can be implemented anywhere in the inheritance chain - despite the fact that the interfaces themselves can inherit from each other with free resolution or declaration of new type parameters.

To demonstrate, run 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 the object and go up the inheritance chain to report on its implemented interfaces and their common arguments (if applicable).

Try in the first case that you specified:

 inspect(new ArrayList<SomeObject>()); 

Fingerprints:

 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 a parameter of type E not allowed. This is understandable if you remove the erasure - during the execution of the bytecode instructions, the corresponding new ArrayList<SomeObject>() do not have the concept of SomeObject .

The case of an anonymous class is different:

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

Print

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

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

Well, as long as some class in the inheritance chain allows a parameter of type E along the way, can we map it? Unfortunately not, at least not using the method above:

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

Fingerprints:

 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 as SomeObject , but where it stops. There is no connection between type parameters. The reason is because 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 information about type parameters for interfaces, but this method is declared by Class , not Type . Whenever a method has an instance of ParameterizedType that contains a state representing its subclass universality, it is forced to call getRawType , which returns a single Class element, devoid of information about the type argument. This is catch-22, which only leads to getting interface type arguments implemented with specific type arguments.

I do not know of any reflection API method that matches type arguments with the parameters that they allow. It was theoretically possible to write reflective code that climbed the inheritance chain, on which a class was found that implemented Iterable (or a subinterface), and then went down until it matched the corresponding type argument. Unfortunately, I can’t imagine how this will be implemented. Classes can declare type parameters with any name and in any order, so they are naively consistent with the name or position. Perhaps someone else can contribute.

+8
source

I do not think that you can get the general type of a parameter only from an instance:

 new ArrayList<Integer>(); 

If you have a field with an instance, you can get it from there:

 private List<Integer> list = ArrayList<Integer>(); 

See this similar question: Get a generic java.util.List type

+1
source

All Articles