简体   繁体   中英

How to use generics with a heterogeneous map?

I have a utility method to help use ConcurrentMap.putIfAbsent like this:

public static <K, V> V putIfAbsent(
        ConcurrentMap<K, V> map, K key, Callable<V> task) {
    try {
        V result = map.get(key);
        if (result == null) {
            V value = task.call();
            result = map.putIfAbsent(key, value);
            if (result == null) {
                result = value;
            }
        }
        return result;
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

Now I have to deal with such a heterogeneous map:

private ConcurrentMap<Class<?>, List<ClassHandler<?>>> handlerMap;

and a method to add handlers to it:

public <T> void addHandler(Class<T> c, ClassHandler<? super T> handler) {
    putIfAbsent(handlerMap, c, new Callable<List<ClassHandler<?>>>() {
        @Override
        public List<ClassHandler<?>> call() throws Exception {
            return new CopyOnWriteArrayList<>();
        }
    }).add(handler);
}

So far so good, but the problem comes when I try to handle a class, eg:

Class<String> c = String.class;
List<ClassHandler<?>> handlers = handlerMap.get(c);
if (handlers != null) {
    for (ClassHandler<?> c : handlers) {
        handler.handle(c);
    }
}

However, this code doesn't compile:

error: method handle in class ClassHandler<T> cannot be applied to given types;
        handler.handle(c);
  required: Class<CAP#1>
  found: Class<String>
  reason: actual argument Class<String> cannot be converted to Class<CAP#1> by method invocation conversion
  where T is a type-variable:
    T extends Object declared in class ClassHandler
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error

The code compiles if I use handler.handle((Class) c) , but I will receive a unchecked warning. Though I can add a @SuppressWarnings("unchecked") annotation, this would be the last choice if there's no better way.

Any idea about this issue?

Right. The type of the map itself doesn't guarantee that the type parameter of the class and the corresponding class handlers are the same. (No type will express that anyway.) However, you correctly used your own add method to ensure that they match when you put them in. So you can trust that they are the same. In that case, simply cast the handler to the appropriately-parameterized type (you will need to suppress a warning):

for (ClassHandler<?> handler : handlers) {
    ((ClassHandler<String>)handler).handle(c);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM