使用 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);
}
}
这里我希望发生以下情况:
- 如果我用处理程序的基本版本调用
callIt()
或 handle()
,它会照常调用。
- 如果我使用 "specialized" 简化版本的处理程序 class 调用它们,我希望它会将调用转移到我提供的简化版本。
这仅部分有效:
- 使用
Simple
/Complicated
组合,它有效:调用 Simple
的 complicated(int)
方法打印给定的参数,然后调用 simple()
方法,它又表示为 lambda。
- 然而,我所追求的组合是将
EventHandler<ActionEvent>
表示为一个 lambda(甚至可能是空的),它形成一个 Clickable
,handle()
称之为 onClick()
. (请不要对名称感到困惑;这是一个相当有历史意义的界面,我将对其进行改进,但不会完全改变。)在这种情况下,它不起作用。
这是我得到的堆栈跟踪:
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 版本
我正在尝试在 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);
}
}
这里我希望发生以下情况:
- 如果我用处理程序的基本版本调用
callIt()
或handle()
,它会照常调用。 - 如果我使用 "specialized" 简化版本的处理程序 class 调用它们,我希望它会将调用转移到我提供的简化版本。
这仅部分有效:
- 使用
Simple
/Complicated
组合,它有效:调用Simple
的complicated(int)
方法打印给定的参数,然后调用simple()
方法,它又表示为 lambda。 - 然而,我所追求的组合是将
EventHandler<ActionEvent>
表示为一个 lambda(甚至可能是空的),它形成一个Clickable
,handle()
称之为onClick()
. (请不要对名称感到困惑;这是一个相当有历史意义的界面,我将对其进行改进,但不会完全改变。)在这种情况下,它不起作用。
这是我得到的堆栈跟踪:
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 版本