Bounded-wildcard related compiler error
Asked Answered
F

1

4

I am wondering what is wrong with this code:

Map <? extends String, ? extends Integer> m = null;
Set<Map.Entry<? extends String, ? extends Integer>> s = m.entrySet();

The compiler complains with the error message:

Type mismatch: cannot convert from Set<Map.Entry<capture#1-of ? extends String,capture#2-of ? extends Integer>> to Set<Map.Entry<? extends String,? extends Integer>>

What should the type of s be? Eclipse suggests Set<?> but I am trying to get more specific than that.

Fairminded answered 20/9, 2013 at 0:51 Comment(0)
S
9

This issue is addressed in this old Apache thread:

The problem is that the entrySet() method is returning a Set<Map.Entry<capture-of ? extends K, capture-of ? extends V>>, which is incompatible with the type Set<Map.Entry<? extends K, ? extends V>>. It's easier to describe why if I drop the extends K and extends V part. So we have Set<Map.Entry<?, ?> and Set<Map.Entry<capture-of ?, capture-of ?>>.

The first one, Set<Map.Entry<?, ?>> is a set of Map.Entries of different types - ie it is a heterogeneous collection. It could contain a Map.Entry<Long, Date> and a Map.Entry<String, ResultSet>> and any other pair of types, all in the same set.

On the other hand, Set<Map.Entry<capture-of ?, capture-of ?>> is a homogenous collection of the same (albeit unknown) pair of types. Eg it might be a Set<Map.Entry<Long, Date>>, so all of the entries in the set MUST be Map.Entry<Long, Date>.

The crux of the problem is that top-level wildcards capture, meaning they are essentially one-off type parameters. In contrast, nested wildcards don't capture, and have somewhat of a different meaning.

So, removing the bounds for simplicity, declaring

Map<?, ?> m;

means "a map of some specific unknown type of keys and some specific unknown type of values".

But declaring

Set<Map.Entry<?, ?>> s;

means "a set of entries of any type of key and value".

So that's where you run into trouble because the expression m.entrySet() doesn't want to return that but instead "a set of entries of some specific unknown type of keys and some specific unknown type of values". And those types are incompatible because generics aren't covariant: A Set<Type> isn't a Set<SuperType>.

(See this fascinating post, which helps tease apart the nuances of nested wildcards: Multiple wildcards on a generic methods makes Java compiler (and me!) very confused.)

One workaround is to use a capture helper method, which takes advantage of the fact that formal type parameters can be nested:

private <K extends String, V extends Integer> void help(final Map<K, V> map) {
    final Set<Map.Entry<K, V>> entries = map.entrySet();
    // logic
}

...

Map<? extends String, ? extends Integer> m = null;
help(m);

That's a contrived example since String and Integer are both final, but it shows the concept.

A simpler workaround is the following:

Set<? extends Map.Entry<? extends String, ? extends Integer>> s = m.entrySet();

This means adding non-null elements to s isn't allowed, but in the case of the Set returned by entrySet, the add and addAll methods are unsupported anyway (thanks to newacct for clarifying this point).

Streamer answered 20/9, 2013 at 1:9 Comment(4)
"This effectively makes s read-only but contextually that shouldn't be an issue." Technically, it only makes it not possible to add (non-null) things to it; removal is still allowed. This is guaranteed not to be an issue -- the documentation for entrySet() says that the returned Set "does not support the add or addAll operations."Circassia
@Circassia You're right, "read-only" was a problematic description - fixed.Streamer
Yeah. I also think the signature of the entrySet() method in the Java API is a mistake. Set<? extends Map.Entry<...>> is sufficient, and it makes it more flexible for the implementation of the Map to, for example, return a set that is parameterized by a custom entry subclass or something.Circassia
Another solution is to explicitly use an unmodifiable set: Set<Map.Entry<?, ?>> set = Collections.<Map.Entry<?, ?>>unmodifiableSet(entries.entrySet())Shalom

© 2022 - 2024 — McMap. All rights reserved.