В Java8, когда функция слияния будет запущена в Collectors.toMap? - PullRequest
1 голос
/ 20 февраля 2020

Я хочу преобразовать Hashmap<String,Long> в Treemap, чтобы отсортировать его ключ по string.length (я не могу просто использовать treemap.addAll, потому что у меня могут быть другие логики c при вставке и я хочу использовать java8)

Код ниже. Но когда ключи с одинаковой длиной существуют в исходном Hashmap, это вызовет функцию слияния, которая выдает исключение (я собираюсь сделать это, потому что в моем случае не будет такой же строки). Интересно, почему функция слияния запускается, поскольку JavaDo c в toMap () говорит: «Если сопоставленные ключи содержат дубликаты (согласно Object # equals (Object)), функция сопоставления значений применяется к каждый равный элемент, и результаты объединяются с использованием предоставленной функции объединения. " Я думаю, что в моем коде «сопоставленные ключи» должны быть записью в hashMap, сопоставленной Entry::getKey, но не string.length() в компараторе TreeMap. то есть "ab c"! = "def". Так что это не должно вызывать слияния. Но?? Что за черт?

public class TestToMap {

    public static Map<String, Long> map1 = new HashMap<String, Long>() {
        {
            put("abc", 123L);
            put("def", 456L);
        }
    };

    public static void main(String[] args) {
        Map<String, Long> priceThresholdMap = map1.entrySet().stream()
            .collect(Collectors.toMap(Entry::getKey,
                                      Entry::getValue,
                                      throwingMerger(),
                                      () -> new TreeMap<String, Long>(
                                          (a, b) -> {
                                              return a.length() - b.length();
                                          }))); // this will trigger merge function, why?
        //() -> new TreeMap<String, Long>(Comparator.comparingInt(String::length).thenComparing(String::compareTo))));  // but this won't trigger merge function 

    }

    private static <T> BinaryOperator<T> throwingMerger() {
        return (u, v) -> {
            throw new IllegalStateException(String.format("priceThresholdMap has duplicate v1 %s,v2 %s", u, v));
        };
    }
}

Ответы [ 2 ]

3 голосов
/ 20 февраля 2020

Конечно, это должно вызвать слияние. Функция слияния используется для слияния значений с одинаковыми ключами в выводе Map, который в вашем случае является TreeMap.

В TreeMap ключи идентичны, если метод Comparator compare возвращает 0, поэтому два ключа одинаковой длины считаются идентичными, и их соответствующие значения должны быть объединены.

Обратите внимание, что ваш Comparator приводит к тому, что вывод TreeMap неправильно реализует интерфейс Map, поскольку определяемый им порядок не соответствует equals():

Обратите внимание, что порядок, поддерживаемый древовидной картой, как и любой отсортированной картой, и наличие или нет явного компаратора, должен соответствовать уравнениям, если эта отсортированная карта должна правильно реализовывать интерфейс карты

(От TreeMap Javado c)

Если вы хотите отсортировать String по длине, вы все равно можете согласиться с equals:

вместо

return a.length() - b.length()

use

return a.length() == b.length() ? a.compareTo(b) : Integer.compare(a.length(),b.length())

Теперь неравные String s одинаковой длины будут упорядочены лексикографически, а String различной длины будут упорядочены по длине.

0 голосов
/ 20 февраля 2020

Согласно исходному коду toMap (), он создает аккумулятор, который сворачивает каждый элемент из исходного потока в карту.

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }

И в Map.merge (), когда get ("def") вернет существующее oldValue = 123, ключом которого является "ab c", потому что компаратором я даю TreeMap «def» равно «ab c». И тогда oldValue! = Null вызывает функцию слияния.

 default V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value); // call the merge function
        if(newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        return newValue;
    }

ref: Коллекторы для отображения дублирующего ключа

...