繁体   English   中英

Java嵌套泛型

[英]Java nested generics

我正在尝试为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> ...一部分GuiComponent<O extends GuiComponent<O, T>, T extends NativeGuiComponent> ...在我们的例子中,因为我们正在监听的组件是一个按钮,可以简化为Button<Button, NativeButton>和所以派生类型OButton

正如预期的那样,它不会参数化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);

组件和动作事件:

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

好的,所以筛选这个模型和解释,我认为问题的关键是:

  • 事件本身在源组件类型上键入。
  • ActionEvent.class不够丰富,无法表示这一点(因为它不会通知运行时/编译器事件的组件类型)。 您需要传递一个Class<ActionEvent<Button>>但这对于类文字是不可能的。

在这种情况下,典型的方法是使用更丰富的类型捕获机制。 Guava的TypeToken是一个实现:

button.onEvent((event) -> {
    System.out.println("Test button pressed! " + event.component.getText());
}, new TypeToken<ActionEvent<Button>>(){});

onEvent在哪里:

public <E extends ComponentEvent<O>> O onEvent(EventListener<E> listener, TypeToken<E> eventType) {
    //register listener
}

此模式捕获Event的完全解析的泛型类型,并使其可在运行时进行检查。

编辑

如果您担心为调用者创建TypeToken的负担,有很多方法可以将很多复杂性转移到库代码中。 这是一个例子:

button.onEvent((event) -> {
    System.out.println("Test button pressed! " + event.component.getText());
}, ActionEvent.type(Button.class));

//then in ActionEvent
public static <C> TypeToken<ActionEvent<C>> type(Class<C> componentType) {
   return new TypeToken<ActionEvent<C>>(){}.where(new TypeParameter<C>(){}, componentType);
}

编辑2

当然,最简单的方法是首先停止在组件类型上键入事件。 您可以通过将两个参数传递给处理程序来完成此操作:

button.onEvent((event, component) -> {
    System.out.println("Test button pressed! " + component.getText());
}, ActionEvent.class);

public <E extends ComponentEvent> O onEvent(EventListener<E, O> listener, Class<E> eventType) {
    //register listener
}

由于您没有遵循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令牌将满足通用侦听器注册而不会生成警告。

你的听众变得稍微复杂一点:

.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)

事件通知机制必须携带两个值而不是一个,但我认为这只会影响框架中心部分的一小部分代码。 在您必须重新定位事件的情况下,它甚至会使事情变得更容易,因为您不必克隆事件以适应不同的源引用...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM