Java Collectors

Posted 3xy0x6yy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Collectors相关的知识,希望对你有一定的参考价值。

当 List 中有 null 值的时候,使用 Collectors.toMap() 转为 Map 时,会报 java.lang.NullPointerException,如下:

复制代码
List<SdsTest> sdsTests = new ArrayList<>();

SdsTest sds1 = new SdsTest("aaa","aaa");
SdsTest sds2 = new SdsTest("bbb",null);

sdsTests.add(sds1);
sdsTests.add(sds2);

Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge));    
System.out.println(map.toString());

运行错误:
Exception in thread "main" java.lang.NullPointerException

at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$150(Collectors.java:1320)
.....

复制代码
  原因是 toMap() 方法中使用 Map.merge() 方法合并时,merge 不允许 value 为 null 导致的,源码如下:

复制代码
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {

Objects.requireNonNull(remappingFunction);
// 在这里判断了value不可为null
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
...

复制代码
解决方法
业务控制不要出现 Null 值【有 Null 的地方,可以赋值默认值】
在转换时加判断,如果为 null,则给一个默认值
Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> sdsTest.getAge() == null ? "0" : sdsTest.getAge()));
使用 collect(..) 构建,允许空值
Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll);
// TODO 下游业务从Map取值要做NPE判断
使用 Optional 对值进行包装
复制代码
Map<String, Optional<String>> opmap = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> Optional.ofNullable(sdsTest.getAge())));

System.out.println("bbb.age=" + opmap.get("bbb").orElse("0"));

输出:
bbb.age=0
复制代码
建议
优先业务控制,尽量避免 List 中存在 Null
其次推荐第 4 种方法【使用 Optional 对值进行包装】,能很好的避免 NPE 问题
key重复风险
java.lang.IllegalStateException: Duplicate key xx

当 List 中有重复值的时候,使用 Collectors.toMap() 转为 Map 时,会报:java.lang.IllegalStateException: Duplicate key xx,例如

复制代码
List<SdsTest> sdsTests = new ArrayList<>();

SdsTest sds1 = new SdsTest("aaa","aaa");
SdsTest sds2 = new SdsTest("aaa","ccc");

sdsTests.add(sds1);
sdsTests.add(sds2);

Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge));    
System.out.println(map.toString());

运行错误:
Exception in thread "main" java.lang.IllegalStateException: Duplicate key aaa

    at java.util.stream.Collectors.lambda$throwingMerger$92(Collectors.java:133)
    at java.util.stream.Collectors$$Lambda$6/1177096266.apply(Unknown Source)
    at java.util.HashMap.merge(HashMap.java:1245)
        .....

复制代码
原因是两个参数的toMap(xx, xx)方法, 当出现重复key触发merge时,直接抛出异常。源码如下:

复制代码
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,

                        Function<? super T, ? extends U> valueMapper) {
 // 注意这里的throwingMerger()
 return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);

}
复制代码
接下来我们看throwingMerger() 方法:【注意方法注释】

复制代码
/**

  • 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 <T> the type of input arguments to the merge function
  • @return a merge function which always throw {@code IllegalStateException}
    */
    private static <T> BinaryOperator<T> throwingMerger() {
    return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
    }

...
复制代码
解决方法
业务控制尽量不要出现重复值
出现重复 key 时,使用后面的 value 覆盖前面的 value
复制代码
SdsTest sds1 = new SdsTest("aaa","aaa");
SdsTest sds2 = new SdsTest("bbb","bbb");
SdsTest sds3 = new SdsTest("aaa","ccc");

sdsTests.add(sds1);
sdsTests.add(sds2);
sdsTests.add(sds3);

// 写法一
Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll);
System.out.println("nmap->:" + nmap.toString());

// 写法二
Map<String, String> nmap1 = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge, (k1, k2) -> k2));
System.out.println("nmap1->:" + nmap1.toString());

...

输出:
nmap->:{aaa=ccc, bbb=bbb}
nmap1->:{aaa=ccc, bbb=bbb}
复制代码
出现重复 key 时,把对应的 value 拼接起来
复制代码
...
Map<String, String> nmap1 = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge, (k1, k2) -> k1 + "," + k2));
System.out.println("nmap1->:" + nmap1.toString());

...

输出:
nmap1->:{aaa=aaa,ccc, bbb=bbb}
复制代码
把重复 key 的值拼成一个集合
复制代码
......
Map<String, List<String>> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName,

s -> {
    List<String> ages = new ArrayList<>();
    ages.add(s.getAge());
    return ages;
},
(List<String> v1, List<String> v2) -> {
    v1.addAll(v2);
    return v1;
}));

System.out.println("map->"+map.toString());

输出:
map->{aaa=[aaa, ccc], bbb=[bbb]}

以上是关于Java Collectors的主要内容,如果未能解决你的问题,请参考以下文章

java8的stream中Collectors.toMap空指针问题

细述 Java垃圾回收机制→Types of Java Garbage Collectors

java8中Collectors.groupingBy下用Collectors.reducing的疑惑

java Collectors.groupingBy(),Collectors.mapping() - grupowanietiveługpola+przekształcenieobiektugrup

03Java 8 - Collectors.joining() 详解

玩转Java8的 Stream 之Collectors收集器