По каким причинам Map.get (Object key) не является (полностью) универсальным - PullRequest
392 голосов
/ 13 мая 2009

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

Для уточнения вопроса, подпись метода

V get(Object key)

вместо

V get(K key)

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

Ответы [ 11 ]

255 голосов
/ 13 мая 2009

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

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

103 голосов
/ 13 мая 2009

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

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

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

29 голосов
/ 13 мая 2009

Контракт выражается так:

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

(мой акцент)

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

17 голосов
/ 25 июня 2009

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

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

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

11 голосов
/ 14 мая 2009

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

"Вы должны убедиться, что универсальный 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, но:

  • Статический тип входящего коллекция может отличаться, возможно потому что звонящий не знает точный тип коллекции прошло, или, возможно, потому что это Коллекция , где S представляет собой подтип E.
  • Это прекрасно законно вызывать containsAll () с коллекция другого типа. рутина должна работать, возвращая ложь. "
6 голосов
/ 13 мая 2009

Причина заключается в том, что ограничение определяется 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
4 голосов
/ 01 января 2014

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

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

2 голосов
/ 23 марта 2017

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

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

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

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

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

1 голос
/ 18 декабря 2014

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

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

1 голос
/ 13 мая 2009

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

...