Java8特性-如何理解Stream中的FlatMap

    科技2024-06-20  72

    Java8特性-如何理解Stream中的FlatMap

    文章目录

    Java8特性-如何理解Stream中的FlatMap0.前言1.正文2.总结

    0.前言

      在学习Stream的过程中,FlatMap的理解是受阻点之一,所以单独写一篇来谈谈我对FlatMap的看法。关于Stream的完整博客可以参考我的这篇Java-Stream学习笔记(上)。   在这之前,为了方便演示,写了一个实体类(包含姓名,年龄,工资属性)。

    Employee:

    package com.lordbao.entity; import java.util.Objects; /** * @Author Lord_Bao * @Date 2020/10/5 11:31 * @Version 1.0 */ public class Employee { private String username; private int age; private int salary; public Employee() { } public Employee(String username, int age, int salary) { this.username = username; this.age = age; this.salary = salary; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String toString() { return "Employee{" + "username='" + username + '\'' + ", age=" + age + ", salary=" + salary + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return age == employee.age && salary == employee.salary && Objects.equals(username, employee.username); } @Override public int hashCode() { return Objects.hash(username, age, salary); } }

    1.正文

      <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)。此方法是将Stream中的每个元素映射成一个流,然后将每个流的中的元素都取出来合并,然后返回合并后的新流。

      按照这个逻辑将Stream中的每个元素都映射为Stream,然后将每个Stream都拆解,将拆解后的元素组装成新的Stream。貌似看起来有点模糊,不如Map好理解啊。

      其实这里可以这么分析,什么元素能够映射为Stream呢?换个问题就是Stream是怎么创建的?大多数是通过集合,数组呗。那么我可不可以理解为本来的数据源就是集合或是数组,而它存储的元素恰好也是集合或是数组呢?

      按照这个思路,我设计了如下的例子。

    测试代码:

    @Test public void testFlatMap2(){ //1.二维数组 Employee [] [] employeeArr1 ={ { new Employee("a", 20, 1000),new Employee("c", 35, 2500)}, { new Employee("b", 30, 3100),new Employee("d", 22, 6900),}, { new Employee("e", 37, 10000)} }; Stream.of(employeeArr1).flatMap(Stream::of).forEach(System.out::println); System.out.println("..................分隔..................."); //2.一维数组 存储元素为List List<Employee>[] employeeArr2= new List[]{ Arrays.asList(new Employee("a", 20, 1000), new Employee("c", 35, 2500)), Arrays.asList(new Employee("b", 30, 3100), new Employee("d", 22, 6900)), Arrays.asList(new Employee("e", 37, 10000)) }; Stream.of(employeeArr2).flatMap(List::stream).forEach(System.out::println); System.out.println("..................分隔..................."); //3.List 存储 List List<List<Employee>> employeeList1 = Arrays.asList( Arrays.asList(new Employee("a", 20, 1000), new Employee("c", 35, 2500)), Arrays.asList(new Employee("b", 30, 3100), new Employee("d", 22, 6900)), Arrays.asList(new Employee("e", 37, 10000))); employeeList1.stream().flatMap(List::stream).forEach(System.out::println); System.out.println("..................分隔..................."); //4.List 存储 数组 List<Employee[]> employeeList2 = Arrays.asList( new Employee[]{new Employee("a", 20, 1000), new Employee("c", 35, 2500)}, new Employee[]{new Employee("b", 30, 3100), new Employee("d", 22, 6900)}, new Employee[]{new Employee("e", 37, 10000)} ); employeeList2.stream().flatMap(Stream::of).forEach(System.out::println); }

    测试结果:

    Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000} …分隔… Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000} …分隔… Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000} …分隔… Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000}

      那么FlatMap有什么作用呢?很显然,它非常适合于那些存储元素本身就是集合或数组 的 集合或数组。

    模拟场景:

    按照工资从3000以下,3000到7000,7000以上分为三个档次(已经用数组存储好),现在要求从这些员工中,挑选出年龄大于20的员工,并打印输出。

    思路分析:

    可以用一个List或是数组存储好已经分好的数组,然后根据flatMap的特性以及filter过滤掉年龄小于等于20岁的员工,并打印输出。

    测试代码:

    @Test public void testFlatMap(){ // 工资3000以下 Employee [] arr1 ={ new Employee("a", 20, 1000),new Employee("c", 35, 2500)}; // 工资3000--7000 Employee [] arr2 = { new Employee("b", 30, 3100),new Employee("d", 22, 6900)}; // 工资7000以上 Employee [] arr3= { new Employee("e", 37, 10000)}; Employee [] [] employeeArr ={ arr1,arr2,arr3 }; Arrays.stream(employeeArr).flatMap(Stream::of).filter(ele->ele.getAge()>20).forEach(System.out::println); }

    测试结果:

    Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000}

    现在考虑一个更为广泛的场景:

    场景描述:

    提供一个方法,要求该方法可以传入集合,数组,单个对象,并且有多少数据就可以传入多少数据。而且还能够根据一定的条件,筛选掉一定的数据。最后打印输出结果。

    思路分析:

    根据传入数据多少不限,那么表明参数应当为可变参数列表。根据一定的条件,这说明还要提供一个Predicate参数。

    下面我自己提供了3组数据,类型分别是对象,集合,数组。条件是过滤掉工资小于1000的员工,并要求分别用传统的方法和flatMap方法进行解决。

    主程序

    @Test public void testFlatMap3(){ Employee a = new Employee("a", 20, 1000); Employee [] employeeArr = {new Employee("b", 30, 3000), new Employee("c", 35, 2500)}; List<Employee> employeeList = Arrays.asList(new Employee("d", 22, 7000), new Employee("e", 37, 10000)); filterAndPrint(ele->ele.getSalary()>1000,a,employeeArr,employeeList); System.out.println(".........................分割线........................."); filterAndPrint2(ele->ele.getSalary()>1000,a,employeeArr,employeeList); }

    传统方法:

    public static void filterAndPrint2(Predicate<Employee> predicate,Object ... obj){ for (Object o:obj){ if (o.getClass().isArray()){ Employee [] arr =(Employee[])o; for (Employee employee:arr){ if (predicate.test(employee)) System.out.println(employee); } }else if (o.getClass().equals(Employee.class)) { if (predicate.test((Employee)o)) System.out.println(o); }else{ Collection<Employee> collection= (Collection<Employee>)o; List<Employee> employeeList= new ArrayList<>(); employeeList.addAll(collection); for (Employee employee :employeeList){ if (predicate.test(employee)){ System.out.println(employee); } } } } }

    FlatMap方法:

    public static void filterAndPrint(Predicate<Employee> predicate,Object ... obj){ Arrays.asList(obj).stream().flatMap( ele -> { if (ele.getClass().isArray()) { //如果是数组 return Arrays.stream((Employee[])ele); } else if (ele.getClass().equals(Employee.class)) { //如果仅仅是一个Employee对象 return Stream.of(ele); } else { return ((Collection<Employee>) ele).stream();//如果是一个集合 } } ).filter(ele->predicate.test((Employee)ele)).forEach(System.out::println); }

    测试结果:

    Employee{username=‘b’, age=30, salary=3000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000} …分割线… Employee{username=‘b’, age=30, salary=3000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000}

    具体采用哪种方案,其实不好说,一种是分开治理,一种是集中处理。我觉得其实传统方法也还看得过去…

    2.总结

      总的来说,FlatMap非常适合那些存储元素本身就是集合或数组的 集合或数组。或者更宽泛来讲,我们可以自己手动利用一个数据结构 如 集合或数组,将那些分散的集合或是数组 整合起来批处理。

    Processed: 0.015, SQL: 8