Ответ 1
Я думаю, что это для обратной совместимости со старыми версиями интерфейса Map. Несчастливо, что это так, но, как вы правы, было бы намного лучше, если бы это заняло правильный тип.
Я столкнулся с ошибкой в своем коде, где я использовал неправильный ключ, чтобы извлечь что-то из карты Java, которая, как я полагала, была строго типизирована с использованием Java-дженериков. Когда вы смотрите на карту Javadocs, многие из методов, включая get и remove, принимают объект как параметр вместо типа K (для Карты, определенной как Карта). Почему это? Есть ли веская причина или это ошибка дизайна API?
Я думаю, что это для обратной совместимости со старыми версиями интерфейса Map. Несчастливо, что это так, но, как вы правы, было бы намного лучше, если бы это заняло правильный тип.
Поскольку карта вернет значение, если объект, переданный методу get, равен любому ключу, сохраненному на карте. Equal не означает, что они должны быть одного типа, но что ключ и переданный объект равны методам, реализуются таким образом, что разные типы объектов взаимно признаются равными.
То же самое относится и к методу удаления.
Пример допустимого кода, который разбивает (не компилирует), если метод get допускает только параметры типа K:
LinkedList<Number> k1 = new LinkedList<Number>();
k1.add(10);
ArrayList<Integer> k2 = new ArrayList<Integer>();
k2.add(10);
Map<LinkedList<Number>, String> map = new HashMap<LinkedList<Number>, String>();
map.put(k1, "foo");
System.out.println(map.get(k2));
Это было сделано так, что если параметр типа является подстановочным знаком, эти методы все равно можно вызвать.
Если у вас есть Map<?, ?>
, Java не позволит вам вызывать любые методы, объявленные с помощью общих типов в качестве аргументов. Это не позволяет вам нарушать ограничения типа, поэтому вы не можете, например, вызвать put(key, value)
с неправильными типами.
Если get()
были определены как get(K key)
вместо текущего get(Object key)
, это тоже было бы исключено из-за этого же правила. Это сделало бы подстановочную карту практически непригодной для использования.
В теории, то же самое относится к remove()
, так как удаление объекта никогда не может нарушать ограничения типа.
Вот пример кода, который не был бы возможен, если get
был объявлен как get(T key)
:
public static <K,V> Map<K, V> intersect(Map<? extends K, ? extends V> m1, Map<? extends K, ? extends V> m2) {
Map<K,V> result = new HashMap<K, V>();
for (Map.Entry<? extends K, ? extends V> e1 : m1.entrySet()) {
V value = m2.get(e1.getKey()); // this would not work in case of Map.get(K key)
if (e1.getValue().equals(value)) {
result.put(e1.getKey(), e1.getValue());
}
}
return result;
}
e1.getKey()
возвращает объект некоторого неизвестного подтипа K
(подтип, используемый m1
), но m2
использует потенциально другой подтип K
. Если Map.get()
был объявлен как get(K key)
, это использование не было бы разрешено.