1

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!

Community
  • 1
  • 1
ComFreek
  • 29,044
  • 18
  • 104
  • 156
  • 1
    Well, I as I'm sure you guessed, the problem here is that there is no way for the compiler to know that the `Event> evt` is compatible with `Listener>.run()`. If you want them to work together, you'll need to express that they're related in some way. It's difficult to make concrete suggestions, as it's not clear what your semantics are here. – Oliver Charlesworth Mar 26 '13 at 13:38
  • @OliCharlesworth Exactly, but how do I express that? Could you explain of which semantics you're thinking? I just want a simple `Listenable` class which can send an `Event` with a typed data member to all registered `Listener`s. The actual problem occurs because I want to have different `Event` data types in one HashMap thus I need to use a wildcard. – ComFreek Mar 26 '13 at 13:39
  • But that's exactly the problem. You're trying to treat all the types as the same (by putting them into a single map and effectively eliminating the type info), but you also want to be able to still consider the type (otherwise why have parameterised `Event`?). These two requirements are incompatible. – Oliver Charlesworth Mar 26 '13 at 13:46
  • @OliCharlesworth So isn't there any way for telling the compiler to assume type X? For example if I call `dispatchEvent( new Event(...) )`, can't I cast each item in my *ArrayList* explicitly to type *Integer*? – ComFreek Mar 26 '13 at 13:52
  • +1 for trying to develop a generic EventListener library. A great idea. [I worked on something similar](http://flyingspaniel.blogspot.com/2012/07/java-event-handling-revisited.html) – user949300 Mar 26 '13 at 15:16
  • You should use CopyOnWriteArrayList instead of an ArrayList in your Listeneable. – user949300 Mar 26 '13 at 15:34
  • @user949300 Thanks for the tip! I've now opensourced my library: https://github.com/ComFreek/GenericEventListeners – ComFreek Mar 27 '13 at 14:30

1 Answers1

2

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.

Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
  • Besides that `` has to be before the `boolean`, the second does not work, unfortunately. It gives this error message:`Cannot cast from ArrayList> to ArrayList>`. – ComFreek Mar 26 '13 at 13:49
  • Please see my edit above, I got it working using a (more or less clean) *for* loop. – ComFreek Mar 26 '13 at 13:59
  • You can not enforce anything with generics. Just do a few casts and you could use it with an other type (and blow something up). But that is nothing you can prevent or should care about. If someone does that, well... his code is responsible for that. – Johannes Kuhn Mar 26 '13 at 14:06
  • +1 this is the way to do it. But you should really use a CopyOnWriteArrayList (or Set) instead of an ArrayList. – user949300 Mar 26 '13 at 15:36