Despite having read all the documentation I'm aware of, I cannot resolve an issue with using lambdas to execute a method. To give a bit of background my use case is a plugin system. I'm using an annotation (@EventHandle) which can be assigned to any method. I use reflection and iterate through every method in the class and check if it has the annotation, if it does the method is added to a handler object (which is added to a list for processing every "tick"). Here is my handler class:
package me.b3nw.dev.Events;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.lang.invoke.*;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@Slf4j
public class Handler {
@Getter
private final Method method;
@Getter
private final EventHandle handle;
private final MethodHandles.Lookup lookup;
private final MethodHandle methodHandle;
private final EventHandler invoker;
public Handler(Method method, EventHandle handle) throws Throwable {
this.method = method;
log.info(method.getGenericReturnType() + "");
for(Type type : method.getParameterTypes()) {
log.info(type.getTypeName());
}
this.handle = handle;
this.lookup = MethodHandles.lookup();
this.methodHandle = lookup.unreflect(method);
log.info("" + methodHandle.type().toMethodDescriptorString());
this.invoker = (EventHandler) LambdaMetafactory.metafactory(lookup, "handle", MethodType.methodType(EventHandler.class), methodHandle.type(), methodHandle, methodHandle.type()).getTarget().invokeExact();
}
public void invoke(GameEvent evt) throws Throwable {
invoker.handle(evt);
}
}
In the current iteration of this class I'm casting straight to the functional interface EventHandler, source:
package me.b3nw.dev.Events;
@FunctionalInterface
public interface EventHandler {
boolean handle(GameEvent evt);
}
Currently I get the following error:
ERROR m.b.d.H.GamemodeHandler -
java.lang.AbstractMethodError: me.b3nw.dev.Events.Handler$$Lambda$3/1704984363.handle(Lme/b3nw/dev/Events/GameEvent;)Z
at me.b3nw.dev.Events.Handler.invoke(Handler.java:40) ~[classes/:na]
at me.b3nw.dev.Handlers.GamemodeHandler.userEventTriggered(GamemodeHandler.java:34) ~[classes/:na]
GamemodeHandler just calls the invoke method in Handler class.
So it outputs an AbstractMethodError when I cast straight to EventHandler and execute, when I don't cast it I get a different error which is:
java.lang.invoke.WrongMethodTypeException: expected ()EventHandler but found ()void
at java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:294) ~[na:1.8.0_45]
at java.lang.invoke.Invokers.checkExactType(Invokers.java:305) ~[na:1.8.0_45]
at me.b3nw.dev.Events.Handler.invoke(Handler.java:40) ~[classes/:na]
at me.b3nw.dev.Handlers.GamemodeHandler.userEventTriggered(GamemodeHandler.java:34) ~[classes/:na]
The modified Handler to reflect changes:
package me.b3nw.dev.Events;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.lang.invoke.*;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
@Slf4j
public class Handler {
@Getter
private final Method method;
@Getter
private final EventHandle handle;
private final MethodHandles.Lookup lookup;
private final MethodHandle methodHandle;
private final MethodHandle invoker;
public Handler(Method method, EventHandle handle) throws Throwable {
this.method = method;
log.info(method.getGenericReturnType() + "");
for(Type type : method.getParameterTypes()) {
log.info(type.getTypeName());
}
this.handle = handle;
this.lookup = MethodHandles.lookup();
this.methodHandle = lookup.unreflect(method);
log.info("" + methodHandle.type().toMethodDescriptorString());
this.invoker = LambdaMetafactory.metafactory(lookup, "handle", MethodType.methodType(EventHandler.class), methodHandle.type(), methodHandle, methodHandle.type()).getTarget();
}
public void invoke(GameEvent evt) throws Throwable {
invoker.invokeExact();
}
}
This class has a method which is annotated and should implement the signature of the functional interface but.. clearly not :( Here's the class:
package me.b3nw.dev.Gamemode;
import lombok.extern.slf4j.Slf4j;
import me.b3nw.dev.Events.EventHandle;
import me.b3nw.dev.Events.GameEvent;
@Slf4j
public class Vanilla extends Gamemode {
public void testMethod() {
}
@EventHandle(type = EventHandle.Type.NICKANNOUNCE)
public boolean testMethod2(GameEvent evt) {
log.info("Fuck yeah!"/* + evt*/);
return true;
}
}
How do I go about fixing this, am I using lambdas completely wrong here?
Thank you.
If you looked at your log output you noticed that your target method signature looks like
(Lme/b3nw/dev/Events/Vanilla;Lme/b3nw/dev/Events/GameEvent;)Z
, in other words, since your target method is an instance method, it needs an instance of its class (i.e.Vanilla
) as first argument.If you don’t provide an instance at lambda creation time but pass the target method’s signature as functional signature, the created lambda instance will have a method like
which doesn’t match the real
interface
methodwhich you are trying to invoke. Therefore you get an
AbstractMethodError
. TheLambdaMetaFactory
is for compiler generated code in the first place and doesn’t perform expensive checks, i.e. doesn’t try to determine the functional interface method to compare it with the provided signature.So what you have to do is to provide the instance on which the target method ought to be invoked:
Of course, you also have to adapt the code which gathers the annotated methods to pass through the instance which you are working on.
Note that this all refers to your first version. I couldn’t get the point of your “modified Handler”.