开发者

Java generics: required capture#67 of?

I have this function:

 /**
 * Helper function that adds the values of b to the elements of a, treating
 * all keys that exist in b but not in a, as existent in a with value 0. NB:
 * It operates IN PLACE. 
 * @param a The {@link java.util.Map} which will hold the result
 * @param b The {@link java.util.Map} which will be added to a
 */
private static void sumMaps(Map<?, Integer> a, Map<?,Integer> b)
{
    for (Object key : b.keySet()) {
        Integer currentCount = a.get(key);
        a.put(key, currentCount == null ? b.get(key) : currentCount + b.get(key));
    }
}

However, NetBeans highlights "key" in the final line of for, and gives me this error:

method put in class java.util.Map<K,V> cannot be applied to given types  
required: capture #67 of?, java.lang.Integer  
found: java.lang.Object, int

(the int is not the problem because of Java unpacking, I tried using 开发者_高级运维Integers too but it didn't work).


The problem is one of covariance. When you pull a key out of one map you cannot guarantee that the key is compatible with the other map. If one map uses Foos and the other map uses Bars and these two classes are unrelated to each other then you cannot interchange them.

Imagine that a is of type Map<String, Integer> and b is of type Map<Float, Integer>. These two types are compatible with your parameter declarations, but their key types are incompatible. You will have trouble if you take a Float from b and try to use it as a key in a.put(key,...). That is effectively what the compiler is complaining about. Using ? does not restrict the maps to compatible key types.

Instead of ? you must specify that the two key classes are the same by setting them both to some generic Key class. This way the keys from one map can be inserted into the keys from the other map.

private static <Key> void sumMaps(Map<Key, Integer> a, Map<Key, Integer> b)
{
    for (Key key : b.keySet()) {
        Integer currentCount = a.get(key);
        a.put(key, currentCount == null ? b.get(key) : currentCount + b.get(key));
    }
}

Generally speaking, if you're trying to suss out a problem with ?, do not mentally replace ? with Object. Instead, a better way to think about it is to replace every instance of ? with a different imaginary type name.

Warning: Math Analogy

It's similar to the C constant that shows up in indefinite integrals in calculus. For instance, ∫ cos x dx = sin x + C. If you do one integral then writing C is fine, but if you do multiple integrals then you have to remember that these constants are different and are not interchangeable. It then behooves you to write C1, C2, C3, etc.

? is the same way. The first time it shows up, pretend it is class A. The next time it is class B. And so on.


Specifying "?" for the key means that it could be of any type, but the keys must be the same for each Map for this method to work. So use the following instead:

private static <K> void sumMaps(Map<K, Integer> a, Map<K, Integer> b) {
        for (K key : b.keySet()) {
            Integer currentCount = a.get(key);
            a.put(key, currentCount == null ? b.get(key) : currentCount + b.get(key));
        }
    }


? is not Object or something. It's totally unknown. Okay, I'm not that good at explaining that stuff either but there seems to be a very simple solution: Since the keys of both Maps need to be of the same type anyway why not introduce a generic type variable?

private static <T> void sumMaps(Map<T, Integer> a, Map<T, Integer> b) {
    for (T key : b.keySet()) {
        Integer currentCount = a.get(key);
        a.put(key, currentCount == null ? b.get(key) : currentCount
                + b.get(key));
    }
}


For ? read "some particular type" and note that this could be a different type each time you mention ?. So, for example, a could be a Map<String, Integer> and b could be a Map<BigDecimal, Integer>. So, indeed, you shouldn't be allowed to take a key out of a and put it into b and the compiler is stopping you. The compiler isn't very helpful in explaining this!

As other answers have suggested, you could make the method generic.


Object is not a valid substitute for ?. Here is a working version with a type variable T:

private static <T> void sumMaps(final Map<T, Integer> a,
    final Map<T, Integer> b){
    for(final T key : b.keySet()){
        final Integer currentCount = a.get(key);
        a.put(key,
            currentCount == null
                ? b.get(key)
                : Integer.valueOf(currentCount.intValue()
                    + b.get(key).intValue()));
    }
}

(I also removed some auto-boxing / unboxing)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