Почему Java Map не расширяет коллекцию?

146

Я был удивлен тем, что Map<?,?>не является Collection<?>.

Я думал, что было бы много смысла, если бы это было объявлено так:

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

В конце концов, это Map<K,V>коллекция Map.Entry<K,V>, не так ли?

Так есть ли веская причина, почему это не реализовано как таковое?


Спасибо Cletus за самый авторитетный ответ, но мне все еще интересно, почему, если вы уже можете просматривать Map<K,V>как Set<Map.Entries<K,V>>(через entrySet()), он не просто расширяет этот интерфейс.

Если a Mapявляется a Collection, каковы элементы? Единственный разумный ответ - «Пары ключ-значение»

Точно, interface Map<K,V> extends Set<Map.Entry<K,V>>было бы здорово!

но это обеспечивает очень ограниченную (и не особо полезную) Mapабстракцию.

Но если это так, то почему entrySetуказывается интерфейс? Это должно быть как-то полезно (и я думаю, что легко отстаивать эту позицию!).

Вы не можете спросить, к какому значению относится данный ключ, и не можете удалить запись для данного ключа, не зная, к какому значению он соответствует.

Я не говорю, что это все, что нужно сделать Map! Он может и должен сохранять все другие методы (кроме entrySet, который сейчас избыточен)!

polygenelubricants
источник
1
Набор событий <Map.Entry <K, V >> на самом деле
Морис Перри
3
Это просто не так. Он основан на мнении (как описано в FAQ по дизайну), а не на логическом заключении. Для альтернативного подхода посмотрите на дизайн контейнеров в C ++ STL ( sgi.com/tech/stl/table_of_contents.html ), который основан на тщательном и блестящем анализе Степанова.
Белдаз

Ответы:

123

Из FAQ по разработке API коллекций Java :

Почему карта не расширяет коллекцию?

Это было разработано. Мы считаем, что отображения не являются коллекциями, а коллекции не являются отображениями. Таким образом, для Map не имеет смысла расширять интерфейс Collection (или наоборот).

Если карта является коллекцией, каковы элементы? Единственный разумный ответ - «Пары ключ-значение», но это обеспечивает очень ограниченную (и не особо полезную) абстракцию карты. Вы не можете спросить, к какому значению относится данный ключ, и не можете удалить запись для данного ключа, не зная, к какому значению он соответствует.

Можно собрать коллекцию для расширения Map, но возникает вопрос: каковы ключи? Там нет действительно удовлетворительного ответа, и принуждение приводит к неестественному интерфейсу.

Карты можно просматривать как Коллекции (ключей, значений или пар), и этот факт отражен в трех «Операциях просмотра коллекций» на Картах (keySet, entrySet и values). Хотя в принципе можно просматривать список как индексы сопоставления карты с элементами, у него есть неприятное свойство, заключающееся в том, что удаление элемента из списка изменяет ключ, связанный с каждым элементом перед удаленным элементом. Вот почему у нас нет операции просмотра карты в списках.

Обновление: я думаю, что цитата отвечает на большинство вопросов. Стоит подчеркнуть, что коллекция записей не является особенно полезной абстракцией. Например:

Set<Map.Entry<String,String>>

разрешит:

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

(предполагая entry()метод, который создает Map.Entryэкземпляр)

MapТребуются уникальные ключи, так что это нарушит это. Или, если вы наложите уникальные ключи на Setзаписи, это не совсем Setв общем смысле. Это Setс дополнительными ограничениями.

Можно сказать, что отношение equals()/ было чисто ключевым, но даже в этом есть проблемы. Что еще более важно, действительно ли это добавляет ценность? Вы можете обнаружить, что эта абстракция рушится, когда вы начинаете смотреть на угловые случаи.hashCode()Map.Entry

Стоит отметить, что на HashSetсамом деле реализован как, а HashMapне наоборот. Это чисто деталь реализации, но, тем не менее, интересно.

Основная причина entrySet()существования - это упрощение обхода, чтобы вам не приходилось обходить ключи, а затем выполнять поиск ключа. Не принимайте это как доказательство prima facie, что a Mapдолжно быть Setиз записей (imho).

