Когда сигнатура метода -
<E, K> void mergeMaps(Map<K, Collection<E>> receivingMap,
Map<K, Collection<E>> givingMap)
Тогда вызов с использованием Map<Integer, List<String>>
в качестве типов аргументов недопустим, поскольку Collection
не является параметром универсального типа метода mergeMaps
.Почему это проблема?При использовании обобщений Map<Integer, List<String>>
нельзя присвоить переменной Map<Integer, Collection<String>>
(или передать в качестве аргумента метода таким образом).Это связано с тем, что универсальные типы являются инвариантными (см. здесь для получения дополнительной информации. Короче говоря, это означает, что List<Integer>
не обязательно совместим с любым List<Number>
, хотя ArrayList<Number>
совместим с List<Number>
).
Другими словами, конкретные аргументы должны иметь тип Map<Integer, Collection<String>>
.Это приводит к вашему первому решению:
//Solution 1: change your arguments to Map<Integer, Collection<String>>:
Map<Integer, Collection<String>> map1 = new HashMap<>();
Map<Integer, Collection<String>> map2 = new HashMap<>();
mergeMaps(map1, map2);
Если вы хотите разрешить вызовы с параметрами типа Map<Integer, List<String>>
, вам нужно изменить целевой метод, чтобы ввести универсальный параметр вокруг значения карты:
public static <E, K, C extends Collection<E>> void
mergeMaps2(Map<K, C> receivingMap, Map<K, C> givingMap) {
for (Map.Entry<K, C> entry : givingMap.entrySet()) {
Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(),
k -> (C) new ArrayList<E>());
someCollection.addAll(entry.getValue());
}
}
И это можно вызвать с картами, где значение объявлено как подтип Collection<E>
(при условии, что тип Collection
одинаков в обоих аргументах):
Map<Integer, List<String>> map1 = new HashMap<>();
Map<Integer, List<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
Map<Integer, Set<String>> map1 = new HashMap<>();
Map<Integer, Set<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
Примечание (или отступление)
Теперь, когда вы компилируете это, у вас возникает еще одна проблема: в этой строке есть предупреждение компилятора:
Collection<E> someCollection =
receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>());
Утверждая, что (C) new ArrayList<E>()
- это непроверенный актерский состав.Почему это?Давайте рассмотрим приведенные выше примеры вызовов (я добавил их два):
Вызов 1:
Map<Integer, List<String>> map1 = new HashMap<>();
Map<Integer, List<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
В этом примере receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>())
означает добавление экземпляра ArrayList<String>
какзначение на карту.Поскольку фактический объект является типа, который совместим с объявленным типом вызывающей стороны (List<String>
), все в порядке.
Теперь, как вы думаете, что это будет делать?
Вызов 2:
Map<Integer, Set<String>> map1 = new HashMap<>();
Map<Integer, Set<String>> map2 = new HashMap<>();
mergeMaps2(map1, map2);
В этом случае к сожалению , receivingMap.computeIfAbsent(entry.getKey(), k -> (C) new ArrayList<E>())
все равно попытается добавить ArrayList<String>
, что несовместимо с вызывающим абонентом.ожидаемый тип значения (Set<String>
).
Компилятор не может быть уверен, что приведение (C) new ArrayList<E>()
всегда будет правильным в контексте аргументов конкретного типа.Он сдается, но выдает предупреждение, чтобы предупредить разработчика.
Решение этой проблемы на самом деле является сложной задачей.Вам нужно знать, какой тип создавать, но параметры вашего метода не позволят вам сделать это, потому что вы не можете просто запустить new C()
.Ваши собственные требования и дизайн определят правильное решение, но я закончу одним возможным решением:
public static <E, K, C extends Collection<E>> void
mergeMaps2(Map<K, C> receivingMap,
Map<K, C> givingMap,
Supplier<C> collectionCreator) {
for (Map.Entry<K, C> entry : givingMap.entrySet()) {
Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(),
k -> collectionCreator.get());
someCollection.addAll(entry.getValue());
}
}