Java8-Stream API

笔记参考视频:https://www.bilibili.com/video/BV184411x7XA?p=19

什么是Stream

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

Stream关注的是对数据的运算

注:

  1. Stream自己不会存储元素
  2. Stream不会改变源对象,而是返回一个持有结果的新Stream
  3. Stream操作是延迟执行的,即会等到需要结果的时候才执行

Stream操作的三个步骤

  1. 创建Stream(实例化)

一个数据源(如:集合、数组),获取一个流

  1. 中间操作(过滤、映射等)

一个中间操作链,对数据源的数据进行处理

  1. 终止操作

一旦执行终止操作,就执行中间操作链,并产生结果。

实体类Employee


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() {
        System.out.println("Employee().....");
    }

    public Employee(int id) {
        this.id = id;
        System.out.println("Employee(int 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;
    }
}

生成数据的类


import java.util.ArrayList;
import java.util.List;

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));
        list.add(new Employee(1009, "张三", 26, 6666));
        return list;
    }

}

Stream的实例化

方法1 通过集合创建Stream
@Test
    public void test1(){
        List<Employee> employees = EmployeeData.getEmployees();
        //返回一个顺序流
        Stream<Employee> stream = employees.stream();
        //返回一个并行流
        Stream<Employee> parallelStream = employees.parallelStream();
    }
方法2 通过数组创建Stream
@Test
public void test2(){
    int[] arr = new int[]{1,2,3,4,5,6};
    //调用Arrays类的static<T> Stream<T> stream(T[] array) 返回一个流
    IntStream stream = Arrays.stream(arr);

    Employee e1 = new Employee(1001,"Tom");
    Employee e2 = new Employee(1002,"Sam");
    Employee[] arr1 = new Employee[]{e1,e2};
    Stream<Employee> stream1 = Arrays.stream(arr1);
}
方法3 通过Stream的of()
@Test
public void test3(){
    Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
}
方法4 创建无限流
@Test
public void test4(){
    //迭代
    //public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)
    //遍历前十个偶数
    Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);

    //生成
    //public static<T> Stream<T> generate(Supplier<T> s)
    Stream.generate(Math::random).limit(10).forEach(System.out::println);
}

Stream的中间操作

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

1.筛选与切片
方法 描述
filter(Predicate p) 接收Lambda,从流中排除某些元素
distinct() 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
@Test
    public void test1(){
    /**
     *      筛选与切片
     * */
//            * | filter(Predicate p) | 接收Lambda,从流中排除某些元素                               |
    System.out.println("-------------------------------------filter-------------------------------------");
    List<Employee> list = EmployeeData.getEmployees();
    Stream<Employee> stream = list.stream();
    stream.filter(e->e.getSalary()>7000).forEach(System.out::println);
//            * | limit(long maxSize) | 截断流,使其元素不超过给定数量                               |
    System.out.println("-------------------------------------limit-------------------------------------");
    list.stream().limit(3).forEach(System.out::println);
//            * | skip(long n)        | 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补 |
    System.out.println("-------------------------------------skip-------------------------------------");
    list.stream().skip(3).forEach(System.out::println);
//            * | distinct()          | 筛选,通过流所生成元素的hashCode()和equals()去除重复元素     |
    System.out.println("-------------------------------------distinct-------------------------------------");
    list.add(new Employee(1003,"刘强东",33,3000.82));
    list.add(new Employee(1003,"刘强东",33,3000.82));
    list.add(new Employee(1003,"刘强东",33,3000.82));
    list.add(new Employee(1003,"刘强东",33,3000.82));
    list.add(new Employee(1003,"刘强东",33,3000.82));
    System.out.println("筛选前:");
    System.out.println(list);
    System.out.println("筛选后:");
    list.stream().distinct().forEach(System.out::println);

}
2.映射
方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的DoubleStream
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的IntStream
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的LongStream
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test
    public void test2(){
    /**
     *          映射
     * */
//          map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
    System.out.println("-------------------------------------map-------------------------------------");
    List<String> list = Arrays.asList("a1", "b2", "c3", "d4", "e5");
    list.stream().map(str->str.toUpperCase()).forEach(System.out::println);

    System.out.println("练习1:获取员工姓名长度大于3的员工的姓名。");
    List<Employee> list0 = EmployeeData.getEmployees();
    //      map
    list0.stream().map(Employee::getName).filter(str->str.length() > 3).forEach(System.out::println);

    System.out.println("练习2");
    System.out.println("-------------------------------------map-------------------------------------");
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
    streamStream.forEach(s->{
        s.forEach(System.out::println);
    });
    System.out.println("-------------------------------------flatmap-------------------------------------");
    list.stream().flatMap(StreamAPITest1::fromStringToStream).forEach(System.out::println);

}
//          将字符串中的多个字符构成的集合转换为对应的Stream的实例
    public static Stream<Character> fromStringToStream(String str){
        ArrayList<Character> list = new ArrayList<>();
        for (Character c : str.toCharArray()){
            list.add(c);
        }
        return list.stream();
    }
3.排序
方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
@Test
    public void test3(){
        //| sorted()               | 产生一个新流,其中按自然顺序排序   |
        System.out.println("-------------------------------------sorted()自然排序-------------------------------------");

        List<Integer> list = Arrays.asList(11, 22, 33, 44, 88, 21, 2, 53, 6);
        list.stream().sorted().forEach(System.out::println);
        //| sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
        System.out.println("-------------------------------------sorted(Comparator com)定制排序-------------------------------------");
        System.out.println("按年龄排序");
        List<Employee> employeeList = EmployeeData.getEmployees();
        employeeList.stream().sorted((e1,e2)-> Integer.compare(e1.getAge(), e2.getAge())).forEach(System.out::println);
        //年龄相同时按工资排序
        System.out.println("年龄相同时按工资排序");
        employeeList.stream().sorted(
                (e1,e2)->{
                    int temp = Integer.compare(e1.getAge(), e2.getAge());
//                    System.out.println(temp);
//                    System.out.println("000000");
                    if (temp!=0){
                        return temp;
                    }else {
                        return Double.compare(e1.getSalary(),e2.getSalary());
                    }
                }
        ).forEach(System.out::println);
    }

Stream的终止操作

1.匹配与查找
方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否匹配至少一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用Collection接口需要用户做迭代,称为外部迭代。相反,Stream API使用内部迭代)
import org.junit.Test;

import java.util.List;
import java.util.Optional;

public class StreamAPITest2 {
    @Test
    public void test1(){
        /**
         * 匹配与查找
         * */
        //| allMatch(Predicate p)  | 检查是否匹配所有元素     |
        System.out.println("-------------------------------------allMatch-------------------------------------");
        System.out.println("练习1:是否所有的员工年龄都大于18");
        List<Employee> list = EmployeeData.getEmployees();
        boolean result1 = list.stream().allMatch(e -> e.getAge() > 18);
        System.out.println(result1);
        //| anyMatch(Predicate p)  | 检查是否匹配至少一个元素 |
        System.out.println("-------------------------------------anyMatch-------------------------------------");
        System.out.println("练习2:是否存在员工的工资大于4000");
        boolean result2 = list.stream().anyMatch(e -> e.getSalary() > 4000);
        System.out.println(result2);
        //| noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
        System.out.println("-------------------------------------noneMatch-------------------------------------");
        System.out.println("练习3:是否没有员工姓“雷”");
        boolean result3 = list.stream().noneMatch(e -> e.getName().startsWith("雷") );
        System.out.println(result3);

        //| findFirst()            | 返回第一个元素           |
        System.out.println("-------------------------------------findFirst-------------------------------------");
        System.out.println("练习4:按年龄从小到大排序并返回第一个结果(返回年龄最小的)");
        Optional<Employee> result4 = list.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).findFirst();
        System.out.println(result4);
        //| findAny()              | 返回当前流中的任意元素   |
        System.out.println("-------------------------------------findAny-------------------------------------");
        System.out.println("练习5:随机返回一个流中的结果");
        System.out.println("串行流:每次返回的结果都是list中的第一个数据");
        Optional<Employee> result5 = list.stream().findAny();
        System.out.println(result5);
        System.out.println("并行流:每次返回的结果都不同,但对同一个源的多次调用可能不会返回相同的结果。");
        Optional<Employee> result6 = list.parallelStream().findAny();
        System.out.println(result6);

        System.out.println("-------------------------------------count-------------------------------------");
        long count = list.stream().filter(employee -> employee.getSalary() > 5000).count();
        System.out.println("工资大于5000的人数:"+count);

        System.out.println("-------------------------------------max-------------------------------------");
        Optional<Employee> max = list.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println("工资最多的员工:"+max);

        System.out.println("-------------------------------------min-------------------------------------");
        Optional<Employee> min = list.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println("工资最少的员工:"+min);

        System.out.println("-------------------------------------forEach-------------------------------------");
        list.stream().forEach(System.out::println);
        
    }
}
2.归约
方法 描述
reduce(T iden,BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回Optional
@Test
public void test2(){
    /**
     * 归约
     * */

    //| reduce(T iden,BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回T           |
    //练习1:计算1-10的自然数的和
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer re = list.stream().reduce(0, Integer::sum);//identity:初始值
    System.out.println(re);
    //| reduce(BinaryOperator b)        | 可以将流中元素反复结合起来,得到一个值。返回Optional<T> |
    //练习2:计算所有员工工资总和
    List<Employee> employeeList = EmployeeData.getEmployees();
    Optional<Double> reduce = employeeList.stream().map(e -> e.getSalary()).reduce(Double::sum);
    System.out.println(reduce);
    Optional<Double> reduce1 = employeeList.stream().map(e -> e.getSalary()).reduce((s1,s2)->s1+s2);
    System.out.println(reduce1);
}
3.收集
方法 描述
collect(Collector c) 将流转换为其他形式。接收以一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map),且Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,常用方法如下表:

方法 描述
toList 把流中的元素收集到List
toSet 把流中的元素收集到Set
toCollection 把流中的元素收集到自定义的集合中
joining 连接流中的每个字符串
counting 计算流中元素个数
averagingDouble 计算流中Double属性元素的平均值
summingDouble 计算流中Double属性元素的和
summarizingDouble 收集流中Double属性的统计值
groupingBy 根据属性的值对流进行分组
partitioningBy 根据true或false对数据进行分区
将流转换为其他形式
@Test
public void test3(){
    /**
     * 收集
     * */
    //collect(Collector c)将流转换为其他形式。接收以一个Collector接口的实现,用于给Stream中元素做汇总的方法
    //练习1:查找工资大于6000的员工,结果返回为一个List或Set
    List<Employee> employeeList = EmployeeData.getEmployees();
    System.out.println("-------------------------------------toList-------------------------------------");

    List<Employee> collect = employeeList.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
    collect.forEach(System.out::println);
    System.out.println("-------------------------------------toSet-------------------------------------");
    Set<Employee> collect1 = employeeList.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
    collect1.forEach(System.out::println);

    System.out.println("-------------------------------------toCollection自定义实现-------------------------------------");
    System.out.println("LinkedList:");
    LinkedList<Employee> collect2 = employeeList.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toCollection(LinkedList::new));
    collect2.forEach(System.out::println);
    System.out.println("Vector:");
    Collection<Employee> collect3 = employeeList.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toCollection(Vector::new));
    collect3.forEach(System.out::println);

}
字符串连接
@Test
public void test4(){
    System.out.println("-------------------------------------joining-------------------------------------");
    List<String> list = Arrays.asList("AAA","BBB","CCC","ddd","eee","fff","GGG","hhh");
    System.out.println("不指定连接符");
    String collect4 = list.stream().collect(Collectors.joining(""));
    System.out.println(collect4);
    System.out.println("指定连接符");
    String collect5 = list.stream().collect(Collectors.joining("---"));
    System.out.println(collect5);
    System.out.println("指定连接符以及前后缀");
    String collect6 = list.stream().collect(Collectors.joining("---","前缀","后缀"));
    System.out.println(collect6);
}
统计
@Test
public void test5(){
    List<Employee> list = EmployeeData.getEmployees();
    System.out.println("-------------------------------------count-------------------------------------");
    Long count = list.stream().collect(Collectors.counting());
    System.out.println(count);
    System.out.println("-------------------------------------average(以averagingDouble为例)-------------------------------------");
    Double averageSalary = list.stream().collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println("平均工资:"+averageSalary);
    System.out.println("-------------------------------------summing(以summingDouble为例)-------------------------------------");
    Double totalSalary = list.stream().collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println("总工资:"+totalSalary);
    System.out.println("-------------------------------------summarizing(以summarizingDouble为例)-------------------------------------");
    DoubleSummaryStatistics summarizingResult = list.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println("统计结果:"+summarizingResult);
    System.out.println("-------------------------------------groupingBy-------------------------------------");
    Map<Integer, List<Employee>> groupByResult = list.stream().collect(Collectors.groupingBy(Employee::getAge));
    System.out.println("分组结果:");
    for (Integer s : groupByResult.keySet()) {
        System.out.println("key:" + s);
        System.out.println("values:" + groupByResult.get(s));
    }
    System.out.println("-------------------------------------partitioningBy-------------------------------------");
    Map<Boolean, List<Employee>> partitioningByResult = list.stream().collect(Collectors.partitioningBy(e -> e.getAge() > 35));
    System.out.println("分区结果:");
    for (Boolean s : partitioningByResult.keySet()) {
        System.out.println("key:" + s);
        System.out.println("values:" + partitioningByResult.get(s));
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