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

Posted Javachichi

tags:

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

文章目录

    前言
    一、Stream 特性
    二、Stream 创建
           2.1 用集合创建流
           2.2 用数组创建流
           2.3 Stream静态方法
     三、Stream 使用案例
           3.1 遍历 forEach
           3.2 过滤 filter
           3.3 查找 findFirst,findAny
           3.4 匹配 match
           3.5 映射 map
           3.6 截取流 limit
           3.7 排序 sorted
           3.8 去重 distinct
           3.9 统计 summaryStatistics
           3.10 归约 reduce
           3.11 归集 toList,toSet,toMap
           3.12 分组 partitioningBy,groupingBy
           3.13 合并 joining
     四、演示代码获取


前言

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 javascript 引擎,新的日期 API,新的Stream API 等。
新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。它可以让你以一种声明的方式处理数据,从而写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

一、Stream 特性

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

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

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

二、Stream 创建

在 Java 8 中, Stream可以由集合或数组创建而来,生成的流有2种类型:
stream() :串行流,由主线程按顺序对流执行操作。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();
123456

2.2 用数组创建流

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

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

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);
123456789

三、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方法,如若测试需自行添加
12345678910111213141516171819202122232425262728
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));
1234567891011

3.1 遍历 forEach

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

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

3.2 过滤 filter

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

// 输出结果
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
12345678

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);

// 输出结果
Student{id='4', name='赵六', age=20, sex=male, score=10.0}
Student{id='7', name='老九', age=20, sex=female, score=66.0}
123456789101112131415161718

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
1234567891011121314

3.5 映射 map

映射,顾名思义,将一个对象映射成另外一个对象。即一个Stream流中的所有元素按照一定的映射规则,映射到另一个流中。映射有map和flatMap两种类型:
map:接收一个函数作为参数,此函数作用到Stream中每一个元素,形成一个新的元素,所有新的元素组成一个新的流。
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]
123456789101112
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]
123456

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);

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

3.7 排序 sorted

sorted 方法用于对流中的元素进行排序,有两种排序:
sorted():自然排序,流中元素需要实现Comparable接口。
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, s2) -> {
    if (s1.getScore() != s2.getScore()) {
        return (int) (s1.getScore() - s2.getScore(玩转 Java8 Stream 流,常用方法,详细用法大合集!

玩转Java8的 Stream 之Collectors收集器

Java8之Stream

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

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

玩转Java8的 Stream 之函数式接口