По каким причинам Map.get (Object key) не является (полностью) универсальным

java generics collections map

51358 просмотра

11 ответа

Каковы причины, по которым принято решение не иметь полностью общий метод get в интерфейсе java.util.Map<K, V>.

Чтобы прояснить вопрос, подпись метода

V get(Object key)

вместо

V get(K key)

и мне интересно, почему (то же самое для remove, containsKey, containsValue).

Автор: WMR Источник Размещён: 22.10.2019 02:17

Ответы (11)


258 плюса

Решение

Как уже упоминалось другими, причина get()и т. Д. Не является общей, поскольку ключ получаемой записи не обязательно должен совпадать с типом объекта, которому вы передаете get(); спецификация метода требует только, чтобы они были равны. Это следует из того, как equals()метод принимает Object в качестве параметра, а не просто того же типа, что и объект.

Хотя обычно может быть верно, что многие классы equals()определили так, что его объекты могут быть равны только объектам своего собственного класса, в Java есть много мест, где это не так. Например, в спецификации для List.equals()говорится, что два объекта List равны, если они оба являются списками и имеют одинаковое содержимое, даже если они являются разными реализациями List. Итак, возвращаясь к примеру в этом вопросе, в соответствии со спецификацией метода можно иметь a Map<ArrayList, Something>и для меня вызов get()с LinkedListаргументом as, и он должен получить ключ, представляющий собой список с тем же содержимым. Это было бы невозможно, если бы они get()были общими и ограничивали тип аргумента.

Автор: newacct Размещён: 13.05.2009 05:14

105 плюса

Кевин Бурриллион (Kevin Bourrillion), замечательный Java-кодер, написал об этой проблеме в своем блоге некоторое время назад (по общему признанию, Setвместо Map). Наиболее актуальное предложение:

Как правило, методы платформы Java Collections Framework (и библиотеки Google Collections тоже) никогда не ограничивают типы своих параметров, кроме случаев, когда это необходимо для предотвращения повреждения коллекции.

Я не совсем уверен, что согласен с этим в качестве принципа - например, .NET, кажется, хорошо, требуя правильный тип ключа - но стоит следовать рассуждениям в блоге. (Упомянув .NET, стоит объяснить, что одна из причин, по которой это не является проблемой в .NET, заключается в том, что в .NET существует большая проблема более ограниченной дисперсии ...)

Автор: Jon Skeet Размещён: 13.05.2009 11:42

29 плюса

Договор выражается таким образом:

Более формально, если эта карта содержит отображение ключа k на значение v, такое что (key == null? K == null: key.equals (k) ), то этот метод возвращает v; в противном случае возвращается ноль. (Может быть не более одного такого отображения.)

(мой акцент)

и как таковой, успешный поиск ключа зависит от реализации входного ключа метода равенства. Это не обязательно зависит от класса k.

Автор: Brian Agnew Размещён: 13.05.2009 11:34

17 плюса

Это применение Закона Постеля: «Будь консервативен в том, что ты делаешь, будь либеральным в том, что ты принимаешь от других».

Проверка на равенство может быть выполнена независимо от типа; equalsметод определен на Objectклассе и принимает любые в Objectкачестве параметра. Таким образом, имеет смысл для эквивалентности ключей и операций, основанных на эквивалентности ключей, принимать любой Objectтип.

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

Автор: erickson Размещён: 25.06.2009 07:24

12 плюса

Я думаю, что этот раздел Общего руководства объясняет ситуацию (мой акцент):

«Необходимо убедиться, что универсальный API не является чрезмерно ограничительным; он должен продолжать поддерживать первоначальный контракт API. Рассмотрим еще несколько примеров из java.util.Collection. Предварительный универсальный API выглядит следующим образом:

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

Наивная попытка сделать это:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

Хотя это, безусловно, безопасность типа, это не соответствует первоначальному контракту API. Метод containsAll () работает с любым видом входящей коллекции. Он будет успешным, только если входящая коллекция действительно содержит только экземпляры E, но:

  • Статический тип входящей коллекции может отличаться, возможно, потому, что вызывающая сторона не знает точный тип передаваемой коллекции, или, возможно, потому что это Collection , где S является подтипом E.
  • Абсолютно законно вызывать containsAll () с коллекцией другого типа. Процедура должна работать, возвращая ложь ".
