Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics: capture cannot be applied to Object

Tags:

java

generics

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?

like image 896
Jonas WS Avatar asked Nov 29 '11 13:11

Jonas WS


People also ask

What is Java generics in what scenarios we can and Cannot use generics?

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.

What is capture of in Java?

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


2 Answers

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);
    }
}
like image 120
Peter Lawrey Avatar answered Sep 22 '22 02:09

Peter Lawrey


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.

like image 21
Thomas Avatar answered Sep 24 '22 02:09

Thomas