Java 动态型铸造

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

我正在玩一个Java 8事件总线,其中
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();
            }
        }
    }
}