线上问题:Stream toMap键重复抛异常源码解析

1 问题复现

  • 问题描述
    使用Stream的toMap方法时,key冲突,即相同的key抛异常。
  • 代码复现
package lambda_expression;

import common.entity.UserEntity;

import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Stream临时测试样例.
 *
 * @author xindaqi
 * @date 2021-06-28 18:00
 */
public class StreamTempTest {

    private static final Logger logger = Logger.getLogger("StreamTempTest");

    public static void main(String[] args) {

        List<UserEntity> userEntityList = new ArrayList<>();

        userEntityList.add(new UserEntity("1", "111", "male"));
        userEntityList.add(new UserEntity("1", "222", "female"));
        userEntityList.add(new UserEntity("3", "333", "male"));
        Map<String, String> map = Optional.ofNullable(userEntityList).orElse(new ArrayList<>()).stream().collect(Collectors.toMap(UserEntity::getUid, UserEntity::getNickname));
        logger.info("Map: " + map);
    }
}
  • 结果
Exception in thread "main" java.lang.IllegalStateException: Duplicate key 111
	at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
	at java.util.HashMap.merge(HashMap.java:1254)
	at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
	at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at lambda_expression.StreamTempTest.main(StreamTempTest.java:26)

2 原因

2.1 抛异常的toMap方法

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);
    }
  • 抛异常
private static <T> BinaryOperator<T> throwingMerger() {
        return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
    }

2.2 处理重复键的toMap方法

toMap三个参数方法源码如下,通过BinaryOperator处理重复的键。

    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);
    }
  • 处理重复键mergeFunction
	public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }

3 方案

3.1 使用处理重复键的toMap方法

  • 保留第一个值
package lambda_expression;

import common.entity.UserEntity;

import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Stream临时测试样例.
 *
 * @author xindaqi
 * @date 2021-06-28 18:00
 */
public class StreamTempTest {

    private static final Logger logger = Logger.getLogger("StreamTempTest");

    public static void main(String[] args) {

        List<UserEntity> userEntityList = new ArrayList<>();

        userEntityList.add(new UserEntity("1", "111", "male"));
        userEntityList.add(new UserEntity("1", "222", "female"));
        userEntityList.add(new UserEntity("3", "333", "male"));
        /**
         * 保留第一个值
         */
        Map<String, String> map = Optional.ofNullable(userEntityList).orElse(new ArrayList<>()).stream().collect(Collectors.toMap(UserEntity::getUid, UserEntity::getNickname, (oldData, newData) -> oldData));
}
  • 保留最后一个值
package lambda_expression;

import common.entity.UserEntity;

import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Stream临时测试样例.
 *
 * @author xindaqi
 * @date 2021-06-28 18:00
 */
public class StreamTempTest {

    private static final Logger logger = Logger.getLogger("StreamTempTest");

    public static void main(String[] args) {

        List<UserEntity> userEntityList = new ArrayList<>();

        userEntityList.add(new UserEntity("1", "111", "male"));
        userEntityList.add(new UserEntity("1", "222", "female"));
        userEntityList.add(new UserEntity("3", "333", "male"));
        /**
         * 保留最后一个值
         */
        Map<String, String> map = Optional.ofNullable(userEntityList).orElse(new ArrayList<>()).stream().collect(Collectors.toMap(UserEntity::getUid, UserEntity::getNickname, (oldData, newData) -> newData));
        logger.info("Map: " + map);
    }
}

3.2 使用HashMap::new

package lambda_expression;

import common.entity.UserEntity;

import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Stream临时测试样例.
 *
 * @author xindaqi
 * @date 2021-06-28 18:00
 */
public class StreamTempTest {

    private static final Logger logger = Logger.getLogger("StreamTempTest");

    public static void main(String[] args) {

        List<UserEntity> userEntityList = new ArrayList<>();

        userEntityList.add(new UserEntity("1", "111", "male"));
        userEntityList.add(new UserEntity("1", "222", "female"));
        userEntityList.add(new UserEntity("3", "333", "male"));
        Map<String, String> map = Optional.ofNullable(userEntityList).orElse(new ArrayList<>()).stream().collect(HashMap::new, (k, v) -> k.put(v.getUid(), v.getNickname()), HashMap::putAll);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

天然玩家

坚持才能做到极致

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

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

打赏作者

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

抵扣说明:

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

余额充值