Чому Java Map не розширює колекцію?


146

Мене здивувало те, що Map<?,?>не є Collection<?>.

Я думав, що це буде мати багато сенсу, якщо це буде оголошено таким:

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

Зрештою, Map<K,V>це колекція Map.Entry<K,V>, чи не так?

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


Дякую Клетусу за найавторитетнішу відповідь, але мені все ще цікаво, чому, якщо ви вже можете переглянути 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, які зараз є зайвими)!


1
Подія Установити <Map.Entry <K, V >> фактично
Моріс Перрі

3
Це просто ні. Він заснований на думці (як описано в FAQ FAQ), а не на логічному висновку. Для альтернативного підходу подивіться на дизайн контейнерів у C ++ STL ( sgi.com/tech/stl/table_of_contents.html ), який базується на ретельному та блискучому аналізі Степанова.
beldaz

Відповіді:


123

З питань FAQ щодо дизайну API API API :

Чому колекція карт не розширює?

Це було задумом. Ми вважаємо, що відображення не колекції, а колекції - не відображення. Таким чином, Map має мало сенсу розширювати інтерфейс колекції (або навпаки).

Якщо карта - це колекція, що це за елементи? Єдина обґрунтована відповідь - "Пари ключових значень", але це забезпечує дуже обмежену (і не особливо корисну) абстракцію карти. Ви не можете запитати, на яке значення відображається певний ключ, а також не можна видалити запис для даного ключа, не знаючи, на яке значення воно відображається.

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

Карти можна розглядати як Колекції (ключів, значень чи пар), і цей факт відображений у трьох "Операціях перегляду колекції" на Картах (keySet, entrySet та значень). Хоча, в принципі, можна переглянути список як картографічне відображення індексів для елементів, це має неприємне властивість, що при видаленні елемента зі списку змінюється ключ, пов'язаний з кожним елементом перед видаленим елементом. Ось чому ми не маємо операції перегляду карти на Списках.

Оновлення: Я думаю, що цитата відповідає на більшість питань. Варто наголосити на тому, що збірка записів не є особливо корисною абстракцією. Наприклад:

Set<Map.Entry<String,String>>

дозволить:

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

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

Maps потрібні унікальні ключі, щоб це порушило це. Або якщо ви накладете унікальні ключі на Setзаписи, це насправді не Setв загальному сенсі. Це Setз подальшими обмеженнями.

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

Варто зазначити, що HashSetреально реалізується як, а HashMapне навпаки. Це суто детальна реалізація, але все ж цікава.

Основна причина entrySet()існування - це спростити обхід, щоб вам не довелося перебирати ключі, а потім робити пошук ключа. Не сприймайте це як фактичне підтвердження того, що а Mapмає бути записом Set(імхо).


1
Оновлення є дуже переконливим, але насправді перегляд Set повертається entrySet()підтримкою remove, хоча він не підтримує add(ймовірно, кидає UnsupportedException). Тож я бачу вашу думку, але знову ж таки, я бачу і пункт ОП .. (Моя власна думка така, як сказано в моїй власній відповіді ..)
Енно Шіоджі

1
Цвай підводить хороший момент. З усіма цими ускладненнями addможна впоратися так само, як це entrySet()робить погляд: дозволяти деякі операції, а інші не дозволяти. Природною відповіддю, звичайно, є: "Що Setне підтримує add?" - ну, якщо така поведінка прийнятна entrySet(), то чому вона не прийнятна this? Те є , я маю в основному переконаний вже про те, чому це не така велика ідея , як я коли - то думки, але я до сих пір вважаю , що це гідно подальшого обговорення, якщо тільки збагатити своє власне розуміння того , що робить дизайн хороший API / ООП.
полігенмастильні матеріали

3
Перший абзац цитування з FAQ є смішним: "Ми відчуваємо ... таким чином ... мало сенсу". Я не відчуваю, що "почуття" та "сенс" формують висновок; o)
Пітер Віпперманн

Колекцію можна зробити для розширення карти ? Не впевнений, чому автор думає про Collectionрозширення Map?
переобмін обміну

11

Хоча ви отримали ряд відповідей, які доволі безпосередньо висвітлюють ваше запитання, я думаю, що це може бути корисним трохи відступити назад і поглянути на питання трохи більш загально. Тобто, не дивитись конкретно на те, як написано бібліотеку Java, а дивитись, чому вона написана саме так.

