I am trying to retrieve the Class<?>
representation of a type parameter at runtime like this:
public <E extends Exception> Try<T> onCatch (Consumer<E> onCatch) {
// retrieve `Class<E>`
}
While normally it is not possible to retrieve the types at runtime because of type erasure, due to reflection utilities and meta-data stored about generics in bytecode it should be possible like seen in this answer to the same question I have.
The accepted answer looks the following
Class<T> persistentClass = (Class<T>)
((ParameterizedType)getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
but does not work for me, due to .getActualTypeArguments()[0]
not being an instance of ParameterizedType
but Class
and thus failing the cast.
Is there any way to reliably do this? I know I can manually pass a Class<E>
reference to the method but due to this being a library being able to just use the generic type would be much more convenient.
CodePudding user response:
The reason why
Class<T> persistentClass = (Class<T>)
((ParameterizedType)getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
worked in the case of this question:
public static void chill(List<?> aListWithTypeSpiderMan) {
// Here I'd like to get the Class-Object 'SpiderMan'
Class typeOfTheList = ???;
}
public static void main(String... args) {
chill(new ArrayList<SpiderMan>());
}
was because the superclass of ArrayList<SpiderMan>
happens to be AbstractList<SpiderMan>
, which is a parameterised type. Therefore you can get the actual type arguments of that type. The type parameters of superclasses are stored as metadata in the class file if you write them in the source file.
However, in your case, whatever is passed to the onCatch
parameter is not going to have a superclass of Consumer<T>
. After all, Consumer<T>
is not a class! You need to use getGenericInterfaces
and find the one that has the name that starts with java.util.function.Consumer
.
System.out.println(
// I've assumed the Consumer interface is the first one, to keep it brief
((ParameterizedType)onCatch.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0]
);
This would work if the caller calls onCatch
like this:
onCatch(new Consumer<RuntimeException>() {
@Override
public void accept(RuntimeException e) {
}
});
The anonymous class will implement Consumer<RuntimeException>
, and this information will be written to the class file representing the anonymous class.
However, if you use a lambda:
onCatch((RuntimeException e) -> {});
Then only a method like this is generated in the same class as the caller:
private static void lambda$caller$0(RuntimeException e) {
}
and at runtime, invokedynamic
is used to create an instance that implements Consumer
, and this is the bad news: the type parameter RuntimeException
is not part of the generated class for this instance, for whatever reason.
The only way you can find RuntimeException
now then, is if you somehow know who the caller is, and find the lambda$caller$0
method, and look at its parameter.
That said, everything I've wrote so far is pretty much all implementation detail, and I wouldn't use any of that in production code. I would say you should just add a Class<E>
parameter:
onCatch(RuntimeException.class, e -> {});
It doesn't look that different on the caller's side anyway.