简体   繁体   English

动态类型转换

[英]Dynamic type-casting

I am playing with a Java 8 event bus where a register method takes an event class and a Java 8 function reference, something like: - 我正在使用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 ...
    }
}

Publishers can simply post to the same bus: - 发布者可以简单地发布到同一总线上:-

class SomePublisher {
    SomePublisher(EventBus eventBus) {
        eventBus.post(new MyEvent(...));
    }
}

My (very simplistic at the moment) EventBus currently looks like this: - 我的(目前很简单) 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()));
        }
    }
}

My problem is with the post method - it needs to somehow dynamically cast to what the target needs, ie MyEvent in the example above. 我的问题是post方法-它需要以某种方式动态地转换为目标所需的内容,即上面示例中的MyEvent I can't seem to write the post method to cast the event or otherwise to satisfy the compiler. 我似乎无法编写post方法来强制转换事件或满足编译器的要求。 Even though the event being passed in (from SomePublisher is a MyEvent instance, the post method is not capable of retaining that information. 即使传入的事件(来自SomePublisher的事件是MyEvent实例), post方法也无法保留该信息。

What do I need to do to achieve this dynamic type casting. 我需要做什么才能实现这种动态类型转换。 Reflection? 反射? MethodHandles? MethodHandles?

Also, I'd ideally not require the EventBase marker interface. 另外,理想情况下,我不需要EventBase标记接口。

Thanks 谢谢

PS. PS。 existing event bus libraries are "good-enough" but using lambda's for this intrigues me and strikes me as compact and direct. 现有的事件总线库“足够好”,但是使用lambda对此很感兴趣,使我感到紧凑而直接。

An option that seems to work is to make event base look like this 似乎可行的选择是使事件库看起来像这样

public interface EventBase {
    default <T extends EventBase> void accept(Handler<T> handler) {
        handler.handleEvent((T) this);
    }
}

and then have your event bus dispatch back through it like 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);
    }
}

Makes event base a bit messy though, so i'm not sure how I feel about it. 虽然使事件基础有些混乱,所以我不确定我对此有何看法。 But it will pass a test based on your sample code. 但是它将通过基于您的示例代码的测试。

EDIT : I simplified EventBase a little, but i can't seem to get rid of the unchecked cast. 编辑 :我简化了EventBase一点,但我似乎无法摆脱未经检查的演员表。

Insight into the problem 洞察问题

Unfortunately, I think that what you're trying to do may not be possible. 不幸的是,我认为您尝试做的事情可能无法实现。 The trouble lies with this: you know that the event you're giving the handler has a class compatible with what the handler can accept, but only as a result of the fact that you retrieved the handlers from the Map you set up which holds the handlers by their event type. 问题就在于此: 知道您为处理程序提供的事件具有一个与该处理程序可以接受的类兼容的类,但这仅是由于您从设置的Map检索了处理程序的事实所致,该处理程序包含处理程序的事件类型。 The compiler , however, doesn't understand this logic. 但是, 编译器无法理解此逻辑。 From its perspective, you're trying to take an arbitrary handler, which is expecting some specific extension of event base that the compiler doesn't know and giving it an event which may or may not fit the expected type. 从它的角度来看,您正在尝试采用一个任意处理程序,它期望编译器不知道的事件库的某些特定扩展,并为其提供可能适合或可能不适合预期类型的​​事件。 Perhaps it would be helpful to think in terms of the question: Even if I could magically change the code so that when an event of type MyEvent was passed in the compiler cast it to a MyEvent before giving it to the handler, how would the compiler know that the handler could accept a MyEvent ? 考虑以下问题也许会有所帮助: 即使我可以神奇地更改代码,以便在将MyEvent类型的事件传递到编译器中时,在将其提供给处理程序之前将其转换为MyEvent ,编译器将如何处理?知道处理程序可以接受MyEvent吗? All the compiler knows is that the handler accepts something specific extending from EventBase. 编译器只知道处理程序接受来自EventBase的特定扩展。

Looking at the bolded question above, it becomes more clear why any reflective solution would fail. 看看上面的大胆问题,就会更清楚为什么任何反射解决方案都会失败。 You may be able to use reflection to cast the event to its appropriate class, but the compiler doesn't know whether that class works for the handler. 您可能可以使用反射将事件强制转换为其适当的类,但是编译器不知道该类是否适用于处理程序。

A Workaround 解决方法

I recommend Iscoughlin's solution of a default method in EventBase to flip around the dependency so that you give the event the handler rather than giving the handler the event. 我建议Iscoughlin在EventBase中使用default方法的解决方案来翻转依赖项,以便为事件提供处理程序,而不是为处理程序提供事件。 This fits much more cleanly into your model than the workaround I am about to suggest. 这比我要建议的解决方法更适合您的模型。 But for the sake of completeness, here is another (admittedly less clean) solution: 但是为了完整起见,这是另一个(公认不太干净)的解决方案:

public interface Handler {
    public void handleEvent(EventBase event);
}

The bus: 公交车:

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

Subscribers would look like this (here's where this solution is less-than-ideal): 订户看起来像这样(这里的解决方案不理想):

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 ...
    }
}

I have succeeded in my original aims by simply using reflection. 仅通过反射,我就已经实现了最初的目标。 For anybody's interest, the code follows. 为了任何人的利益,下面的代码。 The key problem I had (casting event object) is nicely side-stepped by Method.invoke(Object obj, Object ...args) taking an Object for arguments - no need for casting. 我遇到的关键问题(广播事件对象)由Method.invoke(Object obj, Object ...args)很好地Method.invoke(Object obj, Object ...args)接受一个Object作为参数-无需强制转换。

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

The updated version has a little extra stuff, like looking up the handler method from the Handler implementation and a tuple for holding the handler instance and the corresponding Method . 更新后的版本有一些额外的内容,例如从Handler实现中查找handler方法,以及用于保存该handler实例和相应Method的元组。

If I had more time, I would investigate the difference using MethodHandle - maybe quicker. 如果我有更多时间,我会使用MethodHandle进行调查-也许更快。 My solution will probably be slower than using the defender method in the EventBus. 我的解决方案可能会比在EventBus中使用防御者方法慢。

Note: my solution is clearly incomplete, no comments, no tests, no unregister method, etc, etc. 注意:我的解决方案显然是不完整的,没有注释,没有测试,没有unregister方法等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM