This is going to be a bit complicated to explain, but I will try.
Suppose you have a generic class:
static class Box<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
And a method that allows to call the getValue
in a reflective way:
// it's just an example, the real world scenario is slightly more involved
private static final Lookup LOOKUP = MethodHandles.lookup();
public static <T, R> T result(String methodName, Class<T> propertyClass, R instance) {
try {
/* line1 */
MethodHandle handle = LOOKUP.findVirtual(
instance.getClass(),
methodName,
MethodType.methodType(propertyClass)
);
/* line2 */
handle = handle.asType(handle.type()
.changeReturnType(Object.class)
.changeParameterType(0, Object.class));
/* line3 */
Object obj = handle.invokeExact(instance);
return propertyClass.cast(obj);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
What this does is
-
create a
MethodHandle
to thegetValue
method -
adapt that
MethodHandle
, so that I could callinvokeExact
on it (otherwise I would need to callinvoke
, which is slower). But this step is entirely optional. -
once I build the
MethodHandle
, invoke it.
And now let's try to call this:
public static void main(String[] args) throws Throwable {
Box<Long> box = new Box<>();
box.setValue(42L);
result("getValue", Long.class, box);
}
This should work, right? Well, no. This will fail with:
Caused by: java.lang.NoSuchMethodException: no such method: GenericTest$Box.getValue()Long/invokeVirtual
I understand why, because the erased type of T extends Number
is Number
, so the invocation is really supposed to be:
result("getValue", Number.class, box); // not Long.class
This is obvious to me, but not to the callers of the library at my work-place and I can't blame them. Please note that this is a simplified example...
When they build Box<Long> box = new Box<>();
with a Long
type, it is sort of natural to provide Long.class
further, instead of Number.class
. The solution is obviously trivial, but, I was thinking that if I could (at runtime) "see" that the return type of getValue
was a generic type, I could throw a proper error message. For example:
"you provided Long.class, but the generic type was erased to ..."
In other words if I could tell at runtime that the return type is Number.class
from getValue
and that it is the result of some erasure, I could be a bit smarter in the later decisions.
Is that possible?
Aucun commentaire:
Enregistrer un commentaire