Java日记(day27)--Java-Stream流式计算

1,099 阅读20分钟

Java-Stream流式计算

1、概述

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程 序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进 行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。 也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

为什么要使用Stream API

实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要 Java层面去处理。

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中, 后者主要是面向 CPU,通过 CPU 实现计算。

什么是 Stream

Stream到底是什么呢? 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。 “集合讲的是数据,Stream讲的是计算!

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如filter, map, reduce, find, match, sorted 等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

以上的流程转换为 Java 代码为:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

注意:

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时(终止操作)才执行。
  • 和以前的Collection操作不同, Stream操作还有两个基础的特征:
    • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
    • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

Stream 的操作三个步骤

  • 创建 Stream 一个数据源(如:集合、数组),获取一个流
  • 中间操作:一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作): 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用

image.png

2、Stream的实例化

2.1、创建 Stream方式一:通过集合

通过集合 Java8 中的 Collection 接口被扩展,提供了两个获取流 的方法:

  • default Stream<E> stream() : 返回一个顺序流
  • default Stream<E> parallelStream() : 返回一个并行流

default Stream<E> stream() : 返回一个顺序流

public class Demo1 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("123");
        arrayList.add("124");
        arrayList.add("125");
        arrayList.add("126");

        Stream<String> stream = arrayList.stream();
              
        //这个时候我们直接调用了终止操作遍历了流中的内容
        stream.forEach(System.out :: println);
    }
}

使用集合调用这个stream()方法,会返回一个顺序流

  • 这里返回的是一个顺序流,因为这个时候这个流加载集合中的元素的时候是一个元素一个元素顺序进行加载的,这个时候这个流中就是按照加载的顺序
  • 这个方法定义在了接口中,所以这个方法声明为default(默认的)
  • 这里我们说这个方法定义在了接口中不仅仅是说这个方法声明在接口中,而是说这个方法的具体实现也是在接口中实现的,这个时候接口中的实现方法就是定义为default的方法

default Stream<E> parallelStream() : 返回一个并行流

public class Demo1 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("123");
        arrayList.add("124");
        arrayList.add("125");
        arrayList.add("126");

        Stream<String> stream = arrayList.parallelStream();
              
        //这个时候我们直接调用了终止操作遍历了流中的内容
        stream.forEach(System.out :: println);
    }
}

用集合调用这个parallelStream()方法我们会得到一个并行流对象

  • 这里返回的是一个并行流,因为这个时候这个流加载这个集合中的元素的时候是一次几个一次几个这样加载的 ,就类似于我们的多线程一样,这个时候一次加载多个,并发操作,这个时候就不能保证先后顺序,所以这个时候我们获得的并行流对象就不是按照我们的原本的集合的顺序的
  • 这个方法也是定义在了接口中,这个时候我们要在接口中就具体实现这个方法,这个时候我们就只能将这个方法声明为default(默认的)
  • 接口中到方法声明为default之后这个方法就不再是抽象方法,这个时候这个方法就可以有方法体

2.2、创建 Stream方式二:通过数组

通过数组 Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static < T> Stream< T> stream() :返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

实例1

public class Demo3 {
    public static void main(String[] args) {
        int [] arr  = new int[]{1,2,3,4,5};

        /*
        这个时候我们就是通过调用Arrays类中的stream()方法,
        然后我们将我们创建的int类型的数组作为参数传递到这个方法中
        作为这个方法的参数,然后我们就可以得到一个IntStream类型的对象
        public static IntStream stream(int[] array) 
         */
        IntStream intStream = Arrays.stream(arr);

        /*
        这时候我们直接通过我们得到的Stream流对象调用了一个终止操作进行了这个流对象的遍历,
        这个时候我们没有执行中间操作
         */
        intStream.forEach(System.out :: println);
    }
}

实例2:自定义类型

public class Employee {
    private int age;
    private String name;
    public Employee(){

    }
    public Employee(int age, String name){
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Employee{" +
            "age=" + age +
            ", name='" + name + '\'' +
            '}';
    }
}

然后我们创建一个Employee类型的数组,然后再通过使用Arrays类中的stream()方法,将我们创建的Employee类型的数组作为参数传入这个方法中,然后我们就可以获得对应的流对象

public class Demo2 {
    public static void main(String[] args) {

        Employee e1 = new Employee(20,"马云");
        Employee e2 = new Employee(30,"马化腾");

        Employee [] arr = new Employee[]{e1,e2};

        /*
        这里我们就是通过调用Arrays类中的stream()方法得到了对应的流对象
		static <T> Stream<T> stream()
        注意: 这个方法是一个泛型方法,这个泛型方法的泛型是通过传入这个方法的数组的类型确定的
        所以这个方法的返回值,也就是返回的Stream对象的泛型就是传入这个方法中作为参数的数组的类型
         */
        Stream<Employee> stream = Arrays.stream(arr);

        /*
        这个时候我们没有使用中间操作,直接使用终止操作将这个流中的数据输出了出来
         */
        stream.forEach(System.out :: println);

    }
}

这个时候我们调用的是static < T> Stream< T> stream();

  • 这个方法的返回值为Stream< T>,这个时候的返回值是以泛型的形式体现出来的
  • 这个时候之所以调用的是这个方法,是因为这个时候我们的这个stream()方法中传入的是一个引用类型作为类型的数组

2.3、创建 Stream方式三:通过使用Stream类中的of()静态方法

可以调用Stream类静态方法of(), 通过显示值创建一个 流。它可以接收任意数量的参数。

public static Stream of(T... values) : 返回一个流

  • 这个方法的参数是一个可变形参(就是可变长度的参数)
  • 可变形参的本质其实就是一个数组
  • 这个方法是一个泛型方法
  • 这个方法是一个静态方法

实例

import java.util.stream.Stream;
public class Demo4 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1,2,3,4,5);
        stream.forEach(System.out :: println);
    }
}

2.4、创建 Stream方式四:创建无限流

这里提供的创建无限流的方式有两种:

迭代

  • 迭代,调用方法 :public static <T> Stream<T> iterate(final T seed , final UnavyOperator<T> f);
    • 这个方法是一个静态方法
    • 这个方法是一个泛型方法
    • 这个方法的第一个参数是一个种子,就是从这个种子开始,这个方法的第二个参数是一个一元函数函数式接口
    • UnavyOperator接口的输入的参数和返回值的类型是同一种类型,这个接口是Function接口(函数接口)的一个子接口

实例 :这个时候我们创建一个无限流 ,使用这个无限流遍历前十个偶数

public class Demo5 {
    public static void main(String[] args) {
        /*
        这个时候我们使用iterate()方法得到一个无限流对象,
        然后我们再通过使用一个中间操作limit()方法限制次数
        然后我们再使用终止操作将这个无限流对象遍历这个无限流对象
         */
        Stream.iterate(0,t -> t + 2).limit(10).forEach(System.out :: println);
    }
}

生成

调用方法:public static <T> stream<T> generate(Supplier<T> s)

  • 这个方法是一个静态方法
  • 这个方法是一个泛型方法
  • 这个方法的参数是一个Supplier接口对象(供给型函数式接口对象)
  • Supplier接口无参,但是有一个返回值

**实例:**这个时候我们创建一个无限流,使用这个无限流产生十个 0.0到1.0之间的数(可以为0.0,但是不可以为1.0)

public class Demo6 {
    public static void main(String[] args) {
        /*
        这个时候我们创建了一个无限流对象,
        然后通过这个无限流对象调用中间操作限制执行次数,
        然后又调用了终止操作对这个流进行打印
         */
        Stream.generate(Math :: random).limit(10).forEach(System.out :: println);
    }
}

3、Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值

条件准备

public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Employee() {}

    public Employee(int id) {
        this.id = id;
    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", 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;

        if (id != employee.id) {
            return false;
        }
        if (age != employee.age) {
            return false;
        }
        if (Double.compare(employee.salary, salary) != 0) {
            return false;
        }
        return name != null ? name.equals(employee.name) : employee.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + age;
        temp = Double.doubleToLongBits(salary);
        result = 31 * result + (int)(temp ^ (temp >>> 32));
        return result;
    }
}

测试类

public class EmployeeData {

    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();

        list.add(new Employee(1001, "马化腾", 34, 6000.38));
        list.add(new Employee(1002, "马云", 12, 9876.12));
        list.add(new Employee(1003, "刘强东", 33, 3000.82));
        list.add(new Employee(1004, "雷军", 26, 7657.37));
        list.add(new Employee(1005, "李彦宏", 65, 5555.32));
        list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
        list.add(new Employee(1007, "任正非", 26, 4333.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));

        return list;
    }
}

3.1、筛选与切片

image.png

实例

@Test
public void test1() {
    List<Employee> list = EmployeeData.getEmployees();
    // filter(Predicate p)-接收 Lambda,从流中排除某些元素。
    Stream<Employee> stream = list.stream();
    // 练习:查询员工表中薪资大于 7000 的员工信息
    stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}

    System.out.println();

    // limit(n)-截断流,使其元素不超过给定数量。
    // stream.limit(3).forEach(System.out::println);//closed已经关闭,需要重新生成
    list.stream().limit(3).forEach(System.out::println);
    // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1003, name='刘强东', age=33, salary=3000.82}

    System.out.println();

    // skip(n)-跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则
    list.stream().skip(3).forEach(System.out::println);
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
    // Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
    // Employee{id=1007, name='任正非', age=26, salary=4333.32}
    // Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}

    System.out.println();

    // distinct()-筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 42, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));

    // System.out.println(list);
    // [Employee{id=1001, name='马化腾', age=34, salary=6000.38}, Employee{id=1002, name='马云', age=12, salary=9876.12},
    // Employee{id=1003, name='刘强东', age=33, salary=3000.82}, Employee{id=1004, name='雷军', age=26, salary=7657.37},
    // Employee{id=1005, name='李彦宏', age=65, salary=5555.32}, Employee{id=1006, name='比尔盖茨', age=42,
    // salary=9500.43}, Employee{id=1007, name='任正非', age=26, salary=4333.32}, Employee{id=1008, name='扎克伯格',
    // age=35, salary=2500.32}, Employee{id=1010, name='刘强东', age=40, salary=8000.0}, Employee{id=1010, name='刘强东',
    // age=42, salary=8000.0}, Employee{id=1010, name='刘强东', age=40, salary=8000.0}, Employee{id=1010, name='刘强东',
    // age=40, salary=8000.0}, Employee{id=1010, name='刘强东', age=40, salary=8000.0}]

    System.out.println();

    list.stream().distinct().forEach(System.out::println);
    // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1003, name='刘强东', age=33, salary=3000.82}
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
    // Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
    // Employee{id=1007, name='任正非', age=26, salary=4333.32}
    // Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
    // Employee{id=1010, name='刘强东', age=40, salary=8000.0}
    // Employee{id=1010, name='刘强东', age=42, salary=8000.0}
}

3.2、映射

image.png

实例

// 映射
@Test
public void test2() {
    // map(Function f)-接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
    // AA
    // BB
    // CC
    // DD

    System.out.println();

    // 练习1:获取员工姓名长度大于 3 的员工的姓名。
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<String> namesStream = employees.stream().map(Employee::getName);
    namesStream.filter(name -> name.length() > 3).forEach(System.out::println);
    // 比尔盖茨
    // 扎克伯格

    System.out.println();

    // 练习2:
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
    streamStream.forEach(s -> {
        s.forEach(System.out::println);
    });
    // a
    // a
    // b
    // b
    // c
    // c
    // d
    // d

    System.out.println();

    // 集合里面套集合的方式,遍历优先使用 flatMap 方式
    // flatMap(Function f)-接受一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
    Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
    characterStream.forEach(System.out::println);
    // a
    // a
    // b
    // b
    // c
    // c
    // d
    // d
}

// 将字符串中的多个字符构成的集合转换为对应的 Stream 的实例
public static Stream<Character> fromStringToStream(String str) {// a
    ArrayList<Character> list = new ArrayList<>();
    for (Character c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}

@Test
public void test3() {
    ArrayList list1 = new ArrayList();
    list1.add(1);
    list1.add(2);
    list1.add(3);

    ArrayList list2 = new ArrayList();
    list2.add(4);
    list2.add(5);
    list2.add(6);

    // list1.add(list2);
    // [1, 2, 3, [4, 5, 6]]
    // System.out.println(list1);

    list1.addAll(list2);
    // [1, 2, 3, 4, 5, 6]
    System.out.println(list1);
}

3.3、排序

image.png

实例

// 3-排序
@Test
public void test4() {
    // sorted()-自然排序
    List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
    list.stream().sorted().forEach(System.out::println);
    // -98
    // 0
    // 7
    // 12
    // 34
    // 43
    // 65
    // 87

    // 抛异常,原因:Employee 没有实现 Comparable 接口
    // List<Employee> employees = EmployeeData.getEmployees();
    // employees.stream().sorted().forEach(System.out::println);

    // sorted(Comparator com)-定制排序

    // List<Employee> employees = EmployeeData.getEmployees();
    // employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(),
    // e2.getAge())).forEach(System.out::println);
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
    // Employee{id=1007, name='任正非', age=26, salary=4333.32}
    // Employee{id=1003, name='刘强东', age=33, salary=3000.82}
    // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
    // Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
    // Employee{id=1005, name='李彦宏', age=65, salary=5555.32}

    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().sorted((e1, e2) -> {
        int ageValue = Integer.compare(e1.getAge(), e2.getAge());
        if (ageValue != 0) {
            return ageValue;
        } else {
            // 年龄相同,按工资从小到大排序
            return Double.compare(e1.getSalary(), e2.getSalary());
            // 年龄相同,按工资从大到小排序
            // return -Double.compare(e1.getSalary(), e2.getSalary());
        }
    }).forEach(System.out::println);
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1007, name='任正非', age=26, salary=4333.32}
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
    // Employee{id=1003, name='刘强东', age=33, salary=3000.82}
    // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
    // Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
    // Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
}

4、Stream 的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。 流进行了终止操作后,不能再次使用,必须再次生成流。

4.1、匹配与查找

image.png

实例

package com.streamAPI;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.Test;

/**
 * Stream 的终止操作
 * 
 */
public class StreamAPITest2 {

    // 1-匹配与查找
    @Test
    public void test1() {

        List<Employee> employees = EmployeeData.getEmployees();
        // allMatch(Predicate p) - 检查是否匹配所有元素。
        // 练习:是否所有的员工的年龄都大于18
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        // false
        System.out.println(allMatch);

        // anyMatch(Predicate p) - 检查是否至少匹配一个元素。
        // 练习:是否存在员工的工资大于10000
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        // false
        System.out.println(anyMatch);

        // noneMatch(Predicate p) - 检查是否没有匹配的元素。(没有返回true,有返回false)
        // 练习:是否存在员工姓"雷"
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        // false
        System.out.println(noneMatch);

        // findFirst - 返回第一个元素
        Optional<Employee> employee = employees.stream().findFirst();
        // Optional[Employee{id=1001, name='马化腾', age=34, salary=6000.38}]
        System.out.println(employee);

        // findAny - 返回当前流中的任意元素
        Optional<Employee> employee1 = employees.parallelStream().findAny();// parallelStream:并行流
        System.out.println(employee1);

    }

