Каковы причины, по которым карта.get (ключ объекта) не является (полностью) общим

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

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

V get(Object key)

вместо

V get(K key)

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

11 ответов


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

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


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

равномерно, методы Java Рамки коллекций (и Google Сборники Library too) никогда ограничить типы их параметров за исключением случаев, когда необходимо предотвратить коллекция из getting сломанный.

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


договор выражается так:

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

(Курсив мой)

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



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

" вам нужно убедиться, что универсальный API не является чрезмерно ограничительным; он должен продолжайте поддерживать исходный контракт API. Рассмотрим еще раз некоторые примеры с явы.утиль.Коллекция. Pre-generic API выглядит так:

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

наивная попытка generify это:

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

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

  • статический тип входящего коллекция может отличаться, возможно потому что звонивший не знает точный тип коллекции прошел, или, возможно, потому что это Коллекция, где S - a подтип Е.
  • это прекрасно правомерно ли называть containsAll() с коллекции другого типа. Этот процедура должна работать, возвращая false."

причина в том, что сдерживание определяется equals и hashCode которые являются методами на Object и как принимать


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

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


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


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

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


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

прежде чем дженерики были доступны, было просто get (объект o).

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

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

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


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

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

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

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