JoGa
JoGa

Reputation: 85

Iterator of a wildcard type variable with upper bound

Hello everybody I try to extend a HashMap<String,String> to enforce a "all-lowercase" rule

public class HttpQueryMap extends HashMap<String,String>
{    
    ...
    @Override
    public void putAll(Map<? extends String, ? extends String> m)
    {       
        ...
        Iterator<Map.Entry<String,String>> iterator = m.entrySet().iterator();
        ...      
    }
    ... 
}

I get a compile-time error

incompatible types
required: Iterator<Entry<String,String>>
found:    Iterator<Entry<CAP#1,CAP#2>>
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends String from capture of ? extends String
CAP#2 extends String from capture of ? extends String

The next work-around does the job but it is really ugly:

public class HttpQueryMap extends HashMap<String,String>
{    
    ...
    @Override
    public void putAll(Map<? extends String, ? extends String> m)
    {       
        ...
        Map<String,String> m_str=new HashMap<String,String>();
        m_str.putAll(m);
        Iterator<Map.Entry<String,String>> iterator = m_str.entrySet().iterator();
        ...      
    }
    ... 
 }

As far as I understand the problem is that the type variable String used in the Iterator<Map.Entry<String,String>> does not extend String (itself) used in the declaration of Map<? extends String, ? extends String> m

Upvotes: 8

Views: 2343

Answers (4)

ZhongYu
ZhongYu

Reputation: 19682

Wildcards are kind of vague, sometimes we want to turn wildcards into type variables which are more tangible.

The standard way is introducing a method with corresponding type variables

public void putAll(Map<? extends String, ? extends String> m)
{
    _putAll(m);
}

<S1 extends String, S2 extends String>
void _putAll(Map<S1, S2> m)
{
    Iterator<Map.Entry<S1,S2>> iterator = m.entrySet().iterator();
}

In java8, also try

public void putAll(Map<? extends String, ? extends String> m)
{
    m.forEach( (k,v)->
    { 
        ... 
    });
}

The types of (k,v) are inferred to be captured types, just like (S1,S2). However, it is also OK if we fix their types as (String,String), due to the flexibility of the signature of forEach

    m.forEach( (String k, String v)->

Upvotes: 4

Apprentice Queue
Apprentice Queue

Reputation: 2036

My understanding is this: If it were possible that you could derive from String, say classes called LeftRightString and UpDownString, then

  • Map<LeftRightString,LeftRightString> is a subtype of Map<? extends String, ? extends String>
  • Map<String, String> is a subtype of Map<? extends String, ? extends String>
  • but Map<LeftRightString,LeftRightString> is not a subtype of Map<String,String>

Therefore your iterator type mismatches. If it were allowed then, the following would work when it should not work:

void putAll(Map<? extends String, ? extends String> pm) {
    Map<String, String> m = pm;
    m.add(new UpDownString(), new UpDownString());  // ooops!! if ? was LeftRightString
}

(Update) I want to add that almost everything I said here is in the Oracle Java tutorials so I'm baffled at why so many people keep commenting that this is wrong. And what is not in the tutorial can be found in the Java Specification. What I haven't done is give a workaround but other answers have.

Upvotes: 0

Natix
Natix

Reputation: 14247

Without Iterator

The easiest way is to use a for-each loop. Even in this case, you need the parametrize the Entry with the same wildcards as in the given map. The reason is that Entry<? extends String, ? extends String> is not a subtype of Entry<String, String>. The fact that String is a final class is irrelevant here, because the compiler has no knowledge of that.

for (Entry<? extends String, ? extends String> entry : m.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
}

With Iterator

If you really need an Iterator, the syntax that does compile is a bit baffling:

Iterator<? extends Entry<? extends String, ? extends String>> iterator =
    m.entrySet().iterator();

while (iterator.hasNext()) {
    Entry<? extends String, ? extends String> entry = iterator.next();
    String key = entry.getKey();
    String value = entry.getValue();
}

I originally expected the iterator to be only of type Iterator<Entry<? extends String, ? extends String>>, which at first appears to be the return type of iterator() method called on a Set<Entry<? extends String, ? extends String>> which in turns appears to be the return type of entrySet() called on Map<? extends String, ? extends String>.

However, it is a bit more complex than that. I've found a probable answer in here:

http://mail-archives.apache.org/mod_mbox/harmony-dev/200605.mbox/%[email protected]%3E

The interesting part is this:

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

Upvotes: 7

cmbaxter
cmbaxter

Reputation: 35443

Why not just avoid the iterator all together as this code seems to work just fine for your implementation of putAll:

for(String s: m.keySet()){
  put(s.toLowerCase(), m.get(s));
}

As to why you can't seem to work around that error, I have no idea. I tried multiple variants and nothing seemed to work.

Upvotes: 1

Related Questions