Collectors.toMap你真的会用吗?

按照常规思维,往一个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小蜜蜂】关注我,我们下期见


  • 由于博主才疏学浅,难免会有纰漏,假如您发现了错误或遗漏的地方,还望留言斧正,我会尽快对其加以修正。

  • 如果您觉得文章还不错,您的转发、分享、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辛勤de小蜜蜂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值