目录
一、异常呈现
1、主代码
2、执行结果
3、ClazzDuplicateKeyInCollectMap
二、源码解读,找异常,找原因
1、第一步
2、第二步
3、第三步(关键点,一定要看)
三、解决方案
1、解决思路
2、解决方案的代码
3、执行结果
// 场景一: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);
}
运行时异常:
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
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() 开始解读源码。
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 。
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)); };
}
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) ,就一定会抛出异常。
所以,找到异常和原因了。
从上面二中的第二步开始调用,就不会抛出异常了。
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);
}
代码关键点:
// 【重点】
Mapmap
= list.stream().collect(
Collectors.toMap(
e -> e.getKey(),
e -> e,
(oldValue, value) -> oldValue));
红色代码部分表示:
如果有重复的key,在值的处理上,保留原来的值,舍弃当前的新值。
当然,也可以有第二种方案:
如果有重复的key,在值的处理上,新值覆盖旧值。
(oldValue, value) -> value
{1=ClazzDuplicateKeyInCollectMap{key=1, age=10}, 2=ClazzDuplicateKeyInCollectMap{key=2, age=30}}
Process finished with exit code 0
说明:成功解决问题。
我是程序员娟娟,
致力将工作中遇到的问题和解决方案记录下来,
分享给更多需要的同行。
如果对你有帮助,不妨点个关注吧!