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.