网站公告

本次更新:没啥更新的

“钱塘江上潮信起,今日方知我是我”

Skip to content

Java Stream 中间操作 ​

字数: 0 字 时长: 0 分钟

1. 概述 ​

Stream 的中间操作用于对流中的数据进行处理。

常见的中间操作包括:

text
filter
map
distinct
sorted
limit
skip
flatMap

中间操作通常会返回一个新的 Stream,因此可以继续进行链式调用:

java
authors.stream()
        .distinct()
        .filter(author -> author.getAge() < 18)
        .map(author -> author.getName());

中间操作具有惰性,如果没有终结操作,通常不会真正执行。


2. filter() ​

filter() 用于根据条件过滤流中的元素。

符合条件的元素会继续保留在 Stream 中,不符合条件的元素会被过滤掉。

例如:打印姓名长度大于 1 的作者姓名。

java
List<Author> authors = getAuthors();

authors.stream()
        .filter(author -> author.getName().length() > 1)
        .forEach(author -> System.out.println(author.getName()));

执行过程:

text
所有 Author
    ↓
判断 name.length() > 1
    ↓
保留符合条件的 Author

3. map() ​

map() 用于对 Stream 中的元素进行转换或计算。

可以理解为:

text
原来的元素
    ↓
执行转换
    ↓
新的元素

3.1 类型转换 ​

例如将 Author 转换成作者姓名 String:

java
List<Author> authors = getAuthors();

authors.stream()
        .map(author -> author.getName())
        .forEach(name -> System.out.println(name));

此时 Stream 中元素的类型发生了变化:

text
Stream<Author>
      ↓
map()
      ↓
Stream<String>

3.2 数据计算 ​

例如获取作者年龄,并将年龄增加 10:

java
List<Author> authors = getAuthors();

authors.stream()
        .map(author -> author.getAge())
        .map(age -> age + 10)
        .forEach(age -> System.out.println(age));

执行过程:

text
Author
  ↓
Integer 年龄
  ↓
年龄 + 10

4. distinct() ​

distinct() 用于去除 Stream 中的重复元素。

例如打印所有作者姓名,并去除重复作者:

java
List<Author> authors = getAuthors();

authors.stream()
        .distinct()
        .forEach(author -> System.out.println(author.getName()));

对于自定义对象,distinct() 判断元素是否重复依赖:

java
equals()
hashCode()

因此实体类需要正确实现 equals() 和 hashCode()。

例如使用 Lombok:

java
@EqualsAndHashCode
public class Author {
}

distinct() 能否正确去重,取决于对象的 equals() 和 hashCode() 实现。


5. sorted() ​

sorted() 用于对 Stream 中的元素进行排序。

主要有两种形式:

java
sorted()
java
sorted(Comparator)

5.1 使用 Comparator 排序 ​

例如:

对作者进行去重,然后按照年龄降序排序。

java
List<Author> authors = getAuthors();

authors.stream()
        .distinct()
        .sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge()))
        .forEach(author -> System.out.println(author));

其中:

java
o2.getAge().compareTo(o1.getAge())

表示按照年龄降序排列。


5.2 使用自然排序 ​

如果直接调用:

java
sorted()

Stream 中的对象需要支持自然排序。

例如让 Author 实现 Comparable:

java
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Author implements Comparable<Author> {

    private Long id;

    private String name;

    private Integer age;

    private String intro;

    private List<Book> books;

    @Override
    public int compareTo(Author o) {
        return this.age.compareTo(o.getAge());
    }
}

之后就可以直接调用:

java
List<Author> authors = getAuthors();

authors.stream()
        .distinct()
        .sorted()
        .forEach(author -> System.out.println(author.getAge()));

6. limit() ​

limit() 用于限制 Stream 中最多保留多少个元素。

基本格式:

java
limit(n)

例如:

对作者进行去重,按照年龄降序排序,然后打印年龄最大的两个作者。

java
List<Author> authors = getAuthors();

authors.stream()
        .distinct()
        .sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge()))
        .limit(2)
        .forEach(author -> System.out.println(author.getName()));

执行过程:

text
作者集合
   ↓
去重
   ↓
年龄降序排序
   ↓
保留前 2 个

7. skip() ​

skip() 用于跳过 Stream 前面的若干个元素。

基本格式:

java
skip(n)

例如:

对作者进行去重并按照年龄降序排序,然后跳过年龄最大的作者。

java
List<Author> authors = getAuthors();

authors.stream()
        .distinct()
        .sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge()))
        .skip(1)
        .forEach(author -> System.out.println(author.getName()));

执行过程:

text
年龄降序后的作者
      ↓
skip(1)
      ↓
跳过年龄最大的作者

8. flatMap() ​

map() 通常是把一个元素转换成另一个元素:

text
一个元素
   ↓
一个元素

而 flatMap() 可以将一个元素转换成一个 Stream,然后把多个 Stream 展开并合并成一个 Stream。

例如:

text
Author
  ↓
List<Book>
  ↓
Stream<Book>
  ↓
合并
  ↓
一个 Stream<Book>

8.1 获取所有作者的书籍 ​

要求:

打印所有书籍名称,并对重复书籍进行去重。

java
List<Author> authors = getAuthors();

authors.stream()
        .flatMap(author -> author.getBooks().stream())
        .distinct()
        .forEach(book -> System.out.println(book.getName()));

原来每个 Author 都拥有自己的:

java
List<Book>

通过:

java
author.getBooks().stream()

转换为:

java
Stream<Book>

然后由 flatMap() 将这些 Stream 合并成一个大的:

java
Stream<Book>

8.2 flatMap() 完整写法 ​

Lambda:

java
.flatMap(author -> author.getBooks().stream())

对应的匿名内部类写法:

java
List<Author> authors = getAuthors();

authors.stream()
        .flatMap(new Function<Author, Stream<Book>>() {
            @Override
            public Stream<Book> apply(Author author) {
                return author.getBooks().stream();
            }
        })
        .distinct()
        .forEach(new Consumer<Book>() {
            @Override
            public void accept(Book book) {
                System.out.println(book.getName());
            }
        });

8.3 获取所有书籍分类 ​

书籍中的分类数据类似:

text
哲学,爱情
个人成长,爱情
爱情,个人传记

现在要求打印所有分类,并去重,最终不能出现:

text
哲学,爱情

而是拆分成:

text
哲学
爱情
个人成长
个人传记

实现:

java
List<Author> authors = getAuthors();

authors.stream()
        .flatMap(author -> author.getBooks().stream())
        .distinct()
        .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
        .distinct()
        .forEach(category -> System.out.println(category));

这里进行了两次展开:

text
Author
  ↓
flatMap()
  ↓
Book
  ↓
获取 category
  ↓
split(",")
  ↓
flatMap()
  ↓
String 分类

flatMap() 的核心作用就是把多层结构中的数据展开成一个 Stream。


9. 常用中间操作小结 ​

方法作用
filter()按条件过滤元素
map()对元素进行转换或计算
distinct()去除重复元素
sorted()对元素进行排序
limit()保留前 N 个元素
skip()跳过前 N 个元素
flatMap()展开多层数据结构

可以简单理解为:

text
filter     → 筛选

map        → 转换

distinct   → 去重

sorted     → 排序

limit      → 截取前 N 个

skip       → 跳过前 N 个

flatMap    → 展开

这些操作都属于中间操作,可以继续返回 Stream 进行链式调用,最终需要通过终结操作触发执行。

Noctiflorous Garden · MIT License