162

I was surprised by the fact that Map<?,?> is not a Collection<?>.

I thought it'd make a LOT of sense if it was declared as such:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

After all, a Map<K,V> is a collection of Map.Entry<K,V>, isn't it?

So is there a good reason why it's not implemented as such?


Thanks to Cletus for a most authoritative answer, but I'm still wondering why, if you can already view a Map<K,V> as Set<Map.Entries<K,V>> (via entrySet()), it doesn't just extend that interface instead.

If a Map is a Collection, what are the elements? The only reasonable answer is "Key-value pairs"

Exactly, interface Map<K,V> extends Set<Map.Entry<K,V>> would be great!

but this provides a very limited (and not particularly useful) Map abstraction.

But if that's the case then why is entrySet specified by the interface? It must be useful somehow (and I think it's easy to argue for that position!).

You can't ask what value a given key maps to, nor can you delete the entry for a given key without knowing what value it maps to.

I'm not saying that that's all there is to it to Map! It can and should keep all the other methods (except entrySet, which is redundant now)!

toniedzwiedz
  • 17,895
  • 9
  • 86
  • 131
polygenelubricants
  • 376,812
  • 128
  • 561
  • 623
  • 1
    event Set> actually – Maurice Perry Apr 16 '10 at 09:20
  • 4
    It just doesn't. It's based on an opinion (as described in the Design FAQ), rather than being a logical conclusion. For an alternative approach look at the design of containers in the C++ STL (http://www.sgi.com/tech/stl/table_of_contents.html) which is based upon Stepanov's thorough and brilliant analysis. – beldaz May 10 '13 at 20:21
  • The OP has changed their question, I'm not sure if it's permitted in the SO rules. For the impatient, there's a succinct answer to the latter question (why it can't inherit from `Set>`) in the @einpoklum's answer. But for the initial question (as in the title), I've not found any good answers here yet, except the `by design` quoted from the FAQ. – aderchox Apr 13 '21 at 14:18

10 Answers10

139

From the Java Collections API Design FAQ:

Why doesn't Map extend Collection?

This was by design. We feel that mappings are not collections and collections are not mappings. Thus, it makes little sense for Map to extend the Collection interface (or vice versa).

If a Map is a Collection, what are the elements? The only reasonable answer is "Key-value pairs", but this provides a very limited (and not particularly useful) Map abstraction. You can't ask what value a given key maps to, nor can you delete the entry for a given key without knowing what value it maps to.

Collection could be made to extend Map, but this raises the question: what are the keys? There's no really satisfactory answer, and forcing one leads to an unnatural interface.

Maps can be viewed as Collections (of keys, values, or pairs), and this fact is reflected in the three "Collection view operations" on Maps (keySet, entrySet, and values). While it is, in principle, possible to view a List as a Map mapping indices to elements, this has the nasty property that deleting an element from the List changes the Key associated with every element before the deleted element. That's why we don't have a map view operation on Lists.

Update: I think the quote answers most of the questions. It's worth stressing the part about a collection of entries not being a particularly useful abstraction. For example:

Set<Map.Entry<String,String>>

would allow:

set.add(entry("hello", "world"));
set.add(entry("hello", "world 2"));

(assuming an entry() method that creates a Map.Entry instance)

Maps require unique keys so this would violate this. Or if you impose unique keys on a Set of entries, it's not really a Set in the general sense. It's a Set with further restrictions.

Arguably you could say the equals()/hashCode() relationship for Map.Entry was purely on the key but even that has issues. More importantly, does it really add any value? You may find this abstraction breaks down once you start looking at the corner cases.

It's worth noting that the HashSet is actually implemented as a HashMap, not the other way around. This is purely an implementation detail but is interesting nonetheless.

The main reason for entrySet() to exist is to simplify traversal so you don't have to traverse the keys and then do a lookup of the key. Don't take it as prima facie evidence that a Map should be a Set of entries (imho).

Ravi K Thapliyal
  • 51,095
  • 9
  • 76
  • 89
