我正在尝试为Java创建GUI库,并计划通过使用Java 8 lambda表达式使事件驱动来使其高度可扩展。
我目前有两种类型的事件。第一个GuiEvent不是通用的。但是,第二个确实指定了一个通用参数:ComponentEvent<T extends GuiComponent<?, ?>>
,以便以后可以使用以下lambda捕获事件:
button.onEvent((event) -> {
// event.component is supposed to be of the type ComponentEvent<Button>
System.out.println("Test button pressed! " + ((Button) event.component).getText());
}, ActionEvent.class);
onEvent
如下所示:public <EVENT extends ComponentEvent<?>> O onEvent(EventListener<EVENT> listener, Class<EVENT> clazz) {
return onEvent(listener, clazz, Side.CLIENT);
}
它是
GuiComponent<O extends GuiComponent<O, T>, T extends NativeGuiComponent> ...
的一部分,在我们的案例中,作为我们正在侦听的组件是一个按钮,可以简化为Button<Button, NativeButton>
,因此派生的O
类型为Button
。如预期的那样,它没有参数化
ComponentEvent<?>
,因此event.component
的类型正确是GuiComponent<?, ?>
,这使得强制转换成为必需。现在,我尝试了几种方法来以此开始参数化
ComponentEvent
:public <EVENT extends ComponentEvent<O>> O onEvent(EventListener<EVENT> listener, Class<EVENT> clazz) {
return onEvent(listener, clazz, Side.CLIENT);
}
这会恶化情况,因为它现在显示编译警告:
Type safety: Unchecked invocation onEvent(EventListener<ComponentEvent.ActionEvent>,
Class<ComponentEvent.ActionEvent>) of the generic method
onEvent(EventListener<EVENT>, Class<EVENT>) of type
GuiComponent<Button,NativeButton>
出于某些奇怪的原因,将类变量更改为
ActionEvent.class.asSubclass(Button.class)
确实可以编译,为我提供了event.component
的正确类型,但是由于稍后发生ClassCastException,它当然崩溃了...编辑:这是涉及的所有类定义的列表:
基类GuiComponent:
public abstract class GuiComponent<O extends GuiComponent<O, T>,
T extends NativeGuiComponent> implements Identifiable,
EventListener<GuiEvent>, PacketHandler {
private SidedEventBus<ComponentEvent<?>> eventListenerList = new SidedEventBus<ComponentEvent<?>>(this::dispatchNetworkEvent);
...
public <EVENT extends ComponentEvent<?>> O onEvent(EventListener<EVENT> listener, Class<EVENT> clazz, Side side) {
eventListenerList.add(listener, clazz, side);
return (O) this;
}
public <EVENT extends ComponentEvent<?>> O onEvent(EventListener<EVENT> listener, Class<EVENT> clazz) {
return onEvent(listener, clazz, Side.CLIENT);
}
...
按钮,参数化
public class Button extends GuiComponent<Button, NativeButton> {
GUI示例
Gui testGUI = new Gui("testgui")
.add(new Button("testbutton2", "I'm EAST")
.setMaximumSize(Integer.MAX_VALUE, 120)
.onEvent((event) -> {
System.out.println("Test button pressed! " + Side.get());
}, ActionEvent.class), Anchor.EAST)
.add(new Button("testbutton3", "I'm CENTER"))
.add(new Button("testbutton4", "I'm SOUTH"), Anchor.SOUTH)
.add(new GuiContainer("container").setLayout(new FlowLayout())
.add(new Button("testbutton5", "I'm the FIRST Button and need lots of space"))
.add(new Label("testlabel1", "I'm some label hanging around").setBackground(new Background(Color.white)))
.add(new Button("testbutton7", "I'm THIRD"))
.add(new Button("testbutton8", "I'm FOURTH"))
, Anchor.NORTH)
.onGuiEvent((event) -> {
System.out.println("Test GUI initialized! " + event.player.getDisplayName() + " " + event.position);
}, BindEvent.class)
.onGuiEvent((event) -> {
System.out.println("Test GUI closed!");
}, UnBindEvent.class);
guiFactory.registerGui(testGUI, id);
组件和ActionEvent:
public abstract class ComponentEvent<T extends GuiComponent<?, ?>> extends CancelableEvent implements SidedEvent {
public final T component;
public ComponentEvent(T component) {
this.component = component;
}
public static class ActionEvent<T extends GuiComponent<?, ?>> extends ComponentEvent<T> {
public ActionEvent(T component) {
super(component);
}
}
...
最佳答案
由于您没有遵循Bean事件模式,因此可以考虑进行另一种修改:仅放弃侦听器必须具有一个参数的限制。通过将事件源分开,并提供源事件和实际事件作为两个参数,您可以摆脱偶数类型的通用签名。
public interface EventListener<E,S> {
void processEvent(E event, S source);
}
public abstract class ComponentEvent extends CancelableEvent implements SidedEvent {
// not dealing with source anymore…
}
public abstract class
GuiComponent<O extends GuiComponent<O, T>, T extends NativeGuiComponent> {
public <EV extends ComponentEvent> O onEvent(
EventListener<EV,O> listener, Class<EV> clazz) {
return onEvent(listener, clazz, Side.CLIENT);
}
// etc
}
如果事件类型不是通用类型,则使用
Class
token 将满足通用侦听器注册,而不会生成警告。您的听众只会变得稍微复杂一点:
.onEvent((event,source) -> {
System.out.println("Test button pressed! " + Side.get());
}, ActionEvent.class)
要么
.onGuiEvent((event,source) -> {
System.out.println("Test GUI initialized! " + event.player.getDisplayName() + " " + event.position);
}, BindEvent.class)
事件通知机制必须携带两个值而不是一个值,但是我认为这只影响框架中心部分中的一小段代码。如果您必须重新指定事件的目标,那么甚至不必克隆事件即可容纳不同的源引用,这甚至会使事情变得更容易。
关于java - Java嵌套泛型,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28818604/