玩转 Java8 Stream,让你代码更高效紧凑简洁

Posted 陈皮的JavaLib

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转 Java8 Stream,让你代码更高效紧凑简洁相关的知识,希望对你有一定的参考价值。

系列文章

序号标题文章链接
1我使用 Lambda 表达式将上百行的代码精简到几行https://blog.csdn.net/chenlixiao007/article/details/106366676
2玩转 Java8 Optional,让你代码更紧凑简洁且不再出现空指针https://blog.csdn.net/chenlixiao007/article/details/113358813

目录


前言

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 javascript 引擎,新的日期 API,新的Stream API 等。

新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。它可以让你以一种声明的方式处理数据,从而写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

一、Stream 特性

  1. 元素是特定类型的对象,形成一个队列。 Java中的Stream不会存储元素,而是按需计算按照特定的规则对数据进行计算,一般会输出结果。
  2. Stream不会改变数据源,一般情况下会产生一个新的集合或者新值。
  3. Stream流的来源,可以是集合,数组,I/O channel, 产生器generator 等等。
  4. Stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
  5. 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

Stream流的操作可以大概分为2种:

  1. 中间操作:每次操作都返回流对象本身。
  2. 终端操作:一个流只可以进行一次终端操作,即产生一个新的集合或者新值。终端操作结束后流无法再次使用。

二、Stream 创建

在 Java 8 中, Stream可以由集合或数组创建而来,生成的流有2种类型:

  1. stream() :串行流,由主线程按顺序对流执行操作。
  2. parallelStream() :并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如计算集合中的数量之和。如果流种数据量很大,并行流可以加快处理速度。串行流可以通过parallel()方法把顺序流转换成并行流。

2.1 用集合创建流

因为集合继承或实现了java.util.Collection接口,而Collection接口定义了stream()parallelStream()方法,所以可通过集合的stream() 和parallelStream()方法创建流。

// 创建集合
List<String> list = Arrays.asList("张三", "李四", "王五");
// 创建一个串行流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();

2.2 用数组创建流

使用java.util.Arrays.stream(T[] array)方法用数组创建流。

// 创建数组
String[] persons = "张三", "李四", "王五";
// 创建一个串行流
Stream<String> stream = Arrays.stream(persons);
// 创建一个并行流
Stream<String> parallelStream = Arrays.stream(persons).parallel();

2.3 Stream静态方法

使用Stream的静态方法生成Stream,例如of()iterate()generate()等。

Stream<String> stream2 = Stream.of("张三", "李四", "王五");

// 输出5个奇数 1 3 5 7 9
Stream<Integer> stream3 = Stream.iterate(1, x -> x + 2).limit(5);
stream3.forEach(System.out::println);

// 生成2个UUID
Stream<UUID> stream4 = Stream.generate(UUID::randomUUID).limit(2);
stream4.forEach(System.out::println);

三、Stream 使用案例

以下所有案例会基于学生数据,学生类,以及测试数据如下:

package com.nobody;

/**
 * @Description 学生类
 * @Author Mr.nobody
 * @Date 2021/1/17
 * @Version 1.0
 */
public class Student 
    // 主键
    private String id;
    // 姓名
    private String name;
    // 年龄
    private int age;
    // 性别
    private String sex;
    // 成绩
    private double score;

    public Student(String id, String name, int age, String sex, double score) 
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.score = score;
    
	// 省略get和set方法,toString方法,如若测试需自行添加
List<Student> students = new ArrayList<>(16);
students.add(new Student("1", "张三", 18, "male", 88));
students.add(new Student("2", "李四", 17, "male", 60));
students.add(new Student("3", "王五", 18, "male", 100));
students.add(new Student("4", "赵六", 20, "male", 10));
students.add(new Student("5", "董七", 14, "female", 95));
students.add(new Student("6", "幺八", 21, "male", 55));
students.add(new Student("7", "老九", 20, "female", 66));
students.add(new Student("8", "小明", 18, "male", 100));
students.add(new Student("9", "小红", 22, "female", 95));
students.add(new Student("10", "小张", 25, "male", 90));

3.1 遍历 forEach

students.stream().forEach(System.out::println);

// 输出结果
Studentid='1', name='张三', age=18, sex=male, score=88.0
Studentid='2', name='李四', age=17, sex=male, score=60.0
Studentid='3', name='王五', age=18, sex=male, score=100.0
Studentid='4', name='赵六', age=20, sex=male, score=10.0
Studentid='5', name='董七', age=14, sex=female, score=95.0
Studentid='6', name='幺八', age=21, sex=male, score=55.0
Studentid='7', name='老九', age=20, sex=female, score=66.0
Studentid='8', name='小明', age=18, sex=male, score=100.0
Studentid='9', name='小红', age=22, sex=female, score=95.0
Studentid='10', name='小张', age=25, sex=male, score=90.0

3.2 过滤 filter

// 过滤出成绩100分的学生
List<Student> students1 =
        students.stream().filter(student -> student.getScore() == 100).collect(Collectors.toList());
students1.forEach(System.out::println);

// 输出结果
Studentid='3', name='王五', age=18, sex=male, score=100.0
Studentid='8', name='小明', age=18, sex=male, score=100.0

