Java Wildcard ошибка

Как правильно написать метод?

    private HashMap<?, ?> zip(List<?> k, List<?> v) {
        if (ensure(k, v))
            throw new IndexOutOfBoundsException();

        HashMap<?, ?> map = new HashMap<>();

        for (int i = 0; i < keys.size(); i++)
            map.put(k.get(i), v.get(i));

        return map;
    }

В строке, где put, пишет


Ответы (1 шт):

Автор решения: Nowhere Man

Имеет смысл избавиться от wildcard и сделать метод обобщённым и/или утилитарным (статическим), чтобы обеспечить контроль типов.

Также в методе zip не определена переменная keys, которая скорей всего должна быть заменена на список ключей k, и лучше, чтобы он возвращал интерфейс Map, а не конкретную реализацию HashMap:

private <K, V> Map<K, V> zip(List<K> keys, List<V> values) {
    if (ensure(keys, values)) {
        throw new IndexOutOfBoundsException();
    }
    Map<K, V> map = new HashMap<>();
    for (int i = 0, n = keys.size(); i < n; i++) {
        map.put(keys.get(i), values.get(i));
    }
    return map;
}

Вариант с использованием Stream API:

private static <K, V> Map<K, V> zipp(List<K> keys, List<V> values) {
    if (keys.size() != values.size()) {
        throw new IndexOutOfBoundsException();
    }
    return IntStream.range(0, keys.size())
        .mapToObj(i -> Map.entry(keys.get(i), values.get(i)))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
}

Обновление

Если требуется объединить несколько списков (чтобы одному ключу отвечало несколько значений), могут быть следующие варианты:

  • создать мапу со значениями-списками Map<K, List<?>> -- "грязный" способ
private static <K, V, E> Map<K, List<?>> zipp(List<K> keys, List<V> first, List<E> second) {
    if (keys.size() != first.size() || keys.size() != second.size()) {
        throw new IndexOutOfBoundsException();
    }
    return IntStream.range(0, keys.size())
        .mapToObj(i -> Map.entry(keys.get(i), List.of(first.get(i), second.get(i))))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
}
  • более чистый вариант - использовать типизированный класс-контейнер типа пары Pair<V, E> (например, при помощи типизированного кортежа record):
record Pair<V, E>(V first, E second){}

private static <K, V, E> Map<K, Pair<V, E>> zippp(List<K> keys, List<V> first, List<E> second) {
    if (keys.size() != first.size() || keys.size() != second.size()) {
        throw new IndexOutOfBoundsException();
    }
    return IntStream.range(0, keys.size())
        .mapToObj(i -> Map.entry(keys.get(i), new Pair<>(first.get(i), second.get(i))))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
}

Тесты:

List<Integer> k = Arrays.asList(1, 2, 3, 4, 5, 2);
List<String> v = Arrays.asList("aa", "bb", "cc", "xx", "yy", "pp");
List<Double> e = Arrays.asList(2.0, 4d, 8d, 10d, 4d, 5d);

System.out.println(zipp(k, v));      // Map<Integer, String>
System.out.println(zipp(k, v, e));   // Map<Integer, List<?>>
System.out.println(zippp(k, v, e));  // Map<Integer, Pair<String, Double>>

Результат:

{1=aa, 2=pp, 3=cc, 4=xx, 5=yy}
{1=[aa, 2.0], 2=[pp, 5.0], 3=[cc, 8.0], 4=[xx, 10.0], 5=[yy, 4.0]}
{1=Pair[first=aa, second=2.0], 2=Pair[first=pp, second=5.0], 3=Pair[first=cc, second=8.0], 4=Pair[first=xx, second=10.0], 5=Pair[first=yy, second=4.0]}

→ Ссылка