javaeventsgenericsraw-types

How to avoid the use of raw type and use generics instead when the type is unknown?


I have a generic interface for a handler:

public interface EventHandler<T> {

     boolean handleEvent(T message);
}

You can implement this interface and process T message. You have to pass an instance of your implementation to an EventDispatcher instance. The class stores internally multiple EventHandler instances of various types.

At some point an event is triggered and the EventDispatcher instance calls the corresponding EventHandler's handleEvent() method and passes a message that is the same type as the EventHandler. The problem is that I don't know which is the exact type, but I am sure that the T message I'm passing to the EventHandler instance have the same "T".

The way I make this call uses raw types and works:

EventHandler handler = getHandler();
Object message = getMessage();
handler.handleEvent(message);

,but I get the warning "Unchecked call to 'handleEvent(T)' as a member of raw type 'EventHandler'".

Is it possible to use generics to avoid the warning?

One possible solution I have thought of is to make a generic wrapper method:

private <T> boolean handleEventGeneric(EventHandler<T> handler, T message) {
    return handler.handleEvent(message);
}

and use it:

EventHandler handler = getHandler();
Object message = getMessage();
handleEventGeneric(action.eventHandler, message);

But I don't like the fact that I have to make a wrapper method. Is there a more elegant solution?


Solution

  • It's not possible to remove this warning (or at least not without @SuppressWarning). The only way to get rid of it is by doing 2 things:

    1. Making your code type proof: so basically if the invoking code does not use raw type, you are type safe or the invoking code is using rawtypes and it it's fault if the code end up being no type safe
    2. Add the @SuppressWarning("unchecked") on your code.

    The warning is there so that you can easily identify where you have a weakness in terms of type safety, in your code. If you use the annotation correctly, then your code is safe and you're sure of not getting any unpleasant ClassCastException, unless you willingly added an @SuppressWarning("unchecked") in a place where you are actually not all that sure that the type is safe.

    See some demo code illustrating my point:

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestGenerics {
    
        private Map<Class<?>, EventHandler<?>> handlers = new HashMap<Class<?>, TestGenerics.EventHandler<?>>();
    
        public interface EventHandler<T> {
    
            boolean handleEvent(T message);
        }
    
        // Here you force the invoker to provide the correct type of event handler
        // with the given type of klass
        // If he wants to make this fail, then he will have to use rawtype
        public <T> void registerHandler(Class<T> klass, EventHandler<T> handler) {
            handlers.put(klass, handler);
        }
    
        public <T> void handle(T message) {
            @SuppressWarnings("unchecked") // Here you can add this annotation since you are forcing any invoker to provide a correct EventHandler
            EventHandler<T> handler = (EventHandler<T>) handlers.get(message.getClass());
            if (handler != null) {
                handler.handleEvent(message);
            }
        }
    
        public static void main(String[] args) {
            TestGenerics test = new TestGenerics();
            test.registerHandler(Long.class, new EventHandler<Long>() {
                @Override
                public boolean handleEvent(Long message) {
                    System.out.println("Received a long " + message);
                    return true;
                }
            });
            // Here I use raw type but this also means that I created a weak spot in
            // terms of type safety
            EventHandler handler2 = new EventHandler<String>() {
                @Override
                public boolean handleEvent(String message) {
                    System.out.println("THis will never print " + message);
                    return false;
                }
            };
            test.registerHandler(Integer.class, handler2); // This is where the
                                                            // problem comes from
            test.handle(3L); // OK
            test.handle(1); // ClassCastException
    
        }
    
    }