Каковы причины, по которым принято решение не иметь полностью общий метод get в интерфейсе java.util.Map<K, V>
.
Чтобы прояснить вопрос, подпись метода
V get(Object key)
вместо
V get(K key)
и мне интересно, почему (то же самое для remove, containsKey, containsValue
).
Ответы:
Как уже упоминалось, причина
get()
и т. Д. Не является общей, поскольку ключ получаемой записи не обязательно должен совпадать с типом объекта, которому вы передаетеget()
; спецификация метода требует только, чтобы они были равны. Это следует из того, какequals()
метод принимает Object в качестве параметра, а не просто того же типа, что и объект.Хотя обычно может быть верно, что многие классы
equals()
определили так, что его объекты могут быть равны только объектам своего собственного класса, в Java есть много мест, где это не так. Например, спецификация forList.equals()
говорит, что два объекта List равны, если они оба являются списками и имеют одинаковое содержимое, даже если они являются разными реализациямиList
. Итак, возвращаясь к примеру в этом вопросе, в соответствии со спецификацией метода можно иметь aMap<ArrayList, Something>
и для меня вызовget()
сLinkedList
аргументом as, и он должен получить ключ, представляющий собой список с тем же содержимым. Это было бы невозможно, если бы ониget()
были общими и ограничивали тип аргумента.источник
V Get(K k)
в C #?m.get(linkedList)
, почему вы не определилиm
тип какMap<List,Something>
? Я не могу придумать случай использования, в котором вызовm.get(HappensToBeEqual)
без измененияMap
типа для получения интерфейса имеет смысл.TreeMap
может дать сбой, когда вы передаете объекты неправильного типаget
методу, но иногда можете проходить, например, когда карта оказывается пустой. И что еще хуже, в случае прилагаемогоComparator
вcompare
методе (который имеет общую подпись!) Может быть вызвано с аргументами неправильного типа без какого - либо непроверенного предупреждения. Это является неработающим поведением.Кевин Бурриллион (Kevin Bourrillion), замечательный Java-кодер, написал об этой проблеме в своем блоге некоторое время назад (по общему признанию,
Set
вместоMap
). Наиболее актуальное предложение:Я не совсем уверен, что согласен с этим в качестве принципа - например, .NET, кажется, хорошо, требуя правильный тип ключа - но стоит следовать рассуждениям в блоге. (Упомянув .NET, стоит объяснить, что одна из причин, по которой это не является проблемой в .NET, заключается в том, что в .NET существует большая проблема более ограниченной дисперсии ...)
источник
Integer
a и aDouble
никогда не могут быть равны друг другу, все еще справедливо задать вопрос,Set<? extends Number>
содержит ли a значениеnew Integer(5)
.Set<? extends Foo>
. Я очень часто менял тип ключа карты, а потом разочаровывался, что компилятор не смог найти все места, где код нуждался в обновлении. Я действительно не уверен, что это правильный компромисс.Договор выражается таким образом:
(мой акцент)
и как таковой, успешный поиск ключа зависит от реализации входного ключа метода равенства. Это не обязательно зависит от класса k.
источник
hashCode()
. Без правильной реализации hashCode () хорошо реализованная реализацияequals()
в этом случае довольно бесполезна.get()
не нужно принимать аргумент типаObject
для удовлетворения контакта. Представьте, что метод get ограничен типом ключаK
- договор все равно будет действительным. Конечно, использование, где тип времени компиляции не был подклассомK
, теперь не сможет компилироваться, но это не делает контракт недействительным, поскольку контракты неявно обсуждают, что произойдет, если код компилируется.Это применение Закона Постеля: «Будь консервативен в том, что ты делаешь, будь либеральным в том, что ты принимаешь от других».
Проверка на равенство может быть выполнена независимо от типа;
equals
метод определен наObject
классе и принимает любые вObject
качестве параметра. Таким образом, имеет смысл для эквивалентности ключа и операций, основанных на эквивалентности ключа, принимать любойObject
тип.Когда карта возвращает значения ключа, она сохраняет как можно больше информации о типе, используя параметр типа.
источник
V Get(K k)
в C #?V Get(K k)
в C #, потому что это также имеет смысл. Разница между подходами Java и .NET заключается только в том, кто блокирует несоответствующие элементы. В C # это компилятор, в Java это коллекция. Я бушую по поводу несовместимых классов коллекций .NET время от времени, ноGet()
иRemove()
только принятие соответствующего типа, безусловно, предотвращает случайную передачу неправильного значения.contains : K -> boolean
.Я думаю, что этот раздел Общего руководства объясняет ситуацию (мой акцент):
«Необходимо убедиться, что универсальный API не является чрезмерно ограничительным; он должен продолжать поддерживать первоначальный контракт API. Рассмотрим еще несколько примеров из java.util.Collection. Предварительный универсальный API выглядит следующим образом:
Наивная попытка сделать это:
Хотя это, безусловно, безопасность типа, это не соответствует первоначальному контракту API. Метод containsAll () работает с любым видом входящей коллекции. Это будет успешным, только если входящая коллекция действительно содержит только экземпляры E, но:
источник
containsAll( Collection< ? extends E > c )
тогда?containsAll
с а ,Collection<S>
гдеS
является супертипом изE
. Это не будет разрешено, если бы это былоcontainsAll( Collection< ? extends E > c )
. Кроме того, как это явно указано в примере, это законно передать коллекцию другого типа (с , то возвращаемое значение являетсяfalse
).Причина в том, что сдерживание определяется
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
.Пример:
источник
Совместимость.
До того, как дженерики были доступны, был только get (Object o).
Если бы они изменили этот метод, чтобы получить (<K> o), это потенциально привело бы к массовому обслуживанию кода для пользователей Java только для того, чтобы снова скомпилировать рабочий код.
Они могли бы ввести дополнительный метод, скажем, get_checked (<K> o) и отказаться от старого метода get (), чтобы был более мягкий путь перехода. Но по какой-то причине это не было сделано. (Ситуация, в которой мы сейчас находимся, заключается в том, что вам нужно установить такие инструменты, как findBugs, чтобы проверить совместимость типов между аргументом get () и объявленным типом ключа <K> карты.)
Я думаю, что аргументы, относящиеся к семантике .equals (), являются поддельными. (Технически они правильные, но я все еще думаю, что они фальшивые. Ни один здравомыслящий дизайнер никогда не сделает так, чтобы o1.equals (o2) был истинным, если у o1 и o2 нет общего суперкласса.)
источник
Есть еще одна веская причина, это не может быть сделано технически, потому что это нарушает Map.
У Java есть полиморфная общая конструкция, подобная
<? extends SomeClass>
. Отмеченная такая ссылка может указывать на тип со знаком<AnySubclassOfSomeClass>
. Но полиморфный универсальный делает эту ссылку только для чтения . Компилятор позволяет вам использовать универсальные типы только в качестве возвращаемого типа метода (например, простые методы получения), но блокирует использование методов, где универсальный тип является аргументом (например, обычные методы установки). Это означает, что если вы напишитеMap<? extends KeyType, ValueType>
, компилятор не позволит вам вызывать методget(<? extends KeyType>)
, и карта будет бесполезна. Единственное решение , чтобы сделать этот метод не является универсальным:get(Object)
.источник
Обратная совместимость, наверное.
Map
(илиHashMap
) все еще нуждается в поддержкеget(Object)
.источник
put
(который ограничивает универсальные типы). Вы получаете обратную совместимость, используя необработанные типы. Дженерики "согласны".Я смотрел на это и думал, почему они так сделали. Я не думаю, что какой-либо из существующих ответов объясняет, почему они не могли просто заставить новый универсальный интерфейс принимать только правильный тип для ключа. Фактическая причина в том, что, хотя они ввели дженерики, они НЕ создали новый интерфейс. Интерфейс Map - это та же старая неуниверсальная карта, которая служит как универсальной, так и неуниверсальной версией. Таким образом, если у вас есть метод, который принимает неуниверсальную карту, вы можете передать его,
Map<String, Customer>
и он все равно будет работать. В то же время контракт для get принимает Object, поэтому новый интерфейс также должен поддерживать этот контракт.По моему мнению, они должны были добавить новый интерфейс и реализовать оба на существующей коллекции, но они решили в пользу совместимых интерфейсов, даже если это означает худший дизайн для метода get. Обратите внимание, что сами коллекции будут совместимы с существующими методами, только интерфейсы - нет.
источник
Мы сейчас делаем большой рефакторинг, и нам не хватало этого строго типизированного get (), чтобы убедиться, что мы не пропустили некоторые get () со старым типом.
Но я нашел обходной / уродливый трюк для проверки времени компиляции: создайте интерфейс Map со строго типизированным get, containsKey, remove ... и поместите его в пакет java.util вашего проекта.
Вы получите ошибки компиляции только для вызова get (), ... с неправильными типами, все остальное кажется нормальным для компилятора (по крайней мере, внутри eclipse kepler).
Не забудьте удалить этот интерфейс после проверки вашей сборки, так как это не то, что вы хотите во время выполнения.
источник