Java嵌套泛型

Vic*_*Vic 3 java generics lambda java-8

我正在尝试为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);
Run Code Online (Sandbox Code Playgroud)

onEvent 看起来如下:

public <EVENT extends ComponentEvent<?>> O onEvent(EventListener<EVENT> listener, Class<EVENT> clazz) {
    return onEvent(listener, clazz, Side.CLIENT);
}
Run Code Online (Sandbox Code Playgroud)

并且它是的一部分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);
}
Run Code Online (Sandbox Code Playgroud)

这使情况恶化,因为它现在显示编译警告:

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>
Run Code Online (Sandbox Code Playgroud)

出于一些奇怪的原因,将类变量更改为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);
}
...
Run Code Online (Sandbox Code Playgroud)

按钮,参数化

public class Button extends GuiComponent<Button, NativeButton> {
Run Code Online (Sandbox Code Playgroud)

示例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);
Run Code Online (Sandbox Code Playgroud)

组件和动作事件:

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);
    }
}
...
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 5

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

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

在这种情况下,典型的方法是使用更丰富的类型捕获机制. 番石榴TypeToken是一种实施方式:

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

onEvent在哪里:

public <E extends ComponentEvent<O>> O onEvent(EventListener<E> listener, TypeToken<E> eventType) {
    //register listener
}
Run Code Online (Sandbox Code Playgroud)

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

编辑

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

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);
}
Run Code Online (Sandbox Code Playgroud)

编辑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
}
Run Code Online (Sandbox Code Playgroud)

  • 即使它不起作用,这也是信息丰富和令人敬畏的.直到现在我才真正达到TypeTokens的目的. (3认同)