Java 8中Collectors.groupingBy方法空指针异常源码分析
現在有這樣的一個需求:老板讓把所有的員工按年齡進行分組,然后統計各個年齡的人數。
這個需求,如果是在數據庫中,可以直接使用一個 group by 語句進行統計即可,那么在 Java 中的話,可以借助于 Java 8 中 Collectors 類提供的 groupingBy() 方法來實現,groupingBy() 方法返回的是一個 Map<key, value> 集合,如果通過 groupingBy() 分組的屬性 key 值為null,就會拋出空指針異常。
首先來定義一個員工類 Staff。
package com.magic.stream;public class Staff {private String name;private Integer age;public Staff(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Staff{" +"name='" + name + '\'' +", age=" + age +'}';} }再創建一個 Test.java 類,用來驗證將 List<Staff> 轉換為 Map<String, List<Staff>>,即按年齡將員工進行分組。
package com.magic.stream;import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors;public class Test {public static void main(String[] args) {List<Staff> staffs = new ArrayList<>();staffs.add(new Staff("張三", 24));staffs.add(new Staff("李四", 26));staffs.add(new Staff("王五", 27));staffs.add(new Staff("趙六", 24));Map<Integer, List<Staff>> staffMap = staffs.stream().collect(Collectors.groupingBy(Staff::getAge));System.out.println(staffMap);} }運行程序,輸出信息如下:
{24=[Staff{name='張三', age=24}, Staff{name='趙六', age=24}], 26=[Staff{name='李四', age=26}], 27=[Staff{name='王五', age=27}]}此時再向員工表中添加一個周七,但是不設置年齡,如下:
public static void main(String[] args) {List<Staff> staffs = new ArrayList<>();staffs.add(new Staff("張三", 24));staffs.add(new Staff("李四", 26));staffs.add(new Staff("王五", 27));staffs.add(new Staff("趙六", 24));staffs.add(new Staff("周七", null));Map<Integer, List<Staff>> staffMap = staffs.stream().collect(Collectors.groupingBy(Staff::getAge));System.out.println(staffMap); }再次運行,此時就會拋出空指針異常,錯誤信息如下:
Exception in thread "main" java.lang.NullPointerException: element cannot be mapped to a null keyat java.util.Objects.requireNonNull(Objects.java:228)at java.util.stream.Collectors.lambda$groupingBy$45(Collectors.java:907)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.magic.stream.Test.main(Test.java:21)這個錯誤信息是如何報出的呢?下面一起來分析一下 Collectors.groupingBy() 這個方法的源碼了。
public static <T, K> Collector<T, ?, Map<K, List<T>>>groupingBy(Function<? super T, ? extends K> classifier) {return groupingBy(classifier, toList()); }該方法的入參是 Function<? super T, ? extends K> classifier,指分類器,也就是上面示例代碼中的 Staff::getAge,在該方法中,又調用了重載方法 groupingBy(),其定義如下:
public static <T, K, A, D>Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream) {return groupingBy(classifier, HashMap::new, downstream); }這個方法有兩個參數,一個是 classifier,另一個是 downstream,這個 downstream 用于如何對分組后的數據進行歸并操作,上一個方法中直接傳入了 toList() 方法,但是在這個方法中,也并沒有看到具體的實現,而是繼續調用了另一個重載方法,其定義如下:
public static <T, K, D, A, M extends Map<K, D>>Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,Supplier<M> mapFactory,Collector<? super T, A, D> downstream) {// 用于存放可變結果的容器Supplier<A> downstreamSupplier = downstream.supplier();// 用于將結果值保存到可變容器BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {// 獲取 key 值,對應于上面的示例中,就是調用 Staff 的 getAge() 方法獲取員工的年齡// 此處會對獲取的值進行 null 校驗K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());downstreamAccumulator.accept(container, t);};// 創建一個合并器BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());@SuppressWarnings("unchecked")Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;// 判斷 finisher 函數是否為恒等函數,如果是則可以忽略,否則需要構建 finisherif (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {// 使用 CollectorImpl 類構建 Map 集合return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);}else {@SuppressWarnings("unchecked")Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();Function<Map<K, A>, M> finisher = intermediate -> {intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));@SuppressWarnings("unchecked")M castResult = (M) intermediate;return castResult;};return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);} }這個方法就是 groupingBy() 的最終實現,從上面的代碼分析可以看出,在具體的分組過程中,會使用 Objects.requireNonNull() 方法對 key 值進行校驗,如果 key 值為空,則會直接拋出異常了,此方法定義如下:
public static <T> T requireNonNull(T obj, String message) {if (obj == null)throw new NullPointerException(message);return obj; }對于這種空指針異常,該如何處理呢?一般有兩種方式:
- 排除掉空值,空值本身沒有任何含義,可以去掉空值數據再進行分組;
- 將空值替換為一個默認值,再進行分組;
下面分別使用上面的兩種方式改寫代碼:
(1)排除掉空值
public static void main(String[] args) {List<Staff> staffs = new ArrayList<>();staffs.add(new Staff("張三", 24));staffs.add(new Staff("李四", 26));staffs.add(new Staff("王五", 27));staffs.add(new Staff("趙六", 24));staffs.add(new Staff("周七", null));Map<Integer, List<Staff>> staffMap = staffs.stream().filter(s -> Objects.nonNull(s.getAge())).collect(Collectors.groupingBy(Staff::getAge));System.out.println(staffMap); }上面使用了 Objects.nonNull() 方法過濾掉了 age 字段為 null 的數據,運行程序,輸出結果如下:
{24=[Staff{name='張三', age=24}, Staff{name='趙六', age=24}], 26=[Staff{name='李四', age=26}], 27=[Staff{name='王五', age=27}]}(2)將空值替換為一個默認值
public static void main(String[] args) {List<Staff> staffs = new ArrayList<>();staffs.add(new Staff("張三", 24));staffs.add(new Staff("李四", 26));staffs.add(new Staff("王五", 27));staffs.add(new Staff("趙六", 24));staffs.add(new Staff("周七", null));// 如果年齡為 null ,則賦值 -1,表示異常數據staffs.stream().filter(s -> Objects.isNull(s.getAge())).forEach(s -> s.setAge(-1));Map<Integer, List<Staff>> staffMap = staffs.stream().collect(Collectors.groupingBy(Staff::getAge));System.out.println(staffMap); }運行程序,輸出結果如下:
{-1=[Staff{name='周七', age=-1}], 24=[Staff{name='張三', age=24}, Staff{name='趙六', age=24}], 26=[Staff{name='李四', age=26}], 27=[Staff{name='王五', age=27}]}總結
以上是生活随笔為你收集整理的Java 8中Collectors.groupingBy方法空指针异常源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 晶体三极管的主要参数
- 下一篇: 计算机图书应分为书法的什么类,计算机书法