按照常规思维,往一个map中put一个已存在的key,会把该key对应的value值覆盖,然而通过实践发现,对于java8中Collectors.toMap并不是这样,而是直接抛出了异常。
具体为什么会这样呢?
我们一起来看一下
首先大家看一下下面的方法
public static void main(String[] args) {
List<UserInfo> infoList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
UserInfo userInfo = new UserInfo(i + 1, "hh", 20 + i);
infoList.add(userInfo);
}
Map<Integer, UserInfo> distinctMap = infoList.stream().collect(
Collectors.toMap(UserInfo::getId, e -> e));
}
这样执行没有问题,distinctMap能够正常收集,如果在infoList中添加重复id值会怎么样呢?
public static void main(String[] args) {
List<UserInfo> infoList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
UserInfo userInfo = new UserInfo(i + 1, "hh", 20 + i);
infoList.add(userInfo);
}
infoList.add(new UserInfo(1, "hhh", 20));
Map<Integer, UserInfo> distinctMap = infoList.stream().collect(
Collectors.toMap(UserInfo::getId, e -> e));
}
执行发现,抛了如下异常:
我们一起来分析一下,首先进入toMap方法查看,如下:
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
其中有个方法throwingMerger()即上面提到的抛异常方法。
源码如下:
private static <T> BinaryOperator<T> throwingMerger() {
return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
继续查看toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
public static <T, K, U, M extends Map<K, U>>
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);
}
因为传入的 HashMap::new,所以查看HashMap的merge方法,
发现:
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
......
if (old != null) {
V v;
if (old.value != null)
v = remappingFunction.apply(old.value, value);
else
v = value;
if (v != null) {
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
}
......
}
即,如果old值不为空,则执行传进来的方法,即throwingMerger(),抛出异常
private static <T> BinaryOperator<T> throwingMerger() {
return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
}
那么如果我们就是想要实现"往一个map中put一个已存在的key,把该key对应的value值覆盖为新值/保留原值",具体该怎么做呢?
很简单,我们只需要自定义一个BinaryOperator<U> mergeFunction传入即可:
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
例如:
Map<Integer, UserInfo> duplicateMap = infoList.stream().collect(
Collectors.toMap(UserInfo::getId, e -> e, (oldV, newV) -> newV));
其中(oldV, newV) -> newV)即为自定义的BinaryOperator<U> mergeFunction,取代了上面所说的throwingMerger()
执行如下代码:
public static void main(String[] args) {
List<UserInfo> infoList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
UserInfo userInfo = new UserInfo(i + 1, "hh", 20 + i);
infoList.add(userInfo);
}
infoList.add(new UserInfo(1, "hhh", 20));
Map<Integer, UserInfo> duplicateMap = infoList.stream().collect(
Collectors.toMap(UserInfo::getId, e -> e, (oldV, newV) -> newV));
System.out.println(duplicateMap);
}
结果:
日常开发中,常常会使用到StreamAPI,平时没有注意该处存在的“坑”的话,上线生产环境后,很有可能会出现某类值收集时出现重复值,从而导致生产事故发生。
以上就是日常使用 Collectors.toMap 需要注意的事项
我是【辛勤de小蜜蜂】关注我,我们下期见
-
由于博主才疏学浅,难免会有纰漏,假如您发现了错误或遗漏的地方,还望留言斧正,我会尽快对其加以修正。
-
如果您觉得文章还不错,您的转发、分享、点赞、留言就是对我最大的鼓励。
-
感谢您的阅读,十分欢迎并感谢您的关注。