3.3 查找 findFirst,findAny

一般filter和find搭配使用,即从过滤符合条件的数据中,获得一个数据。

// 串行流,匹配第一个
Optional<Student> studentOptional =
        students.stream().filter(student -> student.getAge() >= 20).findFirst();
if (studentOptional.isPresent()) 
    Student student = studentOptional.get();
    System.out.println(student);

// 上面输出语句可简写如下
// studentOptional.ifPresent(System.out::println);

// 并行流,匹配任意一个,findAny一般用于并行流
Optional<Student> studentOptiona2 =
        students.parallelStream().filter(student -> student.getAge() >= 20).findAny();
studentOptiona2.ifPresent(System.out::println);

// 输出结果
Studentid='4', name='赵六', age=20, sex=male, score=10.0
Studentid='7', name='老九', age=20, sex=female, score=66.0

3.4 匹配 match

// 是否存在100分的学生
boolean anyMatch = students.stream().anyMatch(student -> student.getScore() == 100);
// 是否全部学生都100分
boolean allMatch = students.stream().allMatch(student -> student.getScore() == 100);
// 是否全部学生都没有100分
boolean noneMatch = students.stream().noneMatch(student -> student.getScore() == 100);
System.out.println(anyMatch);
System.out.println(allMatch);
System.out.println(noneMatch);

// 输出结果
true
false
false

3.5 映射 map

映射,顾名思义,将一个对象映射成另外一个对象。即一个Stream流中的所有元素按照一定的映射规则,映射到另一个流中。映射有map和flatMap两种类型:

  1. map:接收一个函数作为参数,此函数作用到Stream中每一个元素,形成一个新的元素,所有新的元素组成一个新的流。
  2. flatMap:接收一个函数作为参数,它将流中的每个元素都转换成另一个流,然后把所有流再连接形成一个最终流。
// 获取每个学生的姓名
List<String> studentNames =
        students.stream().map(Student::getName).collect(Collectors.toList());
System.out.println(studentNames);
// 每个学生的成绩加10分
List<Double> studentScores = students.stream().map(student -> student.getScore() + 10)
        .collect(Collectors.toList());
System.out.println(studentScores);

// 输出结果
[张三, 李四, 王五, 赵六, 董七, 幺八, 老九, 小明, 小红, 小张]
[98.0, 70.0, 110.0, 20.0, 105.0, 65.0, 76.0, 110.0, 105.0, 100.0]
List<String> list = Arrays.asList("a-b-c-d", "g-h-i");
List<String> list1 = list.stream().flatMap(s -> Arrays.stream(s.split("-"))).collect(Collectors.toList());
System.out.println(list1);

// 输出结果
[a, b, c, d, g, h, i]

3.6 截取流 limit

// limit方法用于获取指定数量的流。例如下面演示取出学习成绩大于70的5个人
List<Student> students2 = students.stream().filter(student -> student.getScore() > 70)
        .limit(5).collect(Collectors.toList());
students2.forEach(System.out::println);
// 跳过第一个再取2个
List<Student> students8 = students.stream().skip(1).limit(2).collect(Collectors.toList());
// 获取5个int随机数,按从小到大排序
Random random = new Random();
random.ints().limit(5).sorted().forEach(System.out::println);

// 输出结果
Studentid='1', name='张三', age=18, sex=male, score=88.0
Studentid='3', name='王五', age=18, sex=male, score=100.0
Studentid='5', name='董七', age=14, sex=female, score=95.0
Studentid='8', name='小明', age=18, sex=male, score=100.0
Studentid='9', name='小红', age=22, sex=female, score=95.0
-1490202714
145340547
368332155
388399398
1099579920

3.7 排序 sorted

sorted 方法用于对流中的元素进行排序,有两种排序:

  1. sorted():自然排序,流中元素需要实现Comparable接口。
  2. sorted(Comparator<? super T> comparator):需要自定义排序器。
// 按成绩升序
List<Student> students3 = students.stream().sorted(Comparator.comparing(Student::getScore))
        .collect(Collectors.toList());
System.out.println("按成绩升序");
students3.forEach(System.out::println);
// 按成绩降序
List<Student> students4 =
        students.stream().sorted(Comparator.comparing(Student::getScore).reversed())
                .collect(Collectors.toList());
System.out.println("按成绩降序");
students4.forEach(System.out::println);
// 按成绩升序,再按年龄升序
List<Student> students5 = students.stream()
        .sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getAge))
        .collect(Collectors.toList());
System.out.println("按成绩升序,再按年龄升序");
students5.forEach(System.out::println);
// 按成绩升序,再按年龄降序
List<Student> students6 = students.stream().sorted((s1以上是关于玩转 Java8 Stream,让你代码更高效紧凑简洁的主要内容,如果未能解决你的问题,请参考以下文章

玩转 Java8 Stream 流,常用方法,详细用法大合集!

玩转Java8的 Stream 之Collectors收集器

Java8之Stream

Java8 Stream 极大简化了代码,它是如何实现的?

Java8 那些事儿:Stream 函数式编程

玩转Java8的 Stream 之函数式接口