Java8 中使用Stream 讓List 轉(zhuǎn) Map使用問題小結(jié)
在使用 Java 的新特性 Collectors.toMap() 將 List 轉(zhuǎn)換為 Map 時(shí)存在一些不容易發(fā)現(xiàn)的問題,這里總結(jié)一下備查。
空指針風(fēng)險(xiǎn)
java.lang.NullPointerException
當(dāng) List 中有 null 值的時(shí)候,使用 Collectors.toMap() 轉(zhuǎn)為 Map 時(shí),會報(bào) 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()); --------- 運(yùn)行錯(cuò)誤: 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()
方法合并時(shí),merge 不允許 value 為 null 導(dǎo)致的,源碼如下:
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); ...
解決方法
業(yè)務(wù)控制不要出現(xiàn) Null 值【有 Null 的地方,可以賦值默認(rèn)值】在轉(zhuǎn)換時(shí)加判斷,如果為 null,則給一個(gè)默認(rèn)值
Map<String, String> map = sdsTests.stream().collect(Collectors.toMap(SdsTest::getName, sdsTest -> sdsTest.getAge() == null ? "0" : sdsTest.getAge()));
使用 collect(..) 構(gòu)建,允許空值
Map<String, String> nmap = sdsTests.stream().collect(HashMap::new,(k, v) -> k.put(v.getName(), v.getAge()), HashMap::putAll); // TODO 下游業(yè)務(wù)從Map取值要做NPE判斷
使用 Optional 對值進(jìn)行包裝
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
- 優(yōu)先業(yè)務(wù)控制,盡量避免 List 中存在 Null
- 其次推薦第 4 種方法【使用 Optional 對值進(jìn)行包裝】,能很好的避免 NPE 問題
key重復(fù)風(fēng)險(xiǎn)
java.lang.IllegalStateException: Duplicate key xx
當(dāng) List 中有重復(fù)值的時(shí)候,使用 Collectors.toMap() 轉(zhuǎn)為 Map 時(shí),會報(bào):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()); --------- 運(yùn)行錯(cuò)誤: 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) .....
原因是兩個(gè)參數(shù)的toMap(xx, xx)方法, 當(dāng)出現(xiàn)重復(fù)key觸發(fā)merge時(shí),直接拋出異常。源碼如下:
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)); }; } ...
- 業(yè)務(wù)控制盡量不要出現(xiàn)重復(fù)值
- 出現(xiàn)重復(fù) key 時(shí),使用后面的 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}
出現(xiàn)重復(fù) key 時(shí),把對應(yīng)的 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}
把重復(fù) key 的值拼成一個(gè)集合
...... 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\]}
- 優(yōu)先業(yè)務(wù)控制,盡量避免 List 中出現(xiàn)重復(fù)
- 若存在重復(fù)場景,則根據(jù)實(shí)際業(yè)務(wù)場景選擇具體方法【覆蓋、拼接、搞成集合】
以上就是Java8 中使用Stream 讓List 轉(zhuǎn) Map使用總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java8 List 轉(zhuǎn) Map使用的資料請關(guān)注腳本之家其它相關(guān)文章!
- 詳解Java8新特性Stream之list轉(zhuǎn)map及問題解決
- Java8 實(shí)現(xiàn)stream將對象集合list中抽取屬性集合轉(zhuǎn)化為map或list
- java8快速實(shí)現(xiàn)List轉(zhuǎn)map 、分組、過濾等操作
- java8 Stream list to Map key 重復(fù) value合并到Collectio的操作
- Java8 將一個(gè)List<T>轉(zhuǎn)為Map<String,T>的操作
- Java8中List轉(zhuǎn)Map(Collectors.toMap) 的技巧分享
- Java8中forEach語句循環(huán)一個(gè)List和Map
- Java8中List轉(zhuǎn)Map的多種方式代碼
相關(guān)文章
Java?@SpringBootApplication注解深入解析
這篇文章主要給大家介紹了關(guān)于Java?@SpringBootApplication注解的相關(guān)資料,@SpringBootApplication這個(gè)注解是Spring?Boot項(xiàng)目的基石,創(chuàng)建SpringBoot項(xiàng)目之后會默認(rèn)在主類加上,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02SpringBoot項(xiàng)目中引入本地JAR包配置的幾種方法
SpringBoot有時(shí)需要引入本地JAR包以便重用已有的代碼庫或者第三方庫,本文主要介紹了SpringBoot項(xiàng)目中引入本地JAR包配置的幾種方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08MyBatis如何實(shí)現(xiàn)流式查詢的示例代碼
這篇文章主要介紹了MyBatis 如何實(shí)現(xiàn)流式查詢的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04確保SpringBoot定時(shí)任務(wù)只執(zhí)行一次的常見方法小結(jié)
在Spring Boot項(xiàng)目中,確保定時(shí)任務(wù)只執(zhí)行一次是一個(gè)常見的需求,這種需求可以通過多種方式來實(shí)現(xiàn),以下是一些常見的方法,它們各具特點(diǎn),可以根據(jù)項(xiàng)目的實(shí)際需求來選擇最合適的方法,需要的朋友可以參考下2024-10-10RocketMQ實(shí)現(xiàn)消息分發(fā)的步驟
RocketMQ 實(shí)現(xiàn)消息分發(fā)的核心機(jī)制是通過 Topic、Queue 和 Consumer Group 的配合實(shí)現(xiàn)的,下面給大家介紹RocketMQ實(shí)現(xiàn)消息分發(fā)的步驟,感興趣的朋友一起看看吧2024-03-03