Проблема тут полягає в тому, що успадкування моделює лише один тип спільності. Якщо ви виберете дві речі, які здаються "схожими на колекцію", ви, ймовірно, можете вибрати 8 або 10 речей, які мають спільне. Якщо ви виберете іншу пару "схожих на колекцію" речей, вони також матимуть 8 або 10 речей, але вони не будуть такими ж, 8 або 10 речей, як перша пара.

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

Це ситуація, коли успадкування (особливо одинакове успадкування) просто не добре моделює. Немає чіткої лінії розмежування між тим, що є насправді колекціями, а які - ні, але якщо ви хочете визначити змістовний клас колекції, вам не доводиться залишати деякі з них. Якщо ви залишите лише декілька з них, ваш колекційний клас зможе забезпечити лише досить рідкий інтерфейс. Якщо ви залишите більше, ви зможете надати їй багатший інтерфейс.

Деякі також приймають варіант, в основному говорячи: "Цей тип колекції підтримує операцію 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 

Отже, це остаточно можливо, але призводить до деяких інших питань щодо ієрархії класів.

Що краще - це суб’єктивне.


3

Відповідь клетуса хороша, але я хочу додати семантичний підхід. Поєднувати обидва немає сенсу, придумайте випадок, коли ви додаєте пару ключів-значень через інтерфейс колекції, і ключ вже існує. Map-інтерфейс дозволяє лише одне значення, пов'язане з ключем. Але якщо ви автоматично видалите існуючий запис тим самим ключем, колекція після додавання має той же розмір, що і раніше - дуже несподівано для колекції.


4
Ось як Setпрацює, і Set це здійснити Collection.
Лій

2

Колекції Java розбиті. Немає інтерфейсу, що стосується відносин. Значить, Карта продовжує Відносини розширює Набір. Відносини (їх також називають мульти-картами) мають унікальні пари імен-значення. Карти (він же "Функції") мають унікальні імена (або клавіші), які, звичайно, відображають значення. Послідовності розширюють Карти (де кожна клавіша є цілим числом> 0). Мішки (або кілька наборів) розширюють Карти (де кожен ключ є елементом, і кожне значення - це кількість разів, коли елемент з’являється в сумці).

Ця структура дозволила б перетинати, об'єднати і т.д. цілого ряду "колекцій". Отже, ієрархія повинна бути:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - будь ласка, будь ласка, наступного разу. Дякую.


4
Я хотів би детально ознайомитись з API для цих уявних інтерфейсів. Не впевнений, що мені хотілося б послідовність (він же список), де ключем був цілий ряд; це звучить як величезний хіт на виставу.
Лоуренс Дол

Правильно, послідовність є списком - отже, послідовність розширює список. Я не знаю, чи можете ви отримати доступ до цього, але це може бути цікаво portal.acm.org/citation.cfm?id=1838687.1838705
xagyg

@SoftwareMonkey тут є ще одне посилання. Останнє посилання - це посилання на javadoc API. zedlib.sourceforge.net
xagyg

@SoftwareMonkey Я безсоромно визнаю, що дизайн - це моя головна проблема. Я припускаю, що гуру в Oracle / Sun можуть оптимізувати чорт із цього.
xagyg

Я схильний не погоджуватися. Як можна вважати, що "Map is - a Set", якщо ви не завжди можете додати до свого набору елементи, що не належать до домену (як у прикладі у відповіді @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фреймворку.


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's», тоді б заважало впроваджувати відповідний метод; і якщо хтось не може зробити це твердження, то цього не зробить - тобто це повинно бути занепокоєним.
einpoklum

0

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

  • Ви не можете додавати різні Map.Entrys одним і тим же ключем до одного і того ж ключа Map, але
  • До одного і того ж ключа можна додати різні Map.Entrys Set<Map.Entry>.

... це стислий вислів сутінки другої частини відповіді @cletus.
einpoklum

-2

Прямий і простий. Колекція - це інтерфейс, який очікує лише одного Об'єкта, тоді як для карти потрібні два.

Collection(Object o);
Map<Object,Object>

Приємний Trinad, ваші відповіді завжди прості та короткі.
Сайрам
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.