Summary of the problem of using Stream to convert List to Map in Java8

  • 2021-09-16 07:13:33
  • OfStack

There are some problems that are not easy to find when converting List into Map by using the new feature Collectors. toMap () of Java, which are summarized here for future reference.

Null pointer risk

java.lang.NullPointerException

When null is in List, java. lang. NullPointerException is reported when Collectors. toMap () is converted to Map, as follows:


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());

---------
 Running error: 
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)
    .....

The reason is toMap() Method is used in the Map.merge() Method merge, merge does not allow value to be caused by null, source code is as follows:


default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    //  Judged here value Can not be null
    Objects.requireNonNull(value);
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
    ...

Solution

For service control, do not have Null value "Where there is Null, you can assign a default value". Adding judgment during conversion, if it is null, give a default value


Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> sdsTest.getAge() == null ? "0" : sdsTest.getAge()));

Build with collect (...), allowing null values


Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll);
// TODO  Downstream business from Map Value to be done NPE Judge 

Wrapping Values with 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"));
------------
 Output: 
bbb.age=0

Recommendations

Give priority to service control and avoid Null in List as much as possible Secondly, the fourth method "wrapping the value with Optional" is recommended, which can avoid the problem of NPE well

key Repeat Risk

java.lang.IllegalStateException: Duplicate key xx

When there are duplicate values in List, when Collectors. toMap () is converted to Map, java. lang. IllegalStateException: Duplicate key xx will be reported, for example


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());

---------
 Running error: 
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)
            .....

The reason is that the toMap (xx, xx) method with two parameters directly throws an exception when merge is triggered by repeated key. The source code is as follows:


public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                            Function<? super T, ? extends U> valueMapper) {
     //  Pay attention to the throwingMerger()
     return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

Next, let's look at the throwingMerger () method: "Pay attention to method comments"


/**
 * 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)); };
 }
...

Solution

Try not to have duplicate values in business control When a duplicate key occurs, overwrite the preceding value with the following 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);

//  Writing style 1
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());

//  Writing style 2
Map<String, String> nmap1 = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge, (k1, k2) -> k2));
System.out.println("nmap1->:" + nmap1.toString());
...
----------------------
 Output: 
nmap->:{aaa=ccc, bbb=bbb}
nmap1->:{aaa=ccc, bbb=bbb}

In case of duplicate key, splice the corresponding value


...
Map<String, String> nmap1 = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, SdsTest::getAge, (k1, k2) -> k1 + "," + k2));
System.out.println("nmap1->:" + nmap1.toString());
...
----------------
 Output: 
nmap1->:{aaa=aaa,ccc, bbb=bbb}

Split the values of repeating key into a set


default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    Objects.requireNonNull(remappingFunction);
    //  Judged here value Can not be null
    Objects.requireNonNull(value);
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
    ...
0

Recommendations:

Give priority to business control and try to avoid duplication in List If there are duplicate scenarios, select the specific method "overlay, splice, and assemble" according to the actual business scenario

The above is the detailed content of the summary of using Stream to convert List to Map in Java8. Please pay attention to other related articles on this site for more information about the use of Java8 List to Map!


Related articles: