Java 动态型铸造
我正在玩一个Java 8事件总线,其中Java 动态型铸造,java,Java,我正在玩一个Java 8事件总线,其中register方法接受一个事件类和一个Java 8函数引用,类似于:- class SomeSubscriber { SomeSubscriber(EventBus eventBus) { eventBus.register(MyEvent.class, this::onMyEvent); eventBus.register(SomeOtherEvent.class, this::onSomeOtherEvent);
register
方法接受一个事件类和一个Java 8函数引用,类似于:-
class SomeSubscriber {
SomeSubscriber(EventBus eventBus) {
eventBus.register(MyEvent.class, this::onMyEvent);
eventBus.register(SomeOtherEvent.class, this::onSomeOtherEvent);
eventBus.register(YetAnotherEvent.class, this::onYetAnotherEvent);
}
private void onMyEvent(MyEvent e) {
... do something with the MyEvent ...
}
private void onSomeOtherEvent(SomeOtherEvent e) {
... do something with the SomeOtherEvent ...
}
private void onYetAnotherEvent (YetAnotherEvent e) {
... do something with the YetAnotherEvent ...
}
}
发布者只需发布到同一总线:-
class SomePublisher {
SomePublisher(EventBus eventBus) {
eventBus.post(new MyEvent(...));
}
}
我的(目前非常简单)EventBus
目前看起来如下:-
public class EventBus {
private final Map<Class<? extends EventBase>, List<Handler<? extends EventBase>>> subscribers;
public EventBus() {
subscribers = new HashMap<>();
}
public <T extends EventBase> void register(Class<? extends EventBase> eventClass, Handler<T> handler) {
List<Handler<? extends EventBase>> typeSubs =
subscribers.computeIfAbsent(
eventClass,
(e) -> new ArrayList<Handler<? extends EventBase>>());
typeSubs.add(handler);
}
public <T extends EventBase> void post(T event) {
List<Handler<? extends EventBase>> typeSubs = subscribers.get(event.getClass());
for (Handler<? extends EventBase> handler : typeSubs) {
handler.handleEvent((? extends EventBase)event.getClass().asSubclass(event.getClass()));
}
}
}
公共类事件总线{
private final Map一个似乎有效的选项是使事件库看起来像这样
public interface EventBase {
default <T extends EventBase> void accept(Handler<T> handler) {
handler.handleEvent((T) this);
}
}
公共接口事件库{
默认的void接受(处理程序){
handleEvent((T)this);
}
}
然后让你的事件总线像这样通过它发回
public <T extends EventBase> void post(T event) {
List<Handler<? extends EventBase>> typeSubs = subscribers.get(event.getClass());
for (Handler<? extends EventBase> handler : typeSubs) {
event.accept(handler);
}
}
公共作废帖子(T事件){
对问题的深入了解
不幸的是,我认为您试图做的可能是不可能的。问题在于:您知道给处理程序的事件有一个与处理程序可以接受的类兼容的类,但这只是因为您从您设置的映射中检索了处理程序,该映射通过它们的vent类型。但是,编译器不理解此逻辑。从它的角度来看,您试图接受一个任意处理程序,该处理程序需要编译器不知道的特定事件基扩展,并给它一个可能适合或可能不适合预期类型的事件。从以下问题考虑可能会有所帮助:即使我可以神奇地更改代码,以便在编译器中传递类型为MyEvent
的事件时,在将其交给处理程序之前将其强制转换为MyEvent
,编译器如何知道处理程序可以接受MyEvent
?编译器所知道的只是处理程序接受了某个sp从EventBase扩展而来
查看上面的粗体问题,可以更清楚地了解为什么任何反射解决方案都会失败。您可以使用反射将事件强制转换为其相应的类,但编译器不知道该类是否适用于处理程序
变通办法
我推荐Iscoughlin在EventBase
中使用default
方法来翻转依赖关系,这样您就可以为事件提供处理程序,而不是为处理程序提供事件。这比我将要建议的解决方法更适合您的模型。但为了完整性起见,这里有另一种方法(不太干净)解决方案:
public interface Handler {
public void handleEvent(EventBase event);
}
巴士:
public class EventBus {
private final Map<Class<? extends EventBase>, List<Handler>> subscribers;
public EventBus() {
subscribers = new HashMap<>();
}
public void register(Class<? extends EventBase> eventClass, Handler handler) {
List<Handler> typeSubs =
subscribers.computeIfAbsent(
eventClass,
(e) -> new ArrayList<Handler>());
typeSubs.add(handler);
}
public <T extends EventBase> void post(T event) {
List<Handler> typeSubs = subscribers.get(event.getClass());
for (Handler handler : typeSubs) {
handler.handleEvent(event);
}
}
}
通过简单地使用反射,我成功地实现了我最初的目标。出于所有人的兴趣,代码如下。我遇到的关键问题(铸造事件对象)是通过方法来解决的。invoke(object obj,object…args)
将对象作为参数-无需铸造
package experiments.eventbus;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventBus {
private static String handlerMethodName;
private final Map<Class<?>, List<HandlerMethod>> handlerMethods;
static {
Class<Handler> c = Handler.class;
handlerMethodName = c.getMethods()[0].getName();
}
public EventBus() {
handlerMethods = new HashMap<>();
}
public <T> void register(Class<T> eventClass, Handler<T> handler) {
List<HandlerMethod> handlers = handlerMethods.computeIfAbsent(eventClass, (e) -> new ArrayList<HandlerMethod>());
Method method = lookupMethod(handler);
handlers.add(new HandlerMethod(handler, method));
}
public <T> void post(T event) {
List<HandlerMethod> handlers = handlerMethods.get(event.getClass());
if (handlers == null) {
return;
}
for (HandlerMethod handler : handlers) {
handler.invoke(event);
}
}
private <T> Method lookupMethod(Handler<T> handler) {
Method[] methods = handler.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(handlerMethodName)) {
return method;
}
}
// This isn't possible, but need to satisfy the compiler
throw new RuntimeException();
}
/**
* Tuple of a Handler<?> (functional interface provided by subscriber) and a {@link Method} to that function (that
* can be invoked with an "Object" event, i.e. Method#invoke takes an Object.
*/
private static class HandlerMethod {
private final Handler<?> handler;
private final Method method;
HandlerMethod(Handler<?> handler, Method method) {
this.handler = handler;
this.method = method;
}
void invoke(Object event) {
try {
method.invoke(handler, event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package-experiments.eventbus;
导入java.lang.reflect.Method;
导入java.util.ArrayList;
导入java.util.HashMap;
导入java.util.List;
导入java.util.Map;
公共类活动巴士{
私有静态字符串handlerMethodName;
private final MapIf I knowledge,而不是?扩展EventBase
,为什么不使用MyEvent
作为类型?因为总线设计为能够处理许多不同类型事件的发布和订阅,而不仅仅是MyEvent
。硬编码对该类的转换将大大挫败这种总线的se。@augray,没错。是的,谢谢,那会有用的-我已经忘记了defender方法-我还是Java 8的新手。这是一种双重发送/访问方法?如果可以的话,我仍然希望不再需要EventBase
。不知何故:)Java仍在使用擦除类型系统,因此它更多的是关于您可以强制为编译器提供什么,而不是什么可能“工作”。它是一个双重分派(类似于访问者),使用defender方法可以获得,这在java8中非常有趣。这绝对是一种有趣的方法。它肯定会“更慢”可能是一个数量级——我们讨论的是纳秒级,所以取决于你的用例,它可能真的不重要。方法句柄会让你将差异分开,所以同样,依赖于用例的YMMV。
package experiments.eventbus;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventBus {
private static String handlerMethodName;
private final Map<Class<?>, List<HandlerMethod>> handlerMethods;
static {
Class<Handler> c = Handler.class;
handlerMethodName = c.getMethods()[0].getName();
}
public EventBus() {
handlerMethods = new HashMap<>();
}
public <T> void register(Class<T> eventClass, Handler<T> handler) {
List<HandlerMethod> handlers = handlerMethods.computeIfAbsent(eventClass, (e) -> new ArrayList<HandlerMethod>());
Method method = lookupMethod(handler);
handlers.add(new HandlerMethod(handler, method));
}
public <T> void post(T event) {
List<HandlerMethod> handlers = handlerMethods.get(event.getClass());
if (handlers == null) {
return;
}
for (HandlerMethod handler : handlers) {
handler.invoke(event);
}
}
private <T> Method lookupMethod(Handler<T> handler) {
Method[] methods = handler.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(handlerMethodName)) {
return method;
}
}
// This isn't possible, but need to satisfy the compiler
throw new RuntimeException();
}
/**
* Tuple of a Handler<?> (functional interface provided by subscriber) and a {@link Method} to that function (that
* can be invoked with an "Object" event, i.e. Method#invoke takes an Object.
*/
private static class HandlerMethod {
private final Handler<?> handler;
private final Method method;
HandlerMethod(Handler<?> handler, Method method) {
this.handler = handler;
this.method = method;
}
void invoke(Object event) {
try {
method.invoke(handler, event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}