泛型映射:读写

Tag*_*gas 5 java generics collections events event-handling

有许多不同的事件,都实现了相同的接口:

interface Event {}
class FooEvent implements Event {}
class BarEvent implements Event {}
Run Code Online (Sandbox Code Playgroud)

每个事件都有一个专用的处理程序:

interface EventHandler<T extends Event> {
    void handle(T event);
}
class FooEventHandler implements EventHandler<FooEvent> {
    @Override
    public void handle(FooEvent event) { }
}
class BarEventHandler implements EventHandler<BarEvent> {
    @Override
    public void handle(BarEvent event) { }
}
Run Code Online (Sandbox Code Playgroud)

所有事件处理程序都创建一次并添加到地图中。每当事件发生时,都应使用此映射来查找正确的事件处理程序。

class Main {
    Map<Class<? extends Event>, EventHandler<? extends Event>> eventHandlerRegistry = Map.of(
            FooEvent.class, new FooEventHandler(),
            BarEvent.class, new BarEventHandler()
    );

    void handleEvent(Event event) {
        EventHandler<? extends Event> handler = this.eventHandlerRegistry.get(event.getClass());
        handler.handle(event); // DOES NOT COMPILE: needed=capture<? extends Event>, given=Event
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,最后一行不能编译。我可以通过像这样省略 EventHandler 的类型参数来编译它:

EventHandlerhandler = this.eventHandlerRegistry.get(event.getClass());
handler.handle(event); // WARNING: unchecked call to 'handle(T)' as a member of raw type 'EventHandler'
Run Code Online (Sandbox Code Playgroud)

但这感觉不太对劲......我知道PECS,但我觉得有点被困,因为我生产和消费我的 EventHandlers。

我怎样才能干净利落地实现这一点?

ern*_*t_k 4

如果要在同一个映射中混合使用(通用)处理程序,则无法实现类型安全。据我所知,使代码类型安全的方法是摆脱 ; 上的泛型类型参数EventHandler。但这是您要避免的一件事。

如果您可能会牺牲类型安全性,并且知道您的处理程序将始终与指定的类匹配,那么您可以尝试以下操作:

private <T extends Event> EventHandler<T> getHandler(Class<?> eventClass) {
    return (EventHandler<T>) 
              this.eventHandlerRegistry.get(eventClass); //Unchecked cast
}
Run Code Online (Sandbox Code Playgroud)

然后使你的handleEvent方法通用:

<T extends Event> void handleEvent(T event) {
    EventHandler<T> handler = this.getHandler(event.getClass());
    handler.handle(event);
}
Run Code Online (Sandbox Code Playgroud)

然后,此方法将成功编译,而不会发出警告。您唯一需要确保的是eventHandlerRegistry永远不会被这样的东西污染:

put(FooEvent.class, new BarEventHandler())); //this can happen
Run Code Online (Sandbox Code Playgroud)