Search code examples
javaeventsgenericslistenerobserver-pattern

Usage of wildcards and typed generics generates "is not applicable for the arguments" error


I'm currently writing a simple Event Listener library.
Here's my interface for any listener:

public interface Listener<T> {
    // Event is a very simple type which holds a variable T data.
    public boolean run(Event<T> e);
}

My class Listenable records all Listeners in a HashMap:

protected HashMap<String, ArrayList<Listener<?>>> listeners;

I'm using a wildcard here because I want my Listenable instances to have more than one event type.

The problematic part comes now in my Listenable::dispatchEvent() method:

public boolean dispatchEvent(Event<?> evt) {
        ArrayList<Listener<?>> evtListeners = listeners.get(evt.getType());
        if (evtListeners == null) {
            return true;
        }

        for (Listener<?> lst : evtListeners) {
        //           vvv--- error
            if (!lst.run(evt) || evt.shouldStopPropagation()) {
                return false;
            }
        }


        return true;
}

The error message says:

The method run(Event) in the type Listener is not applicable for the arguments (Event)

I've found a "solution" (in the sense of getting the compiler to hide the error):

for (Listener lst : evtListeners) {
            if (!lst.run(evt) || evt.shouldStopPropagation()) {
                return false;
            }
}

The compiler only generates two warnings in this case but I've read here that this technique is very, very poor!


I got it working with this code

public <T> boolean dispatchEvent(Event<T> evt) {
        ArrayList<Listener<?>> evtListeners = listeners.get(evt.getType());
        if (evtListeners == null) {
            return true;
        }

        for (int i = 0; i < evtListeners.size(); i++) {
            @SuppressWarnings("unchecked")
            Listener<T> lst = (Listener<T>) evtListeners.get(i);

            if (!lst.run(evt) || evt.shouldStopPropagation()) {
                return false;
            }
        }

        return true;
}

But I doubt that this is clean code, isn't it? I assume that the user of my library won't mix types for the same event type (evt.getType()).

I would appreciate any suggestions!


Solution

  • You should change the signature of your method to

    public <T> boolean dispatchEvent(Event<T> evt)
    

    and use T as the type:

    for (Listener<T> lst : (ArrayList<Listener<T>>) evtListeners) {
    

    Note this normally gives a "unchecked conversation" warning, you can disable that with @SuppressWarnings("unchecked") iirc.

    Edit: It is hard to get rid of unchecked conversation warnings. They simply mean that the compiler can NOT enforce that cast. The following statement is correct, useless, and will blow up your code somewhere later:

    ArrayList<Thread> al1 = new ArrayList<Thread>();
    al1.add(new Thread());
    ArrayList<Exception> al2 = (ArrayList<Exception>) al1;
    

    Generics in Java are more or less only a compile time hint that saves casts and give you more type safety.