动态类型转换

时间:2015-06-05 14:59:35

标签: java

我正在使用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()));
        }
    }
}

我的问题在于post方法 - 它需要以某种方式动态地转换为目标所需的内容,即上例中的MyEvent。我似乎无法编写post方法来转换事件或以其他方式满足编译器。即使传入的事件(来自SomePublisherMyEvent个实例,post方法也无法保留该信息。

我需要做些什么来实现这种动态类型转换。反射? MethodHandles?

另外,我理想情况下不需要EventBase标记界面。

由于

PS。现有的活动总线图书馆已经足够好了#34;但是使用lambda这会让我感到很兴奋并使我感到紧凑和直接。

3 个答案:

答案 0 :(得分:2)

似乎有用的选项是使事件库看起来像这样

public interface EventBase {
    default <T extends EventBase> void accept(Handler<T> handler) {
        handler.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);
    }
}

虽然事件基础有点乱,所以我不确定我对此感觉如何。但它会根据您的示例代码通过测试。

修改: 我稍微简化了EventBase,但我似乎无法摆脱未经检查的演员。

答案 1 :(得分:1)

洞察问题

不幸的是,我认为您尝试做的事情可能无法实现。麻烦在于:知道你给予处理程序的事件有一个类与处理程序可以接受的类兼容,但只是因为你检索了处理程序这一事实来自您设置的Map,它按事件类型保存处理程序。但是,编译器并不理解这种逻辑。从它的角度来看,你试图采用一个任意的处理程序,它期望一些特定的事件基础扩展,编译器不知道并给它一个可能或可能的事件不符合预期的类型。也许根据问题进行思考是有帮助的:即使我可以神奇地更改代码,以便在编译器中传递类型为MyEvent的事件时将其转换为MyEvent在将它提供给处理程序之前,编译器如何知道处理程序可以接受MyEvent所有编译器都知道处理程序接受从EventBase扩展的特定内容。

观察上面的粗体问题,为什么任何反射解决方案都会失败变得更加明确。您可以使用反射将事件转换为适当的类,但编译器不知道该类是否适用于处理程序。

解决方法

我推荐Iscoughlin在default中使用EventBase方法的解决方案来翻转依赖项,以便为事件提供处理程序而不是给处理程序事件。与我即将建议的解决方法相比,这更适合您的模型。但为了完整起见,这是另一个(公认的不太干净)解决方案:

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

订阅者看起来像这样(这里的解决方案不太理想):

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(EventBase e) {
        MyEvent event = (MyEvent)e;
        //... do something with the MyEvent ...
    }

    private void onSomeOtherEvent(EventBase e) {
        SomeOtherEvent event = (SomeOtherEvent)e;
        //... do something with the SomeOtherEvent ...
    }

    private void onYetAnotherEvent (EventBase e) {
        YetAnotherEvent event = (YetAnotherEvent)e;
        //... do something with the YetAnotherEvent ...
    }
}

答案 2 :(得分:0)

我通过简单地使用反射成功实现了我的原始目标。对于任何人的兴趣,代码如下。我遇到的关键问题(转换事件对象)很好地由Method.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();
            }
        }
    }
}

更新后的版本有一些额外的东西,比如从Handler实现中查找处理程序方法,以及用于保存处理程序实例和相应Method的元组。

如果我有更多时间,我会使用MethodHandle调查差异 - 也许更快。我的解决方案可能比在EventBus中使用defender方法慢。

注意:我的解决方案显然不完整,没有评论,没有测试,没有unregister方法等等。