简体   繁体   中英

Is there a way to tell if the runtime type was erased

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 the getValue method

  • adapt that MethodHandle , so that I could call invokeExact on it (otherwise I would need to call invoke , 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?

Well, maybe you could use good old reflection. Using reflection allows you to look up a method by name and parameter types but not return type. You can then inspect the return type to see if the caller provided the right one:

    Method method = instance.getClass().getMethod(methodName);
    Class<?> rtype = method.getReturnType();
    if (rtype != propertyClass) {
        throw new IllegalArgumentException("must use " + rtype + " instead of " + propertyClass);
    }
    MethodHandle handle = LOOKUP.unreflect(method);

You might need to adjust how you do the reflective lookup (getMethod or getDeclaredMethod) based on your needs. You might also need to check to ensure the matched method isn't abstract or static. (One would think it cannot be abstract because you're provided with an instance, but there might be an edge case I haven't thought of, such as separate compilation.) And you might also need to check that the method is declared on the same class you're reflecting on. Since you're concerned about performance, doing reflection might be too slow. But if all you're concerned about is diagnostics, you could try the happy path, and if you get a NSME, do a reflective lookup to get the right return type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM