使用 Lambda 方法简化事件在 Java 中不起作用

Simplifying event with a Lambda method doesn't work in Java

我正在尝试在 Java 8 中提供 EventHandler<ActionEvent> 的更简单版本 JavaFX。

最终版本应该是这样的

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();
        }
    }
}

有了这个,我试图以更简单的方式创建事件处理程序:它们不将事件作为参数,它们关心隐藏自己。

为了演示,我创建了一个测试套件来重现我遇到的问题:

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);
    }
}

这里我希望发生以下情况:

这仅部分有效:

这是我得到的堆栈跟踪:

java.lang.AbstractMethodError: Method test/BastelTest$$Lambda.handle(Ljavafx/event/Event;)V is abstract
    at test.BastelTest$$Lambda/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...]

为什么不起作用?

(如果我不清楚情况,请告诉我。)

通常,您不应该使用普通的 Java 操作来引发 AbstractMethodError(或任何类型的 LinkageError)。所以遇到它通常是编译器损坏或环境变化不兼容的标志,即 class 链接到与编译时看到的不同版本的 class。

我们在这里看到的是 interface 中缺少的 桥接方法 。当泛型类型被可具体化类型或使用不同下限重新声明类型变量的类型扩展时,继承方法的原始类型签名可能会更改,并且需要具有旧原始签名并委托给方法的桥接方法新签名。

在你的代码中,可具体化的类型Clickable扩展了泛型类型EventHandler<ActionEvent>并且在字节码级别有两个方法,void handle(ActionEvent)void handle(Event),后者需要委托给前者的桥接方法。

从 Java 8 开始,这些桥接方法在 interface 中实现(因为现在非 abstract 方法是可能的),从所有实现中移除实现它的负担classes(大大简化了为 lambda 表达式生成 classes 的过程)。

从堆栈跟踪中我们可以看到方法 BastelTest.handle 正在尝试在编译时类型为 EventHandler<ActionEvent> 的实例上调用 handle 方法,最终将在原始方法 handle(Ljavafx/event/Event;)V,但 lambda 实例缺少所需的桥接方法,这意味着它在应该继承它的接口中也缺少。

对于像您这样的测试套件,其中所有链接的 classes 都是嵌套的 classes,因此编译在一起,不太可能得到不匹配的版本(尽管并非不可能)。另一种可能性是您使用的是带有 bug 436350, “Missing bridge method in interface results in AbstractMethodError”.

的旧 Eclipse 版本