По каким причинам Map.get (Object key) не является (полностью) универсальным
51358 просмотра
11 ответа
Каковы причины, по которым принято решение не иметь полностью общий метод get в интерфейсе java.util.Map<K, V>
.
Чтобы прояснить вопрос, подпись метода
V get(Object key)
вместо
V get(K key)
и мне интересно, почему (то же самое для remove, containsKey, containsValue
).
Ответы (11)
258 плюса
Как уже упоминалось другими, причина get()
и т. Д. Не является общей, поскольку ключ получаемой записи не обязательно должен совпадать с типом объекта, которому вы передаете get()
; спецификация метода требует только, чтобы они были равны. Это следует из того, как equals()
метод принимает Object в качестве параметра, а не просто того же типа, что и объект.
Хотя обычно может быть верно, что многие классы equals()
определили так, что его объекты могут быть равны только объектам своего собственного класса, в Java есть много мест, где это не так. Например, в спецификации для List.equals()
говорится, что два объекта List равны, если они оба являются списками и имеют одинаковое содержимое, даже если они являются разными реализациями List
. Итак, возвращаясь к примеру в этом вопросе, в соответствии со спецификацией метода можно иметь a Map<ArrayList, Something>
и для меня вызов get()
с LinkedList
аргументом as, и он должен получить ключ, представляющий собой список с тем же содержимым. Это было бы невозможно, если бы они get()
были общими и ограничивали тип аргумента.
105 плюса
Кевин Бурриллион (Kevin Bourrillion), замечательный Java-кодер, написал об этой проблеме в своем блоге некоторое время назад (по общему признанию, Set
вместо Map
). Наиболее актуальное предложение:
Как правило, методы платформы Java Collections Framework (и библиотеки Google Collections тоже) никогда не ограничивают типы своих параметров, кроме случаев, когда это необходимо для предотвращения повреждения коллекции.
Я не совсем уверен, что согласен с этим в качестве принципа - например, .NET, кажется, хорошо, требуя правильный тип ключа - но стоит следовать рассуждениям в блоге. (Упомянув .NET, стоит объяснить, что одна из причин, по которой это не является проблемой в .NET, заключается в том, что в .NET существует большая проблема более ограниченной дисперсии ...)
Автор: Jon Skeet Размещён: 13.05.2009 11:4229 плюса
Договор выражается таким образом:
Более формально, если эта карта содержит отображение ключа k на значение v, такое что (key == null? K == null: key.equals (k) ), то этот метод возвращает v; в противном случае возвращается ноль. (Может быть не более одного такого отображения.)
(мой акцент)
и как таковой, успешный поиск ключа зависит от реализации входного ключа метода равенства. Это не обязательно зависит от класса k.
Автор: Brian Agnew Размещён: 13.05.2009 11:3417 плюса
Это применение Закона Постеля: «Будь консервативен в том, что ты делаешь, будь либеральным в том, что ты принимаешь от других».
Проверка на равенство может быть выполнена независимо от типа; equals
метод определен на Object
классе и принимает любые в Object
качестве параметра. Таким образом, имеет смысл для эквивалентности ключей и операций, основанных на эквивалентности ключей, принимать любой Object
тип.
Когда карта возвращает значения ключа, она сохраняет как можно больше информации о типе, используя параметр типа.
Автор: erickson Размещён: 25.06.2009 07:2412 плюса
Я думаю, что этот раздел Общего руководства объясняет ситуацию (мой акцент):
«Необходимо убедиться, что универсальный 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 () с коллекцией другого типа. Процедура должна работать, возвращая ложь ".
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)
.
2 плюса
Совместимость.
До того, как дженерики были доступны, был только get (Object o).
Если бы они изменили этот метод, чтобы получить (
Они могли бы ввести дополнительный метод, скажем, get_checked (
Я думаю, что аргументы, относящиеся к семантике .equals (), являются поддельными. (Технически они правильные, но я все еще думаю, что они фальшивые. Ни один здравомыслящий дизайнер никогда не сделает так, чтобы o1.equals (o2) был истинным, если у o1 и o2 нет общего суперкласса.)
Автор: Erwin Smout Размещён: 23.03.2017 08:381 плюс
Обратная совместимость, наверное. Map
(или HashMap
) все еще нуждается в поддержке get(Object)
.
1 плюс
Я смотрел на это и думал, почему они сделали это таким образом. Я не думаю, что какой-либо из существующих ответов объясняет, почему они не могли просто заставить новый универсальный интерфейс принимать только правильный тип для ключа. Фактическая причина в том, что, хотя они представили дженерики, они НЕ создали новый интерфейс. Интерфейс Map - это та же старая неуниверсальная карта, которая служит как универсальной, так и неуниверсальной версией. Таким образом, если у вас есть метод, который принимает неуниверсальную карту, вы можете передать его, Map<String, Customer>
и он все равно будет работать. В то же время контракт для get принимает Object, поэтому новый интерфейс также должен поддерживать этот контракт.
По моему мнению, они должны были добавить новый интерфейс и реализовать оба на существующей коллекции, но они решили в пользу совместимых интерфейсов, даже если это означает худший дизайн для метода get. Обратите внимание, что сами коллекции будут совместимы с существующими методами, только интерфейсы - нет.
Автор: Stilgar Размещён: 18.12.2014 01:000 плюса
Мы сейчас делаем большой рефакторинг, и нам не хватало этого строго типизированного get (), чтобы убедиться, что мы не пропустили некоторые get () со старым типом.
Но я нашел обходной / уродливый трюк для проверки времени компиляции: создайте интерфейс Map со строго типизированным get, containsKey, remove ... и поместите его в пакет java.util вашего проекта.
Вы получите ошибки компиляции только для вызова get (), ... с неправильными типами, все остальное кажется нормальным для компилятора (по крайней мере, внутри eclipse kepler).
Не забудьте удалить этот интерфейс после проверки вашей сборки, так как это не то, что вы хотите во время выполнения.
Автор: henva Размещён: 09.02.2017 02:48Вопросы из категории :
- java В чем разница между int и Integer в Java и C #?
- java Как я могу определить IP моего маршрутизатора / шлюза в Java?
- java Каков наилучший способ проверки XML-файла по сравнению с XSD-файлом?
- java Как округлить результат целочисленного деления?
- java Преобразование списка <Integer> в список <String>
- generics Почему в C # нельзя хранить объект List <string> в переменной List <object>
- generics Преобразование общего типа из строки
- generics Лучший способ проверить, является ли универсальный тип строкой? (С #)
- generics Есть ли ограничение, которое ограничивает мой общий метод численными типами?
- generics Каковы различия между «универсальными» типами в C ++ и Java?
- collections Фильтрация коллекций в C #
- collections Различия между HashMap и Hashtable?
- collections Как эффективно перебирать каждую запись на карте Java?
- collections Выберите N случайных элементов из списка <T> в C #
- map Случайный элемент на карте
- map remove_if эквивалентно для std :: map
- map По каким причинам Map.get (Object key) не является (полностью) универсальным
- map Расстояние между 2 геокодами
- map c ++ map find () для возможной вставки (): как оптимизировать операции?