Почему Java Map не расширяет коллекцию? - PullRequest
137 голосов
/ 16 апреля 2010

Я был удивлен тем фактом, что 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()), он не просто расширяет этот интерфейс.

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

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

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

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

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

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

Ответы [ 9 ]

119 голосов
/ 16 апреля 2010

Из часто задаваемых вопросов по разработке API Java Collections :

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

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

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

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

Карты можно просматривать как коллекции (из ключи, значения или пары), и этот факт нашло отражение в трёх "Сборниках" просмотр операций "на Картах (keySet, entrySet и значения). Пока это есть, в Принцип, можно просмотреть список как Карта отображения индексов на элементы, это имеет неприятное свойство, которое удаление элемента из списка изменяет ключ, связанный с каждым элемент перед удаленным элементом. Вот почему у нас нет вида карты операция над списками.

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

Set<Map.Entry<String,String>>

позволит:

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

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

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

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

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

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

10 голосов
/ 16 апреля 2010

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

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

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

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

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

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

10 голосов
/ 16 апреля 2010

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

В 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: принимает ассоциацию (эквивалент записи), а do: принимает значения. Они предоставляют другой метод keysAndValuesDo: для итерации словаря по записи. Add: принимает ассоциацию, но remove: был "подавлен":

remove: anObject
self shouldNotImplement 

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

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

3 голосов
/ 16 апреля 2010

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

2 голосов
/ 07 июля 2010

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

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

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

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

1 голос
/ 04 октября 2016

Если вы посмотрите на соответствующую структуру данных, вы можете легко догадаться, почему 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? Ключевой итератор или итератор значения? Это тоже не имеет смысла.

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

0 голосов
/ 24 марта 2017

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

Collection(Object o);
Map<Object,Object>
0 голосов
/ 21 февраля 2015

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

  • Вы не можете добавить разные Map.Entry с одним и тем же ключом к тому же Map, но
  • Вы можете добавлять разные Map.Entry с одним и тем же ключом к одному и тому же Set<Map.Entry>.
0 голосов
/ 16 апреля 2010

Точно, 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 и покончить с этим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...