I have code that has a Map
of (Message)Handlers. I'm trying to make the handlers generified (as seen by the interface Handler). Without generics the handlers all need to cast from Object to the respective class, which would be nice to avoid (but everything works). For each message class (Foo
below) I have a handler class.
How can I keep a Map of any kind of Class to any kind of Handlers and get/call with "just" an Object
? (the parameter to handleMessage(Object)
can't be restricted)
See MWE below.
import java.util.*;
public class Logic
{
Map<Class<?>, Handler<?>> handlers = new HashMap<Class<?>, Handler<?>>();
public void run()
{
handlers.put(Foo.class, new FooHandler());
}
public void handleMessage(Object msg)
{
Handler<?> handler = handlers.get(msg.getClass());
if (handler != null) {
handler.execute(msg);
}
}
private interface Handler<T>
{
public void execute(T msg);
}
private class FooHandler implements Handler<Foo>
{
public void execute(Foo msg) {}
}
private class Foo {}
}
This code produces:
Logic.java:16: execute(capture#x of ?) in Logic.Handler cannot be applied > to (java.lang.Object) handler.execute(msg);
How can this be repaired to work while still keeping the Handler interface generic?
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.
The Java compiler internally represents the value of a wildcard by capturing it in an anonymous type variable, which it calls "capture of ?" (actually, javac calls them "capture #1 of ?" because different uses of ? may refer to different types, and therefore have different captures).
You can't define the relationship between the key and the value in a field, but you can use accessor methods to enforce it, provided only these methods are used to access the map.
private final Map<Class, Handler> handlers = new HashMap<Class, Handler>();
public <T> void addHandler(Class<T> clazz, Handler<T> handler) {
handlers.put(clazz, handler);
}
@SuppressWarnings("unchecked")
public <T> Handler<T> getHandler(Class<T> clazz) {
return (Handler<T>) handlers.get(clazz);
}
@SuppressWarnings("unchecked")
public <T> Handler<T> getHandlerFor(T t) {
return getHandler((Class<T>) t.getClass());
}
public void run() {
addHandler(Foo.class, new FooHandler());
}
public <T> void handleMessage(T msg) {
Handler<T> handler = getHandlerFor(msg);
if (handler != null) {
handler.execute(msg);
}
}
The problem is that execute()
takes a certain parameter type, that is more specific than Object
.
However, in your handleMessage()
method, the compiler doesn't know what type the parameter is. Suppose a case where FooHandler
is registered for class Bar
(which would be possible).
In that context handler.execute(msg);
would actually result in FooHandler#execute(Foo)
being called with a Bar
argument, which would result in a ClassCastException
(unless Bar extends Foo
). Thus the compiler refuses to compile that code.
If 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