Jephron
Jephron

Reputation: 2748

Java generics event system unchecked call

I'm writing an event system for a game. Here's an example of the API for subscribing to a channel.

Events.subscribe(Channel.COLLISION, new EventHandler<CollisionEvent>() {
    @Override
    public void handle(CollisionEvent event) {
    }
});

and publishing

Events.publish(Channel.COLLISION, new CollisionEvent(data));

My problem arises when passing objects to the publish method. I'd been getting a warning from my IDE about an unchecked call in the publish method. I'm trying to find a way to check to make sure the type of the object is compatible with the EventHandler's generic type. How can I ensure that event is of the right type for the handler (in the excerpt below)?

Events.java

private static Map<Channel, ArrayList<EventHandler>> eventHandlers
        = new HashMap<Channel, ArrayList<EventHandler>>();

public static void publish(EventType eventType, GameEvent event) {
    ArrayList<EventHandler> handlers = eventHandlers.get(eventType);
    if(handlers != null) {
        for (EventHandler handler : handlers) {
            handler.handle(event); // unchecked call warning occurs here
        }
    }
}

EventHandler.java

public abstract class EventHandler<T extends GameEvent> {
    public abstract void handle(T event);
}

Upvotes: 2

Views: 371

Answers (2)

thst
thst

Reputation: 4602

I suppose the problem is because your loop uses an untyped EventHandler, while it calls a generic method.

I would use the list as

List<EventHandler<? extends GameEvent>>

Same for the loop variable in the for statement.

Thomas

Update: Sorry, you look for the type of event, thought you don't want the warning.

How about having the EventHandler decide, if that type is interesting inside the handle method. If that is too slow, build a Map with the GameEvent.class as the key and a list of interrested handlers as value. The handler gives the list of events at subscription time.

To be more precise: Your current way to subscribe to an event seems to suggest, that the handler has the proper method implemented. Yet, your subscribe method does not enforce a relationship between handler and subscribed event.

If you modify the subscription to bind the handler not to the Event-Channel, but to the event itself, the compiler will do it's very best to avoid any handler not reacting to the right event getting access to the channel:

public <T extends GameEvent> void subscribe(Class<T> clazz, EventHandler<T> handler)

Inside the subscribe implementation, you place the handler in a list, that you keep in a map, keyed with clazz.

if( !map.containsKey(clazz) ) {
    map.put(clazz, new ArrayList<EventHandler<T>>());
}

// add the handler
map.get(clazz).add(handler);

The publish method will use the given concrete GameEvent.class as key to obtain the right list from the map and call all the handlers.

By regulating the access during subscription, you don't need to pay attention during call. If someone uses dirty tricks to get into the wrong list, he deserves to crash, as long as you don't write code for atomic bombs ;-)

Upvotes: 1

childofsoong
childofsoong

Reputation: 1936

You should be getting this warning - you could potentially be passing in a different event type. You may need to make a decision on how you want to architect your solution - as is, you could change it so the handle function on your EventHandler objects can take in any type of event, not just the one the EventHandler is parameterized for, and then have the EventHandler check whether the event it's asked to handle is the type it is interested in. Alternatively, you can move that type of logic to the event publisher - whenever an event comes it, the for loop can check if the event handler it's currently on supports that type of event, and if so, tell it to handle it.

Upvotes: 2

Related Questions