Автор: Yardena Размещён: 14.05.2009 02:16

6 плюса

Причина в том, что сдерживание определяется equalsи hashCodeкакие методы Objectи оба принимают Objectпараметр. Это был ранний недостаток дизайна в стандартных библиотеках Java. В сочетании с ограничениями в системе типов Java он заставляет все, что полагается на equals и hashCode, принимать Object.

Единственный способ иметь тип безопасных хэш - таблицы и равенство в Java является сторониться Object.equalsи Object.hashCodeи использовать общий заменитель. Функциональная Java поставляется с классами типов как раз для этой цели: Hash<A>и Equal<A>. Предусмотрена оболочка для, HashMap<K, V>которая принимает Hash<K>и Equal<K>в своем конструкторе. Поэтому этот класс getи containsметоды принимают универсальный аргумент типа K.

Пример:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error
Автор: Apocalisp Размещён: 13.05.2009 01:38

4 плюса

Есть еще одна веская причина, это не может быть сделано технически, потому что это нарушает Map.

У Java есть полиморфная общая конструкция, подобная <? extends SomeClass>. Отмеченная такая ссылка может указывать на тип со знаком <AnySubclassOfSomeClass>. Но полиморфный универсальный делает эту ссылку только для чтения . Компилятор позволяет вам использовать универсальные типы только в качестве возвращаемого типа метода (например, простые методы получения), но блокирует использование методов, где универсальный тип является аргументом (например, обычные методы установки). Это означает, что если вы напишите Map<? extends KeyType, ValueType>, компилятор не позволит вам вызывать метод get(<? extends KeyType>), и карта будет бесполезна. Единственное решение , чтобы сделать этот метод не является универсальным: get(Object).

Автор: Owheee Размещён: 01.01.2014 02:37

2 плюса

Совместимость.

До того, как дженерики были доступны, был только get (Object o).

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

Они могли бы ввести дополнительный метод, скажем, get_checked ( o) и отказаться от старого метода get (), чтобы был более мягкий путь перехода. Но по какой-то причине это не было сделано. (Ситуация, в которой мы сейчас находимся, заключается в том, что вам нужно установить такие инструменты, как findBugs, чтобы проверить совместимость типов между аргументом get () и объявленным типом ключа карты.)

Я думаю, что аргументы, относящиеся к семантике .equals (), являются поддельными. (Технически они правильные, но я все еще думаю, что они фальшивые. Ни один здравомыслящий дизайнер никогда не сделает так, чтобы o1.equals (o2) был истинным, если у o1 и o2 нет общего суперкласса.)

Автор: Erwin Smout Размещён: 23.03.2017 08:38

1 плюс

Обратная совместимость, наверное. Map(или HashMap) все еще нуждается в поддержке get(Object).

Автор: Anton Gogolev Размещён: 13.05.2009 11:33

1 плюс

Я смотрел на это и думал, почему они сделали это таким образом. Я не думаю, что какой-либо из существующих ответов объясняет, почему они не могли просто заставить новый универсальный интерфейс принимать только правильный тип для ключа. Фактическая причина в том, что, хотя они представили дженерики, они НЕ создали новый интерфейс. Интерфейс Map - это та же старая неуниверсальная карта, которая служит как универсальной, так и неуниверсальной версией. Таким образом, если у вас есть метод, который принимает неуниверсальную карту, вы можете передать его, Map<String, Customer>и он все равно будет работать. В то же время контракт для get принимает Object, поэтому новый интерфейс также должен поддерживать этот контракт.

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

Автор: Stilgar Размещён: 18.12.2014 01:00

0 плюса

Мы сейчас делаем большой рефакторинг, и нам не хватало этого строго типизированного get (), чтобы убедиться, что мы не пропустили некоторые get () со старым типом.

Но я нашел обходной / уродливый трюк для проверки времени компиляции: создайте интерфейс Map со строго типизированным get, containsKey, remove ... и поместите его в пакет java.util вашего проекта.

Вы получите ошибки компиляции только для вызова get (), ... с неправильными типами, все остальное кажется нормальным для компилятора (по крайней мере, внутри eclipse kepler).

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

Автор: henva Размещён: 09.02.2017 02:48
Вопросы из категории :
32x32