Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterator of a wildcard type variable with upper bound

Tags:

java

generics

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

like image 725
JoGa Avatar asked May 25 '13 20:05

JoGa


1 Answers

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

like image 144
Natix Avatar answered Sep 29 '22 08:09

Natix