如何在Collectors.toMap合并函数中获取密钥?

Collectors.toMap()期间找到重复键条目时,将调用合并函数(o1, o2)

问题:如何获得导致重复的密钥?

 String keyvalp = "test=one\ntest2=two\ntest2=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 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  Collector> toMap(Function keyMapper, Function 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对没有合并function的toMap的实现会做什么)

所以你需要在你的代码中做的就是重定向toMap调用并省略merge函数:

 String keyvalp = "test=one\ntest2=two\ntest2=three"; Map map = Pattern.compile("\n") .splitAsStream(keyvalp) .map(entry -> entry.split("=")) .collect(toMap(split -> split[0], split -> split[1])); 

(如果它既不在同一个类中也不在静态导入中,则为ContainingClass.toMap )<\ sup>

收集器支持与原始toMap收集器类似的并行处理,尽管它不太可能从这里获得并行处理的好处,即使有更多的元素需要处理。

如果,如果我找到你的正确,你只想在合并函数中根据实际的键选择旧的或更新的值,你可以使用像这样的键Predicate来做

 public static  Collector> toMap(Function keyMapper, Function valueMapper, Predicate 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 map = Pattern.compile("\n") .splitAsStream(keyvalp) .map(entry -> entry.split("=")) .collect(toMap(split -> split[0], split -> split[1], key -> condition)); 

有几种方法可以自定义此收集器…

当然,有一个简单而琐碎的技巧 – 在“键映射器”function中保存键并在“合并”function中获取键。 因此,代码可能如下所示(假设键是Integer):

 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 ); 

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