您好,登錄后才能下訂單哦!
這篇文章給大家介紹Collectors.toMap的問題是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
??雖然JDK9.0已經出來了,不過我們系統最近才開始全面引入JDK1.8,JDK1.8也已經出來了好久了,各方面都挺穩定的。最近在使用lambda表達式的Collectors.toMap方法時就遇到了一個問題。
大致源碼如下:
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省略 }
運行程序,直接提示:
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導致的。大概如下:
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,就會拋出空指針異常,原來問題是這樣的。
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.
看了下,在openJDK的bug列表里還有這個呢:JDK-8148463,不知道這到底算不算bug呢。地址:Collectors.toMap fails on null values
問題歸問題,我們還是需要通過其他的方式解決的。
解決方式1
原來for循環的方式,亦或是forEach的方式:
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()); }
解決方式2
使用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);
關于Collectors.toMap的問題是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。