Клетус
источник
1
Обновление очень убедительно, но, действительно, представление Set, возвращаемое функцией entrySet()действительно, поддерживает remove, хотя и не поддерживает add(вероятно, выдает UnsupportedException). Итак, я понимаю вашу точку зрения, но опять же, я также вижу точку зрения ОП .. (Мое собственное мнение соответствует изложенному в моем собственном ответе ..)
Энно Сиоджи,
1
Цвей поднимает хороший вопрос. Все эти сложности addмогут быть обработаны так же, как это entrySet()делает вид: разрешить одни операции и не разрешить другие. Естественным ответом, конечно же, является «Какого родаSet не поддерживает add?» - хорошо, если такое поведение приемлемо entrySet(), то почему оно не приемлемо для this? То есть , я имею в основном убежден уже о том, почему это не такая большая идея , как я когда - то мысли, но я до сих пор считаю , что это достойно дальнейшего обсуждения, если только обогатить свое собственное понимание того , что делает дизайн хороший API / ООП.
полигенасмазочные материалы
3
Первый абзац цитаты из FAQ смешной: «Мы чувствуем ... таким образом ... мало смысла». Я не чувствую, что «чувство» и «чувство» формируют заключение; о)
Питер Випперманн
Коллекция может быть сделана для расширения карты ? Не уверен, почему автор думает о Collectionрасширении Map?
сверхобмена
11

Несмотря на то, что вы получили несколько ответов, которые достаточно прямо касаются вашего вопроса, я думаю, что было бы полезно немного отступить назад и взглянуть на вопрос более широко. То есть не смотреть конкретно на то, как написана библиотека Java, и смотреть, почему она написана именно так.

Проблема здесь в том, что наследование моделирует только один тип общности. Если вы выберете две вещи, которые кажутся «похожими на коллекцию», вы можете выбрать 8 или 10 общих для них вещей. Если вы выберете другую пару «коллекционных» вещей, у них также будет 8 или 10 общих черт, но они не будут такими же, как в первой паре.

Если вы посмотрите на дюжины различных «коллекции, как» вещи, практически каждый из них, вероятно , что - то вроде 8 или 10 общих характеристик, по меньшей мере , один другой , - но если вы посмотрите на то , что общими для каждого одного из них у вас осталось практически ничего.

Это ситуация, когда наследование (особенно одиночное наследование) просто не очень хорошо моделируется. Нет четкой грани между тем, какие из них действительно являются коллекциями, а какие нет, но если вы хотите определить значимый класс Collection, вы застряли на том, чтобы пропустить некоторые из них. Если вы пропустите только несколько из них, ваш класс Collection сможет обеспечить лишь довольно разреженный интерфейс. Если вы оставите больше, вы сможете сделать его более насыщенным.

Некоторые также используют вариант, в основном говоря: «этот тип коллекции поддерживает операцию X, но вы не можете использовать ее, наследуя от базового класса, который определяет X, но пытаясь использовать производный класс« X, не удается (например, , бросив исключение).

Это по-прежнему оставляет одну проблему: почти независимо от того, что вы пропускаете и что вставляете, вам придется провести жесткую грань между тем, какие классы входят, а какие нет. Неважно, где вы проведете эту черту, вы будете иметь четкое, довольно искусственное разделение между некоторыми вещами, которые очень похожи.

Джерри Гроб
источник
10

Я думаю, почему это субъективно.

Я думаю, что в C # Dictionaryрасширяет или, по крайней мере, реализует коллекцию:

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

В Pharo Smalltak также:

Collection subclass: #Set
Set subclass: #Dictionary

Но есть асимметрия с некоторыми методами. Например, collect:will принимает ассоциацию (эквивалент записи), а do:принимает значения. Они предоставляют другой метод keysAndValuesDo:для перебора словаря по записи. Add:принимает ассоциацию, но remove:был «подавлен»:

remove: anObject
self shouldNotImplement 

Так что это окончательно выполнимо, но приводит к некоторым другим проблемам, касающимся иерархии классов.

Что лучше, так это субъективно.

ewernli
источник
3

Ответ Cletus хорош, но я хочу добавить семантический подход. Объединять оба не имеет смысла, подумайте о случае, когда вы добавляете пару ключ-значение через интерфейс коллекции, и ключ уже существует. Map-интерфейс допускает только одно значение, связанное с ключом. Но если вы автоматически удалите существующую запись с тем же ключом, то после добавления коллекция будет иметь тот же размер, что и раньше - что очень неожиданно для коллекции.

Mnementh
источник
4
Вот как Setработает, и Set это осуществить Collection.
Лий
2

Коллекции Java не работают. Отсутствует интерфейс Relation. Следовательно, карта расширяет отношение расширяет набор. Отношения (также называемые мульти-картами) имеют уникальные пары имя-значение. Карты (или «Функции») имеют уникальные имена (или ключи), которые, конечно, соответствуют значениям. Последовательности расширяют карты (где каждый ключ является целым числом> 0). Сумки (или множественные наборы) расширяют Карты (где каждая клавиша является элементом, а каждое значение - количество раз, которое элемент появляется в сумке).

