Я столкнулся с ошибкой в своем коде, где я использовал неправильный ключ, чтобы извлечь что-то из карты Java, которая, как я полагала, была строго типизирована с использованием Java-дженериков. Когда вы смотрите на карту Javadocs, многие из методов, включая get и remove, принимают объект как параметр вместо типа K (для Карты, определенной как Карта). Почему это? Есть ли веская причина или это ошибка дизайна API?
Почему Java Map <K, V> принимает нетипизированный параметр для методов get и remove?
Ответ 1
Я думаю, что это для обратной совместимости со старыми версиями интерфейса Map. Несчастливо, что это так, но, как вы правы, было бы намного лучше, если бы это заняло правильный тип.
Ответ 2
Поскольку карта вернет значение, если объект, переданный методу 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));
Ответ 3
Это было сделано так, что если параметр типа является подстановочным знаком, эти методы все равно можно вызвать.
Если у вас есть 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)
, это использование не было бы разрешено.