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 Listener
s 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!
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!
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.