Эта структура позволила бы пересечение, объединение и т. Д. Ряда «коллекций». Следовательно, иерархия должна быть:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - пожалуйста, сделайте это правильно в следующий раз. Спасибо.

xagyg
источник
4
Я хотел бы видеть API для этих воображаемых интерфейсов детально. Не уверен, что мне нужна последовательность (aka List), где ключом является целое число; это звучит как огромный хит производительности.
Лоуренс Дол
Правильно, последовательность представляет собой список - следовательно, последовательность расширяет список. Я не знаю, можете ли вы получить доступ к этому, но это может быть интересно portal.acm.org/citation.cfm?id=1838687.1838705
xagyg
@SoftwareMonkey вот еще одна ссылка. Последняя ссылка - это ссылка на javadoc API. zedlib.sourceforge.net
xagyg
@ SoftwareMonkey Я смущенно признаю, что дизайн - моя главная задача здесь. Я предполагаю, что гуру в Oracle / Sun могли бы оптимизировать его.
xagyg
Я склонен не соглашаться Как он может хранить эту «Карта - это Набор», если вы не можете всегда добавлять элементы, не являющиеся членами домена, в свой набор (как в примере из ответа @cletus).
einpoklum
1

Если вы посмотрите на соответствующую структуру данных, вы легко сможете догадаться, почему Mapона не является частью Collection. Каждый Collectionхранит одно значение, где в качестве Mapпары ключ-значение. Поэтому методы Collectionинтерфейса несовместимы с Mapинтерфейсом. Например, у Collectionнас есть add(Object o). Что бы такое внедрение в Map. Не имеет смысла иметь такой метод в Map. Вместо этого у нас есть put(key,value)метод в Map.

Тот же аргумент относится и к addAll(), remove()и removeAll()методы. Таким образом, основная причина заключается в разнице в способе хранения данных в Mapи Collection. Также, если вы помните Collectionинтерфейс, реализованный Iterableинтерфейсом, т.е. любой интерфейс с .iterator()методом должен возвращать итератор, который должен позволять нам перебирать значения, хранящиеся в Collection. Теперь, что такой метод вернул бы для Map? Ключевой итератор или итератор значения? Это тоже не имеет смысла.

Существуют способы, с помощью которых мы можем перебирать хранилища ключей и значений в a, Mapи это является частью Collectionфреймворка.

Mayur Ingle
источник
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
Это было очень хорошо объяснено. Я понял теперь. Спасибо!
Эмир Мемик
Реализация add(Object o)будет добавить Entry<ClassA, ClassB>объект. А Mapможно считатьCollection of Tuples
Л.Иванова
0

Точно, interface Map<K,V> extends Set<Map.Entry<K,V>>было бы здорово!

На самом деле, если бы это было так implements Map<K,V>, Set<Map.Entry<K,V>>, то я склонен согласиться .. Это кажется даже естественным. Но это не очень хорошо работает, верно? Допустим, у нас есть HashMap implements Map<K,V>, Set<Map.Entry<K,V>, и LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>т.д ... это все хорошо, но если бы у вас былоentrySet() , никто не забудет реализовать этот метод, и вы можете быть уверены, что вы можете получить entrySet для любой карты, в то время как это не так, если вы надеетесь что разработчик реализовал оба интерфейса ...

Причина, по которой я не хочу этого, interface Map<K,V> extends Set<Map.Entry<K,V>>заключается в простом, потому что будет больше методов. А ведь это разные вещи, верно? Также очень практично, если я попал map.в IDE, я не хочу видеть .remove(Object obj), и .remove(Map.Entry<K,V> entry)потому что я не могу сделать hit ctrl+space, r, returnи быть с этим покончено.

Энно Сиоджи
источник
Мне кажется, что если бы семантически можно было утверждать, что «Карта - это набор на Map.Entry», то можно было бы заняться реализацией соответствующего метода; и если кто-то не может сделать это заявление, то не будет - то есть это должно быть о беспокойстве.
einpoklum
0

Map<K,V>не должен распространяться, Set<Map.Entry<K,V>>так как:

  • Вы не можете добавить разные Map.Entrys с тем же ключом к тому же Map, но
  • Вы можете добавить разные Map.Entrys с тем же ключом к тому же Set<Map.Entry>.
einpoklum
источник
... это краткое изложение сути второй части ответа @cletus.
einpoklum
-2

Прямо и просто. Коллекция - это интерфейс, который ожидает только один объект, тогда как для карты требуется два.

Collection(Object o);
Map<Object,Object>
Тринад Койя
источник
Хорошая Тринада, ваши ответы всегда просты и кратки.
Сайрам