Java8,Collectors.toMap合并功能什么时候触发?

In Java8, when will the merge function be triggered in Collectors.toMap?

我想将 Hashmap<String,Long> 转换为 Treemap,以便按 string.length 对其键进行排序(我不能简单地使用 treemap.addAll,因为我有插入时可能有其他逻辑,我想使用 java8)

代码如下。但是当初始哈希图中存在相同长度的键时,它将触发抛出异常的合并函数(我打算这样做,因为在我的情况下不会有相同的字符串)。我想知道为什么要触发合并函数,因为 toMap() 的 JavaDoc 说 "If the mapped keys contains duplicates (according to Object#equals(Object)), the value mapping function is applied to each equal element, and results are merged using the provided merging function." 我认为在我的代码中 "mapped keys" 应该是由 Entry::getKey 而不是 [= 映射的 hashMap 中的条目17=] 在 TreeMap 比较器中。即 "abc" != "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));
        };
    }
}

当然应该触发合并。合并函数用于合并输出 Map 中具有相同键的值,在您的例子中是 TreeMap

TreeMap 中,如果 Comparatorcompare 方法 returns 0 中的键是相同的,那么两个长度相同的键是认为相同,应合并它们的对应值。

请注意,您的 Comparator 导致输出 TreeMap 无法正确实现 Map 接口,因为它定义的顺序与 equals() 不一致:

Note that the ordering maintained by a tree map, like any sorted map, and whether or not an explicit comparator is provided, must be consistent with equals if this sorted map is to correctly implement the Map interface

(来自 TreeMap Javadoc)

如果想把String的按长度排序,还是可以和equals保持一致:

而不是

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

使用

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

现在不相等的 String 长度相同的将按字典顺序排列,而长度不同的 String 将按长度排列。

根据 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") 将 return 存在 oldValue=123,哪个键是 "abc",因为通过比较器我给 TreeMap "def" 等于 "abc"。然后 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;
    }

参考:Collectors toMap duplicate key