现在有这样的一个需求: 老板让把所有的员工按年龄进行分组,然后统计各个年龄的人数 。
这个需求,如果是在数据库中,可以直接使用一个 group by 语句进行统计即可,那么在 Java 中的话,可以借助于 Java 8 中 Collectors 类提供的 groupingBy() 方法来实现, groupingBy() 方法返回的是一个 Map
首先来定义一个员工类 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; } @Override public String toString() { return "Staff{" + "name='" + name + '\'' + ", age=" + age + '}'; }}再创建一个 Test.java 类,用来验证将 List
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 staffs = new ArrayList<>(); staffs.add(new Staff("张三", 24)); staffs.add(new Staff("李四", 26)); staffs.add(new Staff("王五", 27)); staffs.add(new Staff("赵六", 24)); Map> 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}]}<如果只需要统计各个年龄的员工数量,那么可以直接使用 Collectors.counting() 方法进行统计,代码如下:
Map staffCountMap = staffs.stream().collect(Collectors.groupingBy(Staff::getAge, Collectors.counting()));System.out.println(staffCountMap);
运行后,输出信息如下:
{24=2, 26=1, 27=1}此时再向员工表中添加一个周七,但是不设置年龄,如下:
public static void main(String[] args) { List 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> 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 key at 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 Collector>> groupingBy(Function<? super T, ? extends K> classifier) { return groupingBy(classifier, toList());}
该方法的入参是 Function<? super T, ? extends K> classifier ,指分类器,也就是上面示例代码中的 Staff::getAge ,在该方法中,又调用了重载方法 groupingBy() ,其定义如下:
public static Collector> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) { // 在默认情况下,使用 groupingBy 方法得到的是 HashMap 类型 // 如果希望返回的是 LinkedHashMap 或者 TreeMap,也可以参考下面的方式 return groupingBy(classifier, HashMap::new, downstream);}
这个方法有两个参数,一个是 classifier ,另一个是 downstream ,这个 downstream 用于如何对分组后的数据进行归并操作,上一个方法中直接传入了 toList() 方法,但是在这个方法中,也并没有看到具体的实现,而是继续调用了另一个重载方法,其定义如下:
public static > Collector groupingBy(Function<? super T, ? extends K> classifier, Supplier mapFactory, Collector<? super T, A, D> downstream) { // 用于存放可变结果的容器 Supplier downstreamSupplier = downstream.supplier(); // 用于将结果值保存到可变容器 BiConsumer downstreamAccumulator = downstream.accumulator(); BiConsumer
这个方法就是 groupingBy() 的最终实现,从上面的代码分析可以看出,在具体的分组过程中,会使用 Objects.requireNonNull() 方法对 key 值进行校验,如果 key 值为空,则会直接抛出异常了,此方法定义如下:
public static T requireNonNull(T obj, String message) { if (obj == null) throw new NullPointerException(message); return obj;}
对于这种空指针异常,该如何处理呢?一般有两种方式:
下面分别使用上面的两种方式改写代码:
public static void main(String[] args) { List 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> 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}]}public static void main(String[] args) { List 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> staffMap = staffs.stream().collect(Collectors.groupingBy(Staff::getAge)); System.out.println(staffMap);} 运行程序,输出结果如下:
| 留言与评论(共有 0 条评论) “” |