Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics Wildcards Question

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?

like image 840
Tom Avatar asked May 14 '11 06:05

Tom


People also ask

What are wildcards in generics in Java?

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.

What are wildcards arguments in generics?

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).

Why do we need wildcard in generics in Java?

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.

Can wildcard types be replaced by any 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.


1 Answers

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:

  1. Just use Multimap<String, Handler<Serializable>>, which would allow you to pass any Serializable object to the handler
  2. Use a concrete type, e.g. Multimap<String, Handler<String>>, which would limit you to string handlers only
  3. Get the type parameter of the handler at runtime and cast, which might be error prone if you don't get it right
like image 89
Thomas Avatar answered Oct 03 '22 05:10

Thomas