简体   繁体   中英

Doubly-Wildcard Generic Type error

I am trying to define an operator ++ for my custom Map type like this:

@Override
public MutableMap<K, V> $plus$plus(Map<? extends K, ? extends V> map)
{
    HashMap<K, V> copy = this.copy();
    map.$plus$plus$eq(map);
    return copy;
}

The ++= operator is defined like this:

public void $plus$plus$eq(Map<? extends K, ? extends V> map);

However, the compiler complains on the map.$plus$plus$eq(map); line, with the following crazy error:

The method $plus$plus$eq(Map<? extends capture#10-of ? extends K,
? extends capture#11-of ? extends V>) in the type
Map<capture#10-of ? extends K,capture#11-of ? extends V> is not
applicable for the arguments
(Map<capture#12-of ? extends K,capture#13-of ? extends V>)

As you can see in this screenshot, none of the solutions provided by Eclipse work, yet alone even make sense:

Eclipse Marker

I have been working with Java generics for quite a while now, even having developed my own generic type system for a custom programming (whose library I am currently trying to code), but I have never had an error like this before.


EDIT : Interestingly, casting the map argument to the raw type (Map) seems to fix the problem.

map.$plus$plus$eq((Map) map);

However, changing the cast to (Map<?, ?>) (which is what Eclipse's second solution does) causes a similar error.

It works if you use the raw type Map , but you lose type safety. Don't use raw types. They only exist for compatibility with Java 1.4 and older, which didn't have generics.

It doesn't work with wildcards because the compiler doesn't know what exact types the wildcards ? stand for. The reason is the same as for the question why you can't call add() on a List<? extends T> List<? extends T> .

Note that the wildcard does not mean that you can use whatever types of objects where the type of the key extends K and the type of the value extends V . Instead, it means that you have a map where the keys are of some specific, but unknown type K , and the values are of some specific, but unknown type V . You can't call $plus$plus$eq on such a map because the compiler doesn't know the exact types, so it can't check them.

In principle, the wildcards of the Map that you call the method on, might stand for types that are different than the wildcards of the Map that you pass as an argument - even though you can see in this case that they must be the same because you use the same object map in this case.

You can fix it by using type parameters instead of wildcards:

@Override
public <KK extends K, VV extends V> MutableMap<K, V> $plus$plus(Map<KK, VV> map)
{
    HashMap<K, V> copy = this.copy();
    map.$plus$plus$eq(map);
    return copy;
}

Here is a simplified example that also doesn't compile.

class Foo<T> {

    void bar(Foo<? extends T> foo) {
        foo.bar(foo);
    }
}

Suppose foo and goo have type Foo<? extends T> Foo<? extends T> . This means foo has type Foo<U> where U is a subtype of T and goo has type Foo<V> where V is also a subtype of T . You would not expect foo.bar(goo) to work because V might not be a subtype of U . For this reason, foo.bar(foo) doesn't compile either. This seems mad because foo and foo are the same instance, but whether or not arguments are applicable depends only on their compile time type.

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