I'm facing some problems with Generics when using Google Guava's excellent Multimap. I have a type Handler defined as such
public interface Handler<T extends Serializable> {
void handle(T t);
}
In another class I've defined a multimap that maps a String to a collection of Handlers.
private Multimap<String, Handler<? extends Serializable>> multimap =
ArrayListMultimap.create();
Now when I try to do stuff with the multimap, I'm getting compiler errors. My first attempt looked like this:
public <T extends Serializable> void doStuff1(String s, T t) {
Collection<Handler<T>> collection = multimap.get(s);
for (Handler<T> handler : collection) {
handler.handle(t);
}
}
which resulted in the following error.
Type mismatch: cannot convert from Collection<Handler<? extends Serializable>>
to Collection<Handler<T>>
Afterwards, I tried to code it like this
public void doStuff2(String s, Serializable serializable) {
Collection<Handler<? extends Serializable>> collection = multimap.get(s);
for (Handler<? extends Serializable> handler : collection) {
handler.handle(serializable);
}
}
which unfortunately failed as well:
The method handle(capture#1-of ? extends Serializable) in the type
Handler<capture#1-of ? extends Serializable> is not applicable for the arguments
(Serializable)
Any help would be greatly appreciated. Thanks.
Update:
The only way I have managed to fix this is by suppressing compiler warnings. Given the following handler:
public interface Handler<T extends Event> {
void handle(T t);
Class<T> getType();
}
I can write the event bus as such.
public class EventBus {
private Multimap<Class<?>, Handler<?>> multimap = ArrayListMultimap.create();
public <T extends Event> void subscribe(Handler<T> handler) {
multimap.put(handler.getType(), handler);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void publish(Event event) {
Collection<Handler<?>> collection = multimap.get(event.getClass());
for (Handler handler : collection) {
handler.handle(event);
}
}
}
I guess there's no way to handle this with less or even without @SuppressWarnings?
The question mark (?) is known as the wildcard in generic programming. It represents an unknown type. The wildcard can be used in a variety of situations such as the type of a parameter, field, or local variable; sometimes as a return type.
In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific).
We can use the Java Wildcard as a local variable, parameter, field or as a return type. But, when the generic class is instantiated or when a generic method is called, we can't use wildcards. The wildcard is useful to remove the incompatibility between different instantiations of a generic type.
Due to extensive capture conversion, in most places, compiler treats wildcards as if they are type variables. Therefore indeed programmer can replace wildcard with type variables in such places, a sort of manual capture conversion.
The problem is that the types might be different:
private Multimap<String, Handler<? extends Serializable>> multimap =
ArrayListMultimap.create();
wouldn't allow you to add anything to the multimap, since you don't know what ?
actually stands for. You could for example have a Multimap<String, Handler<String>>
and try to add an Integer
because both implement Serializable
.
Edit: Actually the above paragraph is slightly wrong. You should be able to add handlers to the multimap, but since the type parameters of the handlers are not known, you wouldn't be able to use the handlers, see below.
In your doStuff1
method you define a concrete parameter T
which might be something completely different. Thus the compiler can't determine if this assignment would be correct: Collection<Handler<T>> collection = multimap.get(s);
(is T
really the type of the handler you get from the multimap? - The compiler doesn't know).
Your second approach does get the assignment right, however the handle()
method won't work, since you pass in a Serializable
which could be anything (String
, Integer
, something else) and the compiler still doesn't know if the handler's type matches that (imagine it's a Handler<Number>
and you pass a String
to doStuff2
).
You have several alternatives to fix that, each with it's own drawbacks:
Multimap<String, Handler<Serializable>>
, which would allow you to pass any Serializable
object to the handlerMultimap<String, Handler<String>>
, which would limit you to string handlers onlyIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With