Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map of generics: read and write

There are many different events, all implementing the same interface:

interface Event {}
class FooEvent implements Event {}
class BarEvent implements Event {}

Every event has a dedicated handler:

interface EventHandler<T extends Event> {
    void handle(T event);
}
class FooEventHandler implements EventHandler<FooEvent> {
    @Override
    public void handle(FooEvent event) { }
}
class BarEventHandler implements EventHandler<BarEvent> {
    @Override
    public void handle(BarEvent event) { }
}

All event handlers are created once and added to a map. Whenever an event occurs, this map should be used to find the proper event hander.

class Main {
    Map<Class<? extends Event>, EventHandler<? extends Event>> eventHandlerRegistry = Map.of(
            FooEvent.class, new FooEventHandler(),
            BarEvent.class, new BarEventHandler()
    );

    void handleEvent(Event event) {
        EventHandler<? extends Event> handler = this.eventHandlerRegistry.get(event.getClass());
        handler.handle(event); // DOES NOT COMPILE: needed=capture<? extends Event>, given=Event
    }
}

Unfortunately this last line does not compile. I can make it compile by leaving out the type parameter of EventHandler like this:

EventHandlerhandler = this.eventHandlerRegistry.get(event.getClass());
handler.handle(event); // WARNING: unchecked call to 'handle(T)' as a member of raw type 'EventHandler'

But this does not quite feel right... I am aware of PECS, but I feel kind of trapped because I produce AND consume my EventHandlers.

How can I implement this cleanly?

like image 992
Patric Avatar asked Dec 13 '19 12:12

Patric


People also ask

What is a generic map?

A generic map gives the value to a generic. Usually given in an instance but can also appear in a configuration. The values can be given via positional association or via named association. Use of named association is advised to improve readability and reduce the risk of making errors.

How to define a generic Map in java?

Generic Map in simple language can be generalized as: Map< K, V > map = new HashMap< K, V >(); Where K and V are used to specify the generic type parameter passed in the declaration of a HashMap.

Is map a generic interface java?

Java has provided generic support in Map interface.

How to write generic interface in Java?

The Syntax of a java generic interface is as follows:class class-name <type-parameter-list> implements interface-name <type-arguments-list> { ... ... ... }


2 Answers

You can't have type safety if you're going to mix your (generic) handlers in the same map. As far as I can see, the way to make your code type-safe is to get rid of the generic type parameter on EventHandler; but this is one thing you want to avoid.

If you may sacrifice type safety, knowing that your handlers will always match the specified classes, then you can try something like:

private <T extends Event> EventHandler<T> getHandler(Class<?> eventClass) {
    return (EventHandler<T>) 
              this.eventHandlerRegistry.get(eventClass); //Unchecked cast
}

Then make your handleEvent method generic:

<T extends Event> void handleEvent(T event) {
    EventHandler<T> handler = this.getHandler(event.getClass());
    handler.handle(event);
}

This method would then compile successfully, without warning. The only thing you'd need to make sure of is that eventHandlerRegistry never gets polluted with something like this:

put(FooEvent.class, new BarEventHandler())); //this can happen
like image 128
ernest_k Avatar answered Sep 30 '22 03:09

ernest_k


Here's an example of what you can do using a new EventType enum:

Declare the enum:

public enum EventType {
    FOO_EVENT, BAR_EVENT
}

Declare Event interface:

interface Event {
    EventType getType();
}

class FooEvent implements Event {
    EventType getType() {
        return FOO_EVENT;
    }
}

class BarEvent implements Event {
    EventType getType() {
        return BAR_EVENT;
    }
}

and EventHandlers:

interface EventHandler {
    void handle(Event event);
}

class FooEventHandler implements EventHandler {
    @Override
    public void handle(Event event) {
        //cast Event to FooEvent when processing
    }
}

class BarEventHandler implements EventHandler {
    @Override
    public void handle(Event event) {
        //cast Event to BarEvent when processing
    }
}

Declare a Map between EventType and EventHandler:

Map<EventType, EventHandler> eventHandlerRegistry = //... fill the map here

And finally, when an event occurs, simply do:

eventHandlerRegistry.get(event.getType()).handle(event);

P.S. Note, for enums it's better to use java.util.EnumMap.

like image 45
Pavel Smirnov Avatar answered Sep 30 '22 03:09

Pavel Smirnov