Карта слияния коллекций - PullRequest
       3

Карта слияния коллекций

0 голосов
/ 09 февраля 2019

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

public static <E, K> void  mergeMaps(Map<K, Collection<E>> receivingMap, Map<K, Collection<E>> givingMap) {
    for (Map.Entry<K, Collection<E>> entry : givingMap.entrySet()) {
        Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<E>());
        someCollection.addAll(entry.getValue());
    }
}

Моя цель состоит в том, чтобы функция mergeMaps могла объединять карты (одного типа), значения которых могутбыть произвольными коллекциями (ArrayList, LinkedHashMap, ...).

Однако, когда я пытаюсь объединить, скажем, два экземпляра Map<Integer, ArrayList<String>>, я получаю ошибку во время компиляции, но я не совсемпонять, что говорит мне компилятор.

public static void main(String[] args) {
    Map<Integer, ArrayList<String>> map1 = new HashMap<>();
    Map<Integer, ArrayList<String>> map2 = new HashMap<>();
    mergeMaps(map1, map2); // <-- compile error
}

Что здесь не так и как я могу это исправить?

Error:(9, 9) java: method mergeMaps in class CollectionUtil cannot be applied to given types;
  required: java.util.Map<K,java.util.Collection<E>>,java.util.Map<K,java.util.Collection<E>>
  found: java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>>,java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>>
  reason: cannot infer type-variable(s) E,K
    (argument mismatch; java.util.Map<java.lang.Integer,java.util.ArrayList<java.lang.String>> cannot be converted to java.util.Map<K,java.util.Collection<E>>)

Ответы [ 2 ]

0 голосов
/ 09 февраля 2019

Когда сигнатура метода -

<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());
    }
}
0 голосов
/ 09 февраля 2019

Ошибка говорит java.util.List<java.lang.String>> cannot be converted to java.util.Map<K,java.util.Collection<E>>)

Вы должны изменить свой метод:

public static <E, K> void  mergeMaps(Map<K, List<E>> receivingMap, Map<K, List<E>> givingMap) {
    for (Map.Entry<K, List<E>> entry : givingMap.entrySet()) {
        Collection<E> someCollection = receivingMap.computeIfAbsent(entry.getKey(), k -> new ArrayList<E>());
        someCollection.addAll(entry.getValue());
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...