如何获取 Collectors.toMap 合并函数中的密钥?
How to get the key in Collectors.toMap merge function?
在 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));
在合并函数中,我想根据 key 来决定,如果我取消映射,或者继续并采用这些值。
您需要使用自定义收集器或使用其他方法。
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=one\ntest2=two\ntest2=three";
Map<String, String> map = Pattern.compile("\n")
.splitAsStream(keyvalp)
.map(entry -> entry.split("="))
.collect(toMap(split -> split[0], split -> split[1]));
(或 ContainingClass.toMap
如果它既不在同一个 class 中也不在静态导入中)<\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));
有几种方法可以自定义此收集器...
当然有一个简单而琐碎的技巧 - 在 'key mapper' 函数中保存密钥并在 'merge' 函数中获取密钥。因此,代码可能如下所示(假设键为 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
);
注意:这不利于并行处理。
在 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));
在合并函数中,我想根据 key 来决定,如果我取消映射,或者继续并采用这些值。
您需要使用自定义收集器或使用其他方法。
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=one\ntest2=two\ntest2=three";
Map<String, String> map = Pattern.compile("\n")
.splitAsStream(keyvalp)
.map(entry -> entry.split("="))
.collect(toMap(split -> split[0], split -> split[1]));
(或 ContainingClass.toMap
如果它既不在同一个 class 中也不在静态导入中)<\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));
有几种方法可以自定义此收集器...
当然有一个简单而琐碎的技巧 - 在 'key mapper' 函数中保存密钥并在 'merge' 函数中获取密钥。因此,代码可能如下所示(假设键为 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
);
注意:这不利于并行处理。