如何在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 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对没有合并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 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 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 );
注意:这对并行处理不利。