如何在collectors.tomap合并函数中获取密钥



当在Collectors.toMap()期间找到重复的密钥条目时,合并函数(o1, o2)被称为。

问题:如何获得引起重复的键?

String keyvalp = "test=onentest2=twontest2=three";
Pattern.compile("n")
    .splitAsStream(keyval)
    .map(entry -> entry.split("="))
    .collect(Collectors.toMap(
        split -> split[0],
        split -> split[1],
        (o1, o2) -> {
            //TODO how to access the key that caused the duplicate? o1 and o2 are the values only
            //split[0]; //which is the key, cannot be accessed here
        },
    HashMap::new));

在合并函数中,我想根据来决定,如果我取消映射或继续并接管这些值。

您需要使用自定义收集器或使用其他方法。

Map<String, String> map = new Hashmap<>();
Pattern.compile("n")
    .splitAsStream(keyval)
    .map(entry -> entry.split("="))
    .forEach(arr -> map.merge(arr[0], arr[1], (o1, o2) -> /* use arr[0]));

编写自定义收藏家更为复杂。您需要一个triconsumer(键和两个值(相似,这在JDK中不在,这就是为什么我很确定使用内置的函数使用的原因。;(

合并函数没有机会获取密钥,这是同一问题,当您省略合并函数时,内置函数也具有。

解决方案是使用其他toMap实现,该实现不依赖Map.merge

public static <T, K, V> Collector<T, ?, Map<K,V>>
    toMap(Function<? super T, ? extends K> keyMapper,
          Function<? super T, ? extends V> valueMapper) {
    return Collector.of(HashMap::new,
        (m, t) -> {
            K k = keyMapper.apply(t);
            V v = Objects.requireNonNull(valueMapper.apply(t));
            if(m.putIfAbsent(k, v) != null) throw duplicateKey(k, m.get(k), v);
        },
        (m1, m2) -> {
            m2.forEach((k,v) -> {
                if(m1.putIfAbsent(k, v)!=null) throw duplicateKey(k, m1.get(k), v);
            });
            return m1;
        });
}
private static IllegalStateException duplicateKey(Object k, Object v1, Object v2) {
    return new IllegalStateException("Duplicate key "+k+" (values "+v1+" and "+v2+')');
}

(这基本上是Java 9没有合并函数的toMap实现(

因此,您需要在代码中进行的所有操作,就是重定向toMap调用并省略合并函数:

String keyvalp = "test=onentest2=twontest2=three";
Map<String, String> map = Pattern.compile("n")
        .splitAsStream(keyvalp)
        .map(entry -> entry.split("="))
        .collect(toMap(split -> split[0], split -> split[1]));

(或 ContainingClass.toMap,如果它既不在同一类也不是静态导入(&lt; sup>

收集器像原始toMap收集器一样支持并行处理,尽管它不太可能从此处的并行处理中受益,即使有更多的元素进行处理。

如果我正确地让您找到了您,您只想在基于实际键的合并函数中选择年龄较大或较新的值,您可以使用键Predicate这样的键

来完成。
public static <T, K, V> Collector<T, ?, Map<K,V>>
    toMap(Function<? super T, ? extends K> keyMapper,
          Function<? super T, ? extends V> valueMapper,
          Predicate<? super K> useOlder) {
    return Collector.of(HashMap::new,
        (m, t) -> {
            K k = keyMapper.apply(t);
            m.merge(k, valueMapper.apply(t), (a,b) -> useOlder.test(k)? a: b);
        },
        (m1, m2) -> {
            m2.forEach((k,v) -> m1.merge(k, v, (a,b) -> useOlder.test(k)? a: b));
            return m1;
        });
}
Map<String, String> map = Pattern.compile("n")
        .splitAsStream(keyvalp)
        .map(entry -> entry.split("="))
        .collect(toMap(split -> split[0], split -> split[1], key -> condition));

有几种自定义此收藏家的方法……

当然,有简单而琐碎的技巧 - 将密钥保存在'密钥映射器'函数中,并在'Merge'函数中获取键。因此,代码看起来如下(假设密钥是整数(:

final AtomicInteger key = new AtomicInteger(); 
...collect( Collectors.toMap( 
   item -> { key.set(item.getKey()); return item.getKey(); }, // key mapper 
   item -> ..., // value mapper
   (v1, v2) -> { log(key.get(), v1, v2); return v1; } // merge function
);

注意:这对并行处理不利。

最新更新