lundi 22 mai 2017

Correct approach to using MethodHandleProxies

In a Java project I'm currently working on, I'm dynamically loading classes then using the reflection API to find and execute methods of those classes that have certain annotations.

The code that performs the actual execution works exclusively in terms of Java-8 functional interfaces (for compatibility reasons), so I need to have an intermediate stage where the Method instances discovered using reflection are converted to appropriate functional interfaces. I achieve this using the MethodHandleProxies class.

For compatibility reasons again, the functional interfaces in question are generic interfaces. This causes an "unchecked conversion" warning when using the MethodHandleProxies.asInterfaceInstance method, since that method returns the "bare" interface.

The following is a brief example that reproduces the main steps involved:

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.util.Arrays;

public class TestClass {
    private String prefix;

    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, SecurityException {
        // Use reflection to find method.
        Method method = Arrays.stream(TestClass.class.getDeclaredMethods()) // Stream over methods of ConsumerClass
                .filter(m -> m.isAnnotationPresent(Marker.class)) // Retain only methods with @Marker annotation
                .findFirst().get(); // Get first such method (there is only one in this case)

        // Convert method to "MethodInterface" functional interface.
        MethodHandle handle = MethodHandles.lookup().unreflect(method);
        MethodInterface<TestClass, String> iface = MethodHandleProxies.asInterfaceInstance(MethodInterface.class, handle);

        // Call "testMethod" via functional interface.
        iface.call(new TestClass("A"), "B");
    }

    public TestClass(String prefix) {
        this.prefix = prefix;
    }

    @Marker
    public void testMethod(String arg) {
        System.out.println(prefix + " " + arg);
    }

    @Retention(RUNTIME)
    public @interface Marker { }

    @FunctionalInterface
    public interface MethodInterface<I,V> {
        void call(I instance, V value);
    }
}

This code compiles and runs, but has an unchecked conversion warning on the assignment to iface.

As I see it, I have three options:

  1. Simply live with the warning.
  2. Add an @SuppressWarnings annotation to the assignment.
  3. Come up with an alternative type-safe approach.

I try to ensure there are no warnings generated by my code (to minimise the opportunities for bugs), so I'm not keen on option 1. Option 2 feels like it's simply "papering over the cracks", but is acceptable if absolutely necessary. So my preferred option is to come up with a different approach.

Is there a different approach that is inherently type-safe?





Aucun commentaire:

Enregistrer un commentaire