How is it possible to dynamically convert a method object into a functional interface in Java?

217 Views Asked by At

I have a few classes that each implement an interface. From these classes I search out a method using an annotation. This method returns a boolean and always has an object as parameter, which always inherits from another fixed object. Now I want to create a functional interface from this method. Optimally of course a Predicate, which takes over the mentioned parameter. This I have now tried for some time to implement with LambdaMetafactory:

private Predicate<Parent> toPredicate(final ExampleInterface instance, final Method method) {
    try {
        final MethodHandle handle = LOOKUP.unreflect(method);
        final MethodType signature = MethodType.methodType(boolean.class, method.getParameters()[0].getType());

        final CallSite callSite = LambdaMetafactory.metafactory(
            LOOKUP,
            "test",
            MethodType.methodType(Predicate.class, instance.getClass()),
            signature,
            handle,
            signature
        );

        return (Predicate<Parent>) callSite.getTarget().invoke(instance);
    } catch (Throwable e) {
        e.printStackTrace();
        return null;
    }
}

My problem now is that when I call the Predicate's test method, an AbstractMethodError is thrown. When I use signature with (boolean.class, Parent.class) I get a LambdaConversionException. Is it even possible to implement it that dynamically? If yes, how?

1

There are 1 best solutions below

9
On BEST ANSWER

Since you know the target interface type is Predicate you could use a lambda expression as well:

private Predicate<Parent> toPredicate(final ExampleInterface instance, final Method method) {
    final MethodHandle handle = LOOKUP.unreflect(method)
        .bindTo(instance)
        .asType(MethodType.methodType(boolean.class, Parent.class);
    
    return parent -> {
        try {
            return (boolean) handle.invokeExact(parent);
        } catch (Throwable t) {
            throw new RuntimeException("Should not happen", t);
        }
    };
}

Using LambdaMetafactory is only really beneficial in specific cases where a particular call site only sees 1 or 2 implementations of the interface. Also note that every time you create a metafactory, a new class is generated (unless it can be loaded from the CDS archive), which has its own costs associated with it.

Also, depending on the Java version you are using, these classes are strongly tied to the defining class loader. This started when the implementation was switched to hidden classes in Java 15. So, e.g. if the defining class loader is the application class loader, the classes are in practice never unloaded, and this can lead to metaspace exhaustion if metafactory is called a lot. (for regular use through invokedynamic the resolved instruction keeps the class alive any way, and strongly tying the class to the class loader saves memory elsewhere).

The thing you're getting wrong is the interface method type. It should be the erasure of the test method in Predicate:

final MethodHandle handle = LOOKUP.unreflect(method);

final CallSite callSite = LambdaMetafactory.metafactory(
    LOOKUP,
    "test",
    MethodType.methodType(Predicate.class, instance.getClass()),
    MethodType.methodType(boolean.class, Object.class),
    handle,
    MethodType.methodType(boolean.class, Parent.class)
);