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

393
задан h0ussni 1 January 2014 в 15:03
поделиться

7 ответов

As mentioned by others, the reason why get(), etc. is not generic because the key of the entry you are retrieving does not have to be the same type as the object that you pass in to get(); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Object as parameter, not just the same type as the object.

Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places in Java where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, according to the specification of the method is possible to have a Map and for me to call get() with a LinkedList as argument, and it should retrieve the key which is a list with the same contents. This would not be possible if get() were generic and restricted its argument type.

258
ответ дан 22 November 2019 в 23:43
поделиться

The contract is expressed thus:

More formally, if this map contains a mapping from a key k to a value v such that (key==null ? k==null : key.equals(k)), then this method returns v; otherwise it returns null. (There can be at most one such mapping.)

(my emphasis)

and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.

29
ответ дан 22 November 2019 в 23:43
поделиться

An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set instead of Map). The most relevant sentence:

Uniformly, methods of the Java Collections Framework (and the Google Collections Library too) never restrict the types of their parameters except when it's necessary to prevent коллекцию от поломки.

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

105
ответ дан 22 November 2019 в 23:43
поделиться

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

«Вы должны убедиться, что общий 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 () с коллекция другого типа. В процедура должна работать, возвращая false. "
12
ответ дан 22 November 2019 в 23:43
поделиться

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

Единственный способ иметь безопасные по типу хеш-таблицы и равенство в Java - это избегать Object.equals и Object.hashCode и используйте общий заменитель. Функциональная Java поставляется с классами типов только для этой цели: Hash и Equal . Предоставляется оболочка для HashMap , которая принимает Hash и Equal в его конструкторе. get и этого класса содержат методы, поэтому принимают общий аргумент типа 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
6
ответ дан 22 November 2019 в 23:43
поделиться

Backwards compatibility, I guess. Map (or HashMap) still needs to support get(Object).

1
ответ дан 22 November 2019 в 23:43
поделиться

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

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

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

17
ответ дан 22 November 2019 в 23:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: