能否对以下EventBus类进行更优的泛型化改造?
你的核心问题是在容纳多类型事件和处理器的EventBus中,由于泛型擦除不得不使用Object作为通配类型,导致未检查转换警告,同时又不想依赖Guava那种基于反射的实现(避免注销时的性能损耗)。我来分享几个重构思路,既能保持类型安全,又能消除警告。
问题根源分析
当前实现中,你直接将BiConsumer<Handler, EventType>强制转换为BiConsumer<Object, Object>,这确实会触发警告——因为编译器无法验证这个转换的安全性。而如果改用BiConsumer<?, ?>,又会在post时因为Object无法匹配通配符捕获类型而报错,本质上是因为你把类型信息分散在了Map的键和值中,编译器无法关联它们的类型一致性。
最优重构方案:封装类型安全的订阅者
我们可以创建一个泛型内部类来封装处理器和对应的消费函数,把类型安全的边界收拢到这个类中,避免在公共API中暴露未检查转换。
步骤1:创建泛型Subscription类
这个类会持有匹配的处理器和消费函数,并在内部处理安全的类型转换:
private static class Subscription<HandlerT, EventT> { private final HandlerT handler; private final BiConsumer<HandlerT, EventT> consumer; public Subscription(HandlerT handler, BiConsumer<HandlerT, EventT> consumer) { this.handler = handler; this.consumer = consumer; } @SuppressWarnings("unchecked") public void dispatchEvent(Object event) { // 这里的转换是安全的:register时已经确保event的类型是EventT consumer.accept(handler, (EventT) event); } }
步骤2:修改EventBus的存储结构
把原来的Map<Class<?>, Map<Object, BiConsumer<Object, Object>>>改成存储Subscription对象:
private final Map<Class<?>, Map<Object, Subscription<?, ?>>> mSubscribers = new HashMap<>();
步骤3:更新register方法
现在注册时直接创建Subscription对象,不需要任何强制转换,编译器能完全验证类型一致性:
public <HandlerT, EventT> void register(Class<EventT> type, HandlerT handler, BiConsumer<HandlerT, EventT> function) { var subscribers = mSubscribers.computeIfAbsent(type, x -> new LinkedHashMap<>()); subscribers.put(handler, new Subscription<>(handler, function)); }
步骤4:简化post方法
post时只需要调用Subscription的dispatchEvent方法,内部已经处理了类型转换:
void post(Object event) { var type = event.getClass(); var eventSubscribers = mSubscribers.get(type); if (eventSubscribers == null || eventSubscribers.isEmpty()) { throw new NoSuchElementException("No handler registered for this event"); } for (var entry : eventSubscribers.entrySet()) { entry.getValue().dispatchEvent(event); } }
为什么这个方案更好?
- 完全消除未检查警告:唯一的转换在
Subscription内部,且我们能通过register时的Class<EventT>参数确保转换的安全性,所以添加@SuppressWarnings是合理且安全的。 - 保持原有API的简洁性:调用方的代码(比如你的
State类)不需要做任何修改,依然可以用mEventBus.register(MyEvent.class, this, State::onMyEvent)这种方式注册。 - 避免反射开销:和你原来的实现一样,直接调用
BiConsumer的accept方法,性能和原有方案一致,同时比Guava的反射调用更高效。
当前方案是否是泛型能实现的最优解?
如果你的核心需求是不依赖反射、保持类型安全、支持多类型事件/处理器,那么这个封装方案已经是泛型能做到的最优解了。因为Java的泛型是擦除式的,运行时无法保留泛型类型信息,我们必须在某个地方做一次安全的类型转换——把这个转换放在内部的Subscription类中,是最优雅且安全的方式,既隐藏了实现细节,又保证了外部API的类型安全。
内容的提问来源于stack exchange,提问作者superjugy