    @Test
    public void test2() {
        List<Employee> employees = EmployeeData.getEmployees();

        // count - 返回流中元素的总个数
        long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
        // 5
        System.out.println(count);

        // max(Comparator c) - 返回流中最大值
        // 练习:返回最高的工资:
        Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
        Optional<Double> maxSalary = salaryStream.max(Double::compare);
        // Optional[9876.12]
        System.out.println(maxSalary);

        // min(Comparator c) - 返回流中最小值
        // 练习:返回最低工资的员工
        Optional<Employee> employee =
            employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        // Optional[Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}]
        System.out.println(employee);

        System.out.println();

        // forEach(Consumer c) - 内部迭代
        employees.stream().forEach(System.out::println);
        // 使用集合的遍历操作,效果一样,显示如下:
        employees.forEach(System.out::println);
        // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
        // Employee{id=1002, name='马云', age=12, salary=9876.12}
        // Employee{id=1003, name='刘强东', age=33, salary=3000.82}
        // Employee{id=1004, name='雷军', age=26, salary=7657.37}
        // Employee{id=1005, name='李彦宏', age=65, salary=5555.32}
        // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
        // Employee{id=1007, name='任正非', age=26, salary=4333.32}
        // Employee{id=1008, name='扎克伯格', age=35, salary=2500.32}
    }

4.2、归约

image.png

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

// 2-归约
@Test
public void test3() {
    // reduce(T identity,BinaryOperator) - 可以将流中元素反复结合起来,得到一个值。返回 T
    // 练习1:计算 1-10 自然数的和
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = list.stream().reduce(0, Integer::sum);
    // 55
    System.out.println(sum);

    // reduce(BinaryOperator) - 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
    // 练习2:计算公司所有员工工资的总和
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
    // Optional<Double> sumMoney = salaryStream.reduce(Double::sum);
    Optional<Double> sumMoney = salaryStream.reduce((d1, d2) -> d1 + d2);
    // Optional[48424.08]
    System.out.println(sumMoney);
}

4.3、收集

image.png

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。

// 3-收集
@Test
public void test4() {
    // collect(Collector c):将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法
    // 练习1:查找工资大于 6000 的员工,结果返回为一个 List 或 Set

    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
    employeeList.forEach(System.out::println);
    // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}

    System.out.println();

    Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
    employeeSet.forEach(System.out::println);
    // Employee{id=1001, name='马化腾', age=34, salary=6000.38}
    // Employee{id=1002, name='马云', age=12, salary=9876.12}
    // Employee{id=1006, name='比尔盖茨', age=42, salary=9500.43}
    // Employee{id=1004, name='雷军', age=26, salary=7657.37}
}
}

另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:

image.png

image.png

5、练习

练习1:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;

public class Java8Tester {
    public static void main(String args[]){
        System.out.println("使用 Java 7: ");

        // 计算空字符串
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
        System.out.println("列表: " +strings);
        long count = getCountEmptyStringUsingJava7(strings);

        System.out.println("空字符数量为: " + count);
        count = getCountLength3UsingJava7(strings);

        System.out.println("字符串长度为 3 的数量为: " + count);

        // 删除空字符串
        List<String> filtered = deleteEmptyStringsUsingJava7(strings);
        System.out.println("筛选后的列表: " + filtered);

        // 删除空字符串,并使用逗号把它们合并起来
        String mergedString = getMergedStringUsingJava7(strings,", ");
        System.out.println("合并字符串: " + mergedString);
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

        // 获取列表元素平方数
        List<Integer> squaresList = getSquares(numbers);
        System.out.println("平方数列表: " + squaresList);
        List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);

        System.out.println("列表: " +integers);
        System.out.println("列表中最大的数 : " + getMax(integers));
        System.out.println("列表中最小的数 : " + getMin(integers));
        System.out.println("所有数之和 : " + getSum(integers));
        System.out.println("平均数 : " + getAverage(integers));
        System.out.println("随机数: ");

        // 输出10个随机数
        Random random = new Random();

        for(int i=0; i < 10; i++){
            System.out.println(random.nextInt());
        }

        System.out.println("使用 Java 8: ");
        System.out.println("列表: " +strings);

        count = strings.stream().filter(string->string.isEmpty()).count();
        System.out.println("空字符串数量为: " + count);

        count = strings.stream().filter(string -> string.length() == 3).count();
        System.out.println("字符串长度为 3 的数量为: " + count);

        filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
        System.out.println("筛选后的列表: " + filtered);

        mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
        System.out.println("合并字符串: " + mergedString);

        squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
        System.out.println("Squares List: " + squaresList);
        System.out.println("列表: " +integers);

        IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();

        System.out.println("列表中最大的数 : " + stats.getMax());
        System.out.println("列表中最小的数 : " + stats.getMin());
        System.out.println("所有数之和 : " + stats.getSum());
        System.out.println("平均数 : " + stats.getAverage());
        System.out.println("随机数: ");

        random.ints().limit(10).sorted().forEach(System.out::println);

        // 并行处理
        count = strings.parallelStream().filter(string -> string.isEmpty()).count();
        System.out.println("空字符串的数量为: " + count);
    }

    private static int getCountEmptyStringUsingJava7(List<String> strings){
        int count = 0;

        for(String string: strings){

            if(string.isEmpty()){
                count++;
            }
        }
        return count;
    }

    private static int getCountLength3UsingJava7(List<String> strings){
        int count = 0;

        for(String string: strings){

            if(string.length() == 3){
                count++;
            }
        }
        return count;
    }

    private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){
        List<String> filteredList = new ArrayList<String>();

        for(String string: strings){

            if(!string.isEmpty()){
                filteredList.add(string);
            }
        }
        return filteredList;
    }

    private static String getMergedStringUsingJava7(List<String> strings, String separator){
        StringBuilder stringBuilder = new StringBuilder();

        for(String string: strings){

            if(!string.isEmpty()){
                stringBuilder.append(string);
                stringBuilder.append(separator);
            }
        }
        String mergedString = stringBuilder.toString();
        return mergedString.substring(0, mergedString.length()-2);
    }

    private static List<Integer> getSquares(List<Integer> numbers){
        List<Integer> squaresList = new ArrayList<Integer>();

        for(Integer number: numbers){
            Integer square = new Integer(number.intValue() * number.intValue());

            if(!squaresList.contains(square)){
                squaresList.add(square);
            }
        }
        return squaresList;
    }

    private static int getMax(List<Integer> numbers){
        int max = numbers.get(0);

        for(int i=1;i < numbers.size();i++){

            Integer number = numbers.get(i);

            if(number.intValue() > max){
                max = number.intValue();
            }
        }
        return max;
    }

    private static int getMin(List<Integer> numbers){
        int min = numbers.get(0);

        for(int i=1;i < numbers.size();i++){
            Integer number = numbers.get(i);

            if(number.intValue() < min){
                min = number.intValue();
            }
        }
        return min;
    }

    private static int getSum(List numbers){
        int sum = (int)(numbers.get(0));

        for(int i=1;i < numbers.size();i++){
            sum += (int)numbers.get(i);
        }
        return sum;
    }

    private static int getAverage(List<Integer> numbers){
        return getSum(numbers) / numbers.size();
    }
}

执行以上脚本,输出结果为:

$ javac Java8Tester.java 
$ java Java8Tester
使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9
随机数: 
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

练习2

数字平方排序(倒叙)输出。字符串转 map 输出

import java.util.List;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;
class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        // 获取对应的平方数
        //        List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
        List<Integer> squaresList = numbers.stream()
            .map(i -> i * i)
            .sorted((x, y) -> y - x)
            .collect(Collectors.toList());
        //        squaresList.forEach(System.out::println);
        squaresList.forEach(num -> {
            num++;
            System.out.println(num);
        });

        List<String> strList = Arrays.asList("a", "ba", "bb", "abc", "cbb", "bba", "cab");
        Map<Integer, String> strMap = new HashMap<Integer, String>();

        strMap = strList.stream()
            .collect( Collectors.toMap( str -> strList.indexOf(str), str -> str ) );

        strMap.forEach((key, value) -> {
            System.out.println(key+"::"+value);
        });

    }
}

输出结果为:

50
26
10
10
10
5
5
0::a
1::ba
2::bb
3::abc
4::cbb
5::bba
6::cab