cletus
  • 616,129
  • 168
  • 910
  • 942
  • 1
    The update is very convincing, but indeed, the Set view returned by `entrySet()` does support `remove`, while it does not support `add` (probably throws `UnsupportedException`). So I see your point, but then again, I see the OP's point, too.. (My own opinion is as stated in my own answer..) – Enno Shioji Apr 16 '10 at 10:22
  • 3
    Zwei brings up a good point. All those complications due to `add` can be handled just like how the `entrySet()` view does: allow some operations and don't allow others. A natural response, of course, is "What kind of `Set` doesn't support `add`?" -- well, if such behavior is acceptable for `entrySet()`, then why isn't it acceptable for `this`? That said, I _am_ mostly convinced already of why this is not such a great idea as I once thought, but I still think it's worthy of further debate, if only to enrich my own understanding of what makes a good API/OOP design. – polygenelubricants Apr 17 '10 at 02:37
  • 6
    The first paragraph of the cite from the FAQ is funny: "We feel... thus... makes little sense". I don't feel that "feel" and "sense" form a conclusion ;o) – Peter Wippermann Aug 29 '12 at 09:32
  • *Collection could be made to extend Map*? Not sure, why author thinks for `Collection` extend `Map`? – overexchange Sep 20 '17 at 20:13
  • For some reasons the OP changed their question from "why it can't be a collection" to "why it can't be a set" in their updates, and it seems that the main question is still unanswered. For the latter question, I believe you are answering it clearly, but for the former (initial) question of the OP, I see no reason why it can't be that way. Would someone explain? ** If the Map is a collection of "tuples" of keys and values, then, e.g., it can override the collection's "add" method and implement it like the current "put" method of the Map class. Right? ** Maybe it's as the FAQ said `by design`? . – aderchox Apr 13 '21 at 14:11
  • What does this part mean: `You can't ask what value a given key maps to, nor can you delete the entry for a given key without knowing what value it maps to.` ?!? – aderchox Apr 13 '21 at 14:23
  • 2
    @aderchox if you have a `Set>`, you can’t ask whether it contains a particular `K`, as the contract of `contains` requires to return `true` if the particular object is within the set, which must be an instance of `Pair` having the same `K` *and* `V` to match. The same applies to the contract of `remove`. This is exactly the behavior of the set returned by `entrySet()` whose `contains` and `remove` methods operate in terms of `Map.Entry` elements. Which is an entirely different API than `Map` itself, which has `containsKey`, `remove(key)`, etc, which is what this is all about.. – Holger Nov 18 '21 at 09:46
12

While you've gotten a number of answers that cover your question fairly directly, I think it might be useful to step back a bit, and look at the question a bit more generally. That is, not to look specifically at how the Java library happens to be written, and look at why it's written that way.

The problem here is that inheritance only models one type of commonality. If you pick out two things that both seem "collection-like", you can probably pick out a 8 or 10 things they have in common. If you pick out a different pair of "collection-like" things, they'll also 8 or 10 things in common -- but they won't be the same 8 or 10 things as the first pair.

If you look at a dozen or so different "collection-like" things, virtually every one of them will probably have something like 8 or 10 characteristics in common with at least one other one -- but if you look at what's shared across every one of them, you're left with practically nothing.

This is a situation that inheritance (especially single inheritance) just doesn't model well. There's no clean dividing line between which of those are really collections and which aren't -- but if you want to define a meaningful Collection class, you're stuck with leaving some of them out. If you leave only a few of them out, your Collection class will only be able to provide quite a sparse interface. If you leave more out, you'll be able to give it a richer interface.

Some also take the option of basically saying: "this type of collection supports operation X, but you're not allowed to use it, by deriving from a base class that defines X, but attempting to use the derived class' X fails (e.g., by throwing an exception).

That still leaves one problem: almost regardless of which you leave out and which you put in, you're going to have to draw a hard line between what classes are in and what are out. No matter where you draw that line, you're going to be left with a clear, rather artificial, division between some things that are quite similar.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
11

I guess the why is subjective.

In C#, I think Dictionary extends or at least implements a collection:

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

In Pharo Smalltak as well:

Collection subclass: #Set
Set subclass: #Dictionary

But there is an asymmetry with some methods. For instance, collect: will takes association (the equivalent of an entry), while do: take the values. They provide another method keysAndValuesDo: to iterate the dictionary by entry. Add: takes an association, but remove: has been "suppressed":

remove: anObject
self shouldNotImplement 

So it's definitively doable, but leads to some other issues regarding the class hierarchy.

What is better is subjective.

Lawrence Dol
  • 63,018
  • 25
  • 139
  • 189
ewernli
  • 38,045
  • 5
  • 92
  • 123
3

The answer of cletus is good, but I want to add a semantic approach. To combine both makes no sense, think of the case you add a key-value-pair via the collection interface and the key already exists. The Map-interface allows only one value associated with the key. But if you automatically remove the existing entry with the same key, the collection has after the add the same size as before - very unexpected for a collection.

Mnementh
  • 50,487
  • 48
  • 148
  • 202
3

Map<K,V> should not extend Set<Map.Entry<K,V>> since:

  • You can't add different Map.Entrys with the same key to the same Map, but
  • You can add different Map.Entrys with the same key to the same Set<Map.Entry>.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
3

Java collections are broken. There is a missing interface, that of Relation. Hence, Map extends Relation extends Set. Relations (also called multi-maps) have unique name-value pairs. Maps (aka "Functions"), have unique names (or keys) which of course map to values. Sequences extend Maps (where each key is an integer > 0). Bags (or multi-sets) extend Maps (where each key is an element and each value is the number of times the element appears in the bag).

This structure would allow intersection, union etc. of a range of "collections". Hence, the hierarchy should be:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun/Oracle/Java ppl - please get it right next time. Thanks.

xagyg
  • 9,562
  • 2
  • 32
  • 29
  • 4
    I'd like to see the API's for these imaginary interfaces detailed. Not sure I'd like a Sequence (aka List) where the key was an Integer; that sounds like a huge performance hit. – Lawrence Dol Aug 27 '10 at 03:13
  • That's right, a sequence is a list - hence Sequence extends List. I do not know if you can access this, but it may be interesting http://portal.acm.org/citation.cfm?id=1838687.1838705 – xagyg Oct 27 '10 at 02:48
  • @SoftwareMonkey here is another link. Last link is the link to the javadoc of the API. http://zedlib.sourceforge.net/ – xagyg Dec 05 '12 at 22:18
  • @SoftwareMonkey I unashamedly admit that design is my primary concern here. I am assuming the gurus at Oracle/Sun could optimize the heck out of it. – xagyg Dec 05 '12 at 22:23
  • I tend to disagree. How can it hold that "Map is-a Set" if you can't always add non-member elements of the domain to your set (as in the example in @cletus' answer). – einpoklum Feb 21 '15 at 18:42
  • @einpoklum: this can be interpreted as two entries with the same key are equal members in the context of a map. – Dávid Horváth Dec 15 '16 at 02:17
1

If you look at the respective data structure you can easily guess why Map is not a part of Collection. Each Collection stores a single value where as a Map stores key-value pair. So methods in Collection interface are incompatible for Map interface. For example in Collection we have add(Object o). What would be such implementation in Map. It doesn't make sense to have such a method in Map. Instead we have a put(key,value) method in Map.

Same argument goes for addAll(), remove(), and removeAll() methods. So the main reason is the difference in the way data is stored in Map and Collection. Also if you recall Collection interface implemented Iterable interface i.e. any interface with .iterator() method should return an iterator which must allow us to iterate over the values stored in the Collection. Now what would such method return for a Map? Key iterator or a Value iterator? This does not make sense either.

There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.

RamenChef
  • 5,557
  • 11
  • 31
  • 43
Mayur Ingle
  • 85
  • 2
  • 7
  • `There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.` Can you show a code example for this? – RamenChef Oct 04 '16 at 16:47
  • That was very well explained. I get it now. Thanks! – Emir Memic May 20 '17 at 15:08
  • An implementation of ``add(Object o)`` would be to add an ``Entry`` object. A ``Map`` can be considered a ``Collection of Tuples`` – LIvanov Oct 18 '17 at 09:48
0

Exactly, interface Map<K,V> extends Set<Map.Entry<K,V>> would be great!

Actually, if it were implements Map<K,V>, Set<Map.Entry<K,V>>, then I tend to agree.. It seems even natural. But that doesn't work very well, right? Let's say we have HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V> etc... that is all good, but if you had entrySet(), nobody will forget to implement that method, and you can be sure that you can get entrySet for any Map, whereas you aren't if you are hoping that the implementor has implemented both interfaces...

The reason I don't want to have interface Map<K,V> extends Set<Map.Entry<K,V>> is simply, because there will be more methods. And after all, they are different things, right? Also very practically, if I hit map. in IDE, I don't want to see .remove(Object obj), and .remove(Map.Entry<K,V> entry) because I can't do hit ctrl+space, r, return and be done with it.

Enno Shioji
  • 26,542
  • 13
  • 70
  • 109
  • It seems to me that if one could claim, semantically, that "A Map is-a Set on Map.Entry's" then one would bother implementing the relevant method; and if one can't make that statement, then one wouldn't - i.e. it should be about the bother. – einpoklum Feb 21 '15 at 18:42
0
  1. The Map interface in Java follows key/value pair structure whereas the Collection interface is collection of objects which are stored in structure with a specified mechanism.
  2. The main reason map doesn't extend Collection interface is that add(E e) method of collection interface doesn't support key-value pair like map interface's put(k,v) method.
  3. It might not extend Collection interface but still an integral part of Java Collection framework
-2

Straight and simple. Collection is an interface which is expecting only one Object, whereas Map requires Two.

Collection(Object o);
Map<Object,Object>
Pang
  • 9,564
  • 146
  • 81
  • 122
Trinadh Koya
  • 1,089
  • 15
  • 19