I am trying to provide a simpler version of an EventHandler<ActionEvent>
in Java 8 with JavaFX.
The final version is supposed to look like
package dialogutil;
import org.controlsfx.dialog.Dialog;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@FunctionalInterface
public interface Clickable extends EventHandler<ActionEvent> {
public static Clickable EMPTY = () -> {};
public void onClick();
@Override
public default void handle(ActionEvent event) {
this.onClick();
if (event != null && event.getSource() != null) {
((Dialog)event.getSource()).hide();
}
}
}
With this, I am trying to create event handlers in a simpler way: they don't take the event as a parameter, and they care about hiding themselves.
For the sake of demonstration, I created a test suite to reproduce the problem I have with that:
package test;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import org.junit.Test;
public class BastelTest {
/**
* Interface Complicated is called with a value.
*/
@FunctionalInterface
interface Complicated {
void complicated(int value);
}
/**
* Interface Simple is called without a value.
*/
@FunctionalInterface
interface Simple extends Complicated {
void simple();
/**
* The value given is printed and then the call is deflected to the simple method given.
*/
@Override
default void complicated(int value) {
System.out.println("Swallowing the " + value);
simple();
}
}
/**
* This is in order to try the Complicated/Simple interface.
* The given {@link Complicated} is called with a 42.
* It can be a {@link Simple} as well; in this case the call is deflected.
* @param x
*/
private void callIt(Complicated x) {
x.complicated(42);
}
/**
* This is the interface I am indeed working on.
* Here the deflection doesn't work; instead, I get an AbstractMethodError.
*/
@FunctionalInterface
public interface Clickable extends EventHandler<ActionEvent> {
public static Clickable EMPTY = () -> {};
public void onClick();
@Override
public default void handle(ActionEvent event) {
System.out.println("Simplifying the call:");
this.onClick();
System.out.println("Call simplified.");
}
}
private void handle(EventHandler<ActionEvent> x) {
System.out.println("Handling null event via " + x);
x.handle(null);
System.out.println("Handling nonnull event via " + x);
x.handle(new ActionEvent());
}
@Test
public void testFunc() {
callIt(x -> System.out.println("Complicated with " + x));
callIt((Simple) () -> System.out.println("Called simple."));
Clickable c = () -> System.out.println("Hdl3");
c.handle(null);
handle(x -> System.out.println("Hdl1 " + x));
handle((Clickable)() -> System.out.println("Hdl2"));
handle(Clickable.EMPTY);
}
}
Here I expect the following to happen:
callIt()
or handle()
with the base version of the handler, it is called as usual.This works only partially:
Simple
/Complicated
combination, it works: calling the complicated(int)
method of a Simple
prints the given argument and then calls the simple()
method, which is in turn expressed as a lambda.EventHandler<ActionEvent>
as a lambda (possibly even empty) which forms a Clickable
whose handle()
calls this onClick()
. (Please don't be confused about the names; it is a quite historic interface which I am just going to improve, but not completely change.) In this case, it doesn't work.Here is the stack trace I get:
java.lang.AbstractMethodError: Method test/BastelTest$$Lambda$7.handle(Ljavafx/event/Event;)V is abstract
at test.BastelTest$$Lambda$7/25282035.handle(Unknown Source)
at test.BastelTest.handle(BastelTest.java:38)
at test.BastelTest.testFunc(BastelTest.java:69)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at [usual test case stuff...]
Why doesn't it work?
(If I am not clear enough about the situation, please tell me that.)
Generally, you shouldn’t be able to provoke an AbstractMethodError
(or any kind of LinkageError
) using ordinary Java operations. So encountering it is usually a sign of either, a broken compiler or an incompatibly changed environment, i.e. a class is linked against a different version of a class than it saw at compile-time.
What we see here, is a missing bridge method in an interface
. When a generic type is extended by either, a reifiable type or a type redeclaring a type variable using a different lower bound, the raw type signature of inherited methods may change and require a bridge method having the old raw signature and delegating to the method with the new signature.
In your code, the reifiable type Clickable
extends the generic type EventHandler<ActionEvent>
and has two methods at the byte code level, void handle(ActionEvent)
and void handle(Event)
, the latter requiring a bridge method delegating to the former.
Starting with Java 8, these bridge method are implemented in the interface
(as now non-abstract
methods are possible), removing the burden of implementing it from all implementation classes (greatly simplifying the process of generating classes for lambda expressions).
From the stack trace we can see that the method BastelTest.handle
is trying to invoke the handle
method on an instance with the compile-time type EventHandler<ActionEvent>
which will end up at the raw method handle(Ljavafx/event/Event;)V
, but the required bridge method is missing for the lambda instance, which implies that it is also missing in the interface from which it should be inherited.
For a test suite like yours, where all linked classes are nested classes and therefore compiled together, it’s unlikely to get mismatched versions (though not impossible). The other possibility is that you are using an older Eclipse version bearing the bug 436350, “Missing bridge method in interface results in AbstractMethodError”.