8

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

JoGa
  • 85
  • 5
  • 2
    @arynaq: Irrelevant. It's an override of `putAll(Map extends K, ? extends V> m)` on a `Map`, thus the signature. – Ryan Stewart May 25 '13 at 20:54
  • @RyanStewart: It's not irrelevant at all. It's just not the answer to the question, but it is a perfectly valid comment. – Keppil May 25 '13 at 20:58
  • @Keppil: The fact that it's not incorrect doesn't make it relevant to the question. In what way does the fact that String is final apply to this question? – Ryan Stewart May 25 '13 at 21:01
  • 1
    Have you considered composition here instead of inheritance? Create a class that implements `Map` for String,String and wraps a `Map`, delegating calls to the underlying `Map` and performing conversions where necessary, similar to a `Decorator Pattern`. Let me know if you are open to this and I can answer with a simple example. – cmbaxter May 25 '13 at 21:04
  • @RyanStewart: All the `? extends String` parts in the code makes it pretty relevant imo. – Keppil May 25 '13 at 21:07
  • 3
    @Keppil: The fact that there are no legal subclasses of String doesn't change the way the compiler interprets `? extends String` as a type parameter. That's exactly why I felt it was important to note its irrelevance: it's a red herring that could easily divert attention from an interesting question to which I want to see a good answer. – Ryan Stewart May 25 '13 at 21:13
  • 2
    @Keppil: To put it another way, if you replaced all occurrences of `String` with `Number` in the question text, the same problem exists, and the same question is being asked. The `String` in the question doesn't matter at all, much less the fact that `String` is final. – Ryan Stewart May 25 '13 at 21:16
  • @RyanStewart: I don't think we're going to agree here anytime soon. I think it is relevant, you don't. – Keppil May 25 '13 at 21:30
  • 1
    @Keppil: It would be possible to define a language where a non-subclassable class had different behaviour in generic subtyping to a subclassable one. That would actually be pretty useful. But Java is not such a language. Which is why it's not relevant. – Tom Anderson May 25 '13 at 21:55
  • @cmbaxter: Thank you for your hint. It was what I actually need – JoGa May 26 '13 at 11:15

4 Answers4

7

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/%3Cbb4674270605110156r4727e563of9ce24cdcb41a0c8@mail.gmail.com%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>.

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Natix
  • 14,017
  • 7
  • 54
  • 69
  • I've updated the answer with a compilable piece of code. However, I'm not sure I understand why it has to be like this. – Natix May 25 '13 at 21:41
  • 1
    Right, that's what I can't explain yet: why does it have to be `Iterator extends Entry...` instead of just `Iterator – Ryan Stewart May 25 '13 at 21:52
  • @Ryan Stewart because Entry extends String,? extends String> is a subtype of ? extends Entry extends String,? extends String>. But Entry extends String,? extends String> is not the subtype of Entry. – Apprentice Queue May 25 '13 at 22:00
  • @ApprenticeQueue: I think I agree with both of your statements. However a subtype relationship doesn't explain this because ["Subtyping does not extend through parameterized types"](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.10). By your logic, because `Integer` is a subtype of `Number`, then `Iterator i1; Iterator i2 = i1;` should be valid, and we know that's not the case. – Ryan Stewart May 25 '13 at 22:56
  • @Ryan Stewart, my argument is that your example isn't valid. – Apprentice Queue May 26 '13 at 00:10
  • @ApprenticeQueue: It's not *my* example. It's what the compiler demands. – Ryan Stewart May 26 '13 at 01:59
  • +1 The quote does explain the issue, nice find. Ignore my earlier comment - I was reading it wrong. – Paul Bellora May 26 '13 at 05:01
  • @Ryan Stewart You said "By your logic ... should be valid" and I'm saying no, that is not by my logic; my logic is the complete opposite. – Apprentice Queue May 26 '13 at 06:19
  • @ApprenticeQueue: Ryan is right that none of this explains where the `Iterator extends ...` comes from. – Tom Anderson May 26 '13 at 11:02
  • @Tom Anderson Natix didn't explain where it comes from but my comment does explains where `Iterator extends` comes from. And Ryan's subsequent comment isn't correct. And I'm not going to cut-and-paste Oracle's Java tutorial on generics here. – Apprentice Queue May 26 '13 at 16:09
  • 1
    @RyanStewart See this awesome post for a further explanation of how nested wildcards work: http://stackoverflow.com/questions/3546745/multiple-wildcards-on-a-generic-methods-makes-java-compiler-and-me-very-confu – Paul Bellora May 26 '13 at 17:52
4

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)->
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • Yeah I think it was coined here: http://www.ibm.com/developerworks/java/library/j-jtp04298/index.html#3.0 – Paul Bellora May 26 '13 at 02:43
  • It's not a very poetic name, but if it's good enough for Brian Goetz, then i guess it's good enough for me. – Tom Anderson May 26 '13 at 11:04
  • 1
    I refer to this as taming the wildcard. The idea is that by giving something a name, you bring it under your control - which i admit i lifted directly from [A Wizard of Earthsea](http://en.wikipedia.org/wiki/True_name). – Tom Anderson May 26 '13 at 11:17
1

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.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • `Iterator extends Map.Entry extends String, ? extends String>>` works, but I don't get why. Hoping someone has an answer. – Ryan Stewart May 25 '13 at 21:26
  • I'm kinda hoping @Jon Skeet sees this question as I'm sure he will know what's up. He has good insight into generics related issues. – cmbaxter May 25 '13 at 21:29
0

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.

Apprentice Queue
  • 2,036
  • 13
  • 13
  • 1
    Spot on. `? extends Foo` means "some particular subtype of foo which we don't know right here", which something that is *not* freely interchangeable with a simple `Foo`. – Tom Anderson May 25 '13 at 21:51
  • The m.add should actually be `m.put(new UpDownString(), new UpDownString());`. However, this does not answer the question why the iterator over entry set doesn't compile. – Natix May 25 '13 at 21:53
  • @Natrix, thanks. But I think it answers it because Map.Entry is not the supertype of Map.Entry extends String, ? extend String>. – Apprentice Queue May 25 '13 at 21:56
  • @Natix: The literal answer to the question is that it doesn't compile because the types on either side of the assignment are different, and the one on the right is not a subtype of the one on the left. This answer attempts to explain *why* it is not a subtype. – Tom Anderson May 25 '13 at 21:57
  • 2
    @TomAnderson: It only addresses half of the problem though: the `? extends String` vs `String` part. This is the `Entry` parameterization. But even when that's solved, there's a problem with the `Iterator` parameterization that nobody can explain yet. Why does it have to be `Iterator extends Map.Entry extends Number, ? extends Number>>` instead of just `Iterator – Ryan Stewart May 25 '13 at 22:07
  • Will m = pm even compile without a cast? – arynaq May 25 '13 at 22:26
  • @RyanStewart: True. I can't explain the other half either. Compiler bug? By this point, i would have introduced some new type variables to tame the wildcards, after doing which the problem goes away. – Tom Anderson May 25 '13 at 23:36
  • That is, if you say `public void putAll(Map m) {Iterator> iterator = m.entrySet().iterator();}`, then that compiles. Even though that looks like it's identical to the wildcard case, just with names. – Tom Anderson May 25 '13 at 23:39
  • I do wonder if this is a compiler bug. I just can't see anything in the rules or the declaration of `entrySet` that would introduce a `? extends` on the the container type, rather than its type parameters. – Tom Anderson May 25 '13 at 23:40
  • @TomAnderson: With JDK 1.7.0_21: `java: name clash: putAll(java.util.Map) in org.example.HttpQueryMap and putAll(java.util.Map extends K,? extends V>) in java.util.HashMap have the same erasure, yet neither overrides the other`. – Ryan Stewart May 26 '13 at 02:16
  • -1 This is a subtlety, so bear with me: your answer doesn't address the actual issue here, which is related to the *nested* wildcards in `Iterator>`. You only get as far as demonstrating why generics aren't covariant. – Paul Bellora May 26 '13 at 02:36
  • @Paul Bellora, I don't think it matters whether or not it is nested. – Apprentice Queue May 26 '13 at 05:38
  • It does matter. For example, this issue won't occur with `Iterator extends String> iterator = m_str.keySet().iterator();`. – Paul Bellora May 26 '13 at 05:41
  • @Paul Bellora because Iterator is subtype of Iterator extends String>. – Apprentice Queue May 26 '13 at 06:03
  • That's irrelevant. `Iterator` doesn't figure anywhere in the problem. – Paul Bellora May 26 '13 at 06:16
  • @Paul Bellora I'm referring to your example: "For example, this issue won't occur with ...". And I'm telling you why it wouldn't occur with your example. – Apprentice Queue May 26 '13 at 06:17
  • @Ryan Stewart see my comment to Natix's answer. – Apprentice Queue May 26 '13 at 06:18
  • `m_str.keySet().iterator()` does not return an `Iterator`, but I don't have time to walk you through it anymore. I recommend you look more closely at this problem with a compiler handy, and reread Natix's answer, especially the quote at the end. – Paul Bellora May 26 '13 at 06:50
  • @RyanStewart: You can get around the erasure collision by pushing this code into another method. I didn't bring that up, because i thought it would be a distraction. – Tom Anderson May 26 '13 at 11:00
  • @Paul Bellora You should read your own comments which are contradictory. It has nothing to do with wildcards nested in types. – Apprentice Queue May 26 '13 at 16:04
  • @Paul Bellora and I read that quote already and it makes sense because the compiler does not do a lexical replacement of ,?> with the types that are captured in the functions argument. Otherwise what type would the local variable `List> x;` resolve to if the arguments to the function were `Map, ?>`. – Apprentice Queue May 26 '13 at 16:22
  • @ApprenticeQueue: What we're discussing is specifically related to nested wildcards. If you don't see that, then you're talking about something different than we are. This comment chain is already really long, but I'll try one more time. Please explain or link to something else that explains why `List> l1 = new ArrayList<>(); List> l2 = l1;` isn't okay but `List> l1 = new ArrayList<>(); List extends List>> l2 = l1;` works. I sort of see the reasoning in the quote Natix found, but I still don't understand what rule makes this work. – Ryan Stewart May 26 '13 at 16:42
  • Indeed, this is getting unwieldy. You may want to continue the conversation in [chat]. – BoltClock May 26 '13 at 16:58