Java8 Collectors.toMap():Duplicate key异常源码解析

目录

一、异常呈现

1、主代码

2、执行结果

3、ClazzDuplicateKeyInCollectMap

二、源码解读,找异常,找原因

1、第一步

2、第二步

3、第三步(关键点,一定要看)

三、解决方案

1、解决思路

2、解决方案的代码

3、执行结果


一、异常呈现

1、主代码

    // 场景一:list中有重复数据,转换为map时抛异常
    private static void part1() {
        // 这里的key 有重复
        List list
                = Arrays.asList(
                new ClazzDuplicateKeyInCollectMap(1, 10),
                new ClazzDuplicateKeyInCollectMap(1, 20),
                new ClazzDuplicateKeyInCollectMap(2, 30));

        // 【重点】
        // 这里执行时,抛出异常
        // java.lang.IllegalStateException:
        // Duplicate key
        // com.djj.zmine.collect.collect_map.ClazzDuplicateKeyInCollectMap@6e2c634b
        //
        Map map
                = list.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e));

        System.out.println(map);

    }

2、执行结果

运行时异常

Exception in thread "main" java.lang.IllegalStateException: Duplicate key com.djj.zmine.collect.collect_map.ClazzDuplicateKeyInCollectMap@6e2c634b
    at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1245)
    at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    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 com.djj.zmine.collect.collect_map.DuplicateKeyInCollectMap.part1(DuplicateKeyInCollectMap.java:38)
    at com.djj.zmine.collect.collect_map.DuplicateKeyInCollectMap.main(DuplicateKeyInCollectMap.java:17)

Process finished with exit code 1

Java8 Collectors.toMap():Duplicate key异常源码解析_第1张图片

3、ClazzDuplicateKeyInCollectMap

class ClazzDuplicateKeyInCollectMap {
    private Integer key;
    private Integer age;

    public ClazzDuplicateKeyInCollectMap() {
    }

    public ClazzDuplicateKeyInCollectMap(Integer key, Integer age) {
        this.key = key;
        this.age = age;
    }

    public Integer getKey() {
        return key;
    }

    public void setKey(Integer key) {
        this.key = key;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

二、源码解读,找异常,找原因

问题是从下面代码红色部分抛出来的

        Map map
                = list.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e));

所以,就从 Collectors.toMap() 开始解读源码。

1、第一步

C:/Program Files/Java/jdk1.8.0_71/src.zip!/java/util/stream/Collectors.java

    /**
     * Returns a {@code Collector} that accumulates elements into a
     * {@code Map} whose keys and values are the result of applying the provided
     * mapping functions to the input elements.
     *
     * 

If the mapped keys contains duplicates (according to * {@link Object#equals(Object)}), an {@code IllegalStateException} is * thrown when the collection operation is performed. If the mapped keys * may have duplicates, use {@link #toMap(Function, Function, BinaryOperator)} * instead. * * @apiNote * It is common for either the key or the value to be the input elements. * In this case, the utility method * {@link java.util.function.Function#identity()} may be helpful. * For example, the following produces a {@code Map} mapping * students to their grade point average: *

{@code
     *     Map studentToGPA
     *         students.stream().collect(toMap(Functions.identity(),
     *                                         student -> computeGPA(student)));
     * }
* And the following produces a {@code Map} mapping a unique identifier to * students: *
{@code
     *     Map studentIdToStudent
     *         students.stream().collect(toMap(Student::getId,
     *                                         Functions.identity());
     * }
* * @implNote * The returned {@code Collector} is not concurrent. For parallel stream * pipelines, the {@code combiner} function operates by merging the keys * from one map into another, which can be an expensive operation. If it is * not required that results are inserted into the {@code Map} in encounter * order, using {@link #toConcurrentMap(Function, Function)} * may offer better parallel performance. * * @param the type of the input elements * @param the output type of the key mapping function * @param the output type of the value mapping function * @param keyMapper a mapping function to produce keys * @param valueMapper a mapping function to produce values * @return a {@code Collector} which collects elements into a {@code Map} * whose keys and values are the result of applying mapping functions to * the input elements * * @see #toMap(Function, Function, BinaryOperator) * @see #toMap(Function, Function, BinaryOperator, Supplier) * @see #toConcurrentMap(Function, Function) */ public static Collector> toMap(Function keyMapper, Function valueMapper) { return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new); }

参数解析

keyMapper

T 是 ClazzDuplicateKeyInCollectMap ,K 是 Integer 。

valueMapper

T 是 ClazzDuplicateKeyInCollectMap ,U 是 ClazzDuplicateKeyInCollectMap 。

2、第二步

toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new)

是下面的代码

C:/Program Files/Java/jdk1.8.0_71/src.zip!/java/util/stream/Collectors.java

    /**
     * Returns a {@code Collector} that accumulates elements into a
     * {@code Map} whose keys and values are the result of applying the provided
     * mapping functions to the input elements.
     *
     * 

If the mapped * keys contains duplicates (according to {@link Object#equals(Object)}), * the value mapping function is applied to each equal element, and the * results are merged using the provided merging function. The {@code Map} * is created by a provided supplier function. * * @implNote * The returned {@code Collector} is not concurrent. For parallel stream * pipelines, the {@code combiner} function operates by merging the keys * from one map into another, which can be an expensive operation. If it is * not required that results are merged into the {@code Map} in encounter * order, using {@link #toConcurrentMap(Function, Function, BinaryOperator, Supplier)} * may offer better parallel performance. * * @param the type of the input elements * @param the output type of the key mapping function * @param the output type of the value mapping function * @param the type of the resulting {@code Map} * @param keyMapper a mapping function to produce keys * @param valueMapper a mapping function to produce values * @param mergeFunction a merge function, used to resolve collisions between * values associated with the same key, as supplied * to {@link Map#merge(Object, Object, BiFunction)} * @param mapSupplier a function which returns a new, empty {@code Map} into * which the results will be inserted * @return a {@code Collector} which collects elements into a {@code Map} * whose keys are the result of applying a key mapping function to the input * elements, and whose values are the result of applying a value mapping * function to all input elements equal to the key and combining them * using the merge function * * @see #toMap(Function, Function) * @see #toMap(Function, Function, BinaryOperator) * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier) */ public static > Collector toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier) { BiConsumer accumulator = (map, element) -> map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); }

参数解析

keyMapper

T 是 ClazzDuplicateKeyInCollectMap ,K 是 Integer 。

valueMapper

T 是 ClazzDuplicateKeyInCollectMap ,U 是 ClazzDuplicateKeyInCollectMap 。

mapSupplier

M 是 HashMap 。

mergeFunction

U 是 ClazzDuplicateKeyInCollectMap 。

mergeFunction = throwingMerger()

throwingMerger() 是下面的代码:

C:/Program Files/Java/jdk1.8.0_71/src.zip!/java/util/stream/Collectors.java

    /**
     * Returns a merge function, suitable for use in
     * {@link Map#merge(Object, Object, BiFunction) Map.merge()} or
     * {@link #toMap(Function, Function, BinaryOperator) toMap()}, which always
     * throws {@code IllegalStateException}.  This can be used to enforce the
     * assumption that the elements being collected are distinct.
     *
     * @param  the type of input arguments to the merge function
     * @return a merge function which always throw {@code IllegalStateException}
     */
    private static  BinaryOperator throwingMerger() {
        return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
    }

3、第三步(关键点,一定要看)

map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction);

执行下面的代码:

C:/Program Files/Java/jdk1.8.0_71/src.zip!/java/util/HashMap.java

    @Override
    public V merge(K key, V value,
                   BiFunction remappingFunction) {
        if (value == null)
            throw new NullPointerException();
        if (remappingFunction == null)
            throw new NullPointerException();
        int hash = hash(key);
        Node[] tab; Node first; int n, i;
        int binCount = 0;
        TreeNode t = null;
        Node old = null;
        if (size > threshold || (tab = table) == null ||
            (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((first = tab[i = (n - 1) & hash]) != null) {
            if (first instanceof TreeNode)
                old = (t = (TreeNode)first).getTreeNode(hash, key);
            else {
                Node e = first; K k;
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) {
                        old = e;
                        break;
                    }
                    ++binCount;
                } while ((e = e.next) != null);
            }
        }
        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;
        }
        if (value != null) {
            if (t != null)
                t.putTreeVal(this, tab, hash, key, value);
            else {
                tab[i] = newNode(hash, key, value, first);
                if (binCount >= TREEIFY_THRESHOLD - 1)
                    treeifyBin(tab, hash);
            }
            ++modCount;
            ++size;
            afterNodeInsertion(true);
        }
        return value;
    }

代码关键点

        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;
        }

说明:

当map中原来已存在指定key,且值不为null时,再次添加该key时,就会执行 remappingFunction.apply(old.value, value)

而整个参数传递中,可以知道 remappingFunction = throwingMerger(),

也就是只要执行 remappingFunction.apply(old.value, value) ,就一定会抛出异常。

所以,找到异常和原因了。

三、解决方案

1、解决思路

从上面二中的第二步开始调用,就不会抛出异常了。

2、解决方案的代码

    private static void solution() {
        // 这里的key 有重复
        List list
                = Arrays.asList(
                new ClazzDuplicateKeyInCollectMap(1, 10),
                new ClazzDuplicateKeyInCollectMap(1, 20),
                new ClazzDuplicateKeyInCollectMap(2, 30));

        // 【重点】
        Map map
                = list.stream().collect(
                        Collectors.toMap(
                                e -> e.getKey(),
                                e -> e,
                                (oldValue, value) -> oldValue));

        System.out.println(map);
    }

代码关键点:

        // 【重点】
        Map map
                = list.stream().collect(
                        Collectors.toMap(
                                e -> e.getKey(),
                                e -> e,
                                (oldValue, value) -> oldValue));

红色代码部分表示:

如果有重复的key,在值的处理上,保留原来的值,舍弃当前的新值

当然,也可以有第二种方案

如果有重复的key,在值的处理上,新值覆盖旧值

(oldValue, value) -> value

3、执行结果

{1=ClazzDuplicateKeyInCollectMap{key=1, age=10}, 2=ClazzDuplicateKeyInCollectMap{key=2, age=30}}

Process finished with exit code 0

说明:成功解决问题。


我是程序员娟娟,

致力将工作中遇到的问题和解决方案记录下来,

分享给更多需要的同行。

如果对你有帮助,不妨点个关注吧!

你可能感兴趣的:(java,java,后端)