雖然JDK9.0已經出來了,不過咱們系統最近纔開始全面引入JDK1.8,JDK1.8也已經出來了很久了,各方面都挺穩定的。最近在使用lambda表達式的Collectors.toMap方法時就遇到了一個問題。
大體源碼以下:java
public class Test { public static void main(String[] args) { // initMemberList爲獲取數據的方法 List<Member> list = Test.initMemberList(); Map<String, String> memberMap = list.stream().collect(Collectors.toMap(Member::getId, Member::getImgPath)); System.out.println(memberMap); } } class Member { private String id; private String imgPath; // get set省略 }
運行程序,直接提示:app
Exception in thread "main" java.lang.NullPointerException at java.util.HashMap.merge(HashMap.java:1224) at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320) at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) 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.jdk.test.Test.main(Test.java:13)
想來想去,一開始一直沒想明白爲何會爲空指針呢,由於list是不可能爲null的,無奈後來拿着java.util.HashMap.merge加NullPointerException異常上網搜索了一下。原來是因爲在由對象轉爲Map的時候,轉爲map的value是null致使的。大概以下:less
public static List<Member> initMemberList() { Member member1 = new Member(); member1.setId("id_1"); member1.setImgPath("http://www.baidu.com"); // 這裏有一個null致使的 Member member2 = new Member(); member2.setId("id_2"); member2.setImgPath(null); List<Member> list = new ArrayList<>(); list.add(member1); list.add(member2); return list; }
大體看了下源碼,原來Collectors.toMap底層是基於Map.merge方法來實現的,而merge中value是不能爲null的,若是爲null,就會拋出空指針異常,原來問題是這樣的。google
Collectors.toMap() internally uses Map.merge() to add mappings to the map. Map.merge() is spec'd not to allow null values, regardless of whether the underlying Map supports null values. This could probably use some clarification in the Collectors.toMap() specifications.lua
看了下,在openJDK的bug列表裏還有這個呢:JDK-8148463,不知道這到底算不算bug呢。地址:Collectors.toMap fails on null valuesspa
問題歸問題,咱們仍是須要經過其餘的方式解決的。.net
解決方式1指針
原來for循環的方式,亦或是forEach的方式:code
Map<String, String> memberMap = new HashMap<>(); list.forEach((answer) -> memberMap.put(answer.getId(), answer.getImgPath())); System.out.println(memberMap); Map<String, String> memberMap = new HashMap<>(); for (Member member : list) { memberMap.put(member.getId(), member.getImgPath()); }
解決方式2orm
使用stream的collect的重載方法:
Map<String, String> memberMap = list.stream().collect(HashMap::new, (m,v)-> m.put(v.getId(), v.getImgPath()),HashMap::putAll); System.out.println(memberMap);
解決方式3
繼承Collector,手動實現toMap方法,而後調用咱們本身封裝的toMap方法就能夠了。有關實現Collector,可參考:JDK8 Stream API中Collectors中toMap方法的問題以及解決方案
其實無論這是否是bug,說到底,仍是JDK1.8的lambda表達式用的太少,瞭解的太少致使的問題。因此說仍是應該多去使用新技術,多踩坑。
stackoverflow地址:# Java 8 NullPointerException in Collectors.toMap
後記
使用Collector.toMap又發現了一個問題,Map中的key不能重複,若是重複的話,會拋出異常:
public static List<Member> initMemberList() { Member member1 = new Member(); member1.setId("id_1"); member1.setImgPath("http://www.google.com"); Member member2 = new Member(); member2.setId("id_1"); member2.setImgPath("http://www.baidu.com"); List<Member> list = new ArrayList<>(); list.add(member1); list.add(member2); return list; }
以上代碼,運行時,提示錯誤:
Exception in thread "main" java.lang.IllegalStateException: Duplicate key http://www.google.com at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) at java.util.HashMap.merge(HashMap.java:1253) at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320) at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) 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.jdk.test.Test.main(Test.java:13)
經過查看Collectors.toMap的代碼及註釋咱們會發現:
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) { return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new); }
If the mapped keys contains duplicates (according to Object#equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
因此說呢,咱們使用的Collectors.toMap的方法是不支持key重複的,而且若是有重複的時候,建議咱們使用toMap(Function, Function, BinaryOperator) 方法來替換使用,而且咱們還能夠定義當key重複的時候,是使用舊的數據仍是使用新的數據呢,除了選擇使用新舊數據,固然也能夠作一些額外的操做,但該方法仍是會有value爲null的問題哦。
即:
Map<String, String> memberMap = list.stream().collect(Collectors.toMap(Member::getId, Member::getImgPath, (oldValue,newValue) -> oldValue)); System.out.println(memberMap);
一樣,在openJDK的bug列表裏天然也少不了這個小bug:JDK-8040892
Incorrect message in Exception thrown by Collectors.toMap(Function,Function)
不過,在JDK9裏這個bug應該是被修復了的:JDK-8173464
Wrong exception message when collecting a stream to a map
Pallavi Sonal added a comment - 2017-01-27 00:42
This has been fixed in JDK 9 with JDK-8040892.
In JDK8 versions, it throws the wrong message i.e. instead of Duplicate key <KEY>, it shows Duplicate key <VALUE>.
再多說一句,toMap方法還有一個重載方法,是能夠指定一個Map的具體實現,該方法或許有時候咱們會用到呢。
Map<String, String> memberMap = list.stream().collect(Collectors.toMap(Member::getId, Member::getImgPath, (oldValue,newValue) -> oldValue, HashMap::new)); System.out.println(memberMap);