Java Stream

Java Stream

Overview

Java 8 引入用于操作集合(Collection)的 Stream API,它将元素集合抽象为(stream),元素如同水流一样在管道(pipeline)中传输,我们可以方便地对流进行各种操作(Operation),最终得到我们想要的结果,我们将这种操作的方式称为流式处理(stream processing)

操作分类:

  • Intermediate operations: 返回一个新的 Stream,可进行链式操作
  • Terminal operations: 返回一个 result 或 side-effect,只能放在最后

如下是一个使用 Stream 对 Collection 进行操作的代码示例,其中filter中间操作,forEach终止操作

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
    List<String> nameList = Arrays.asList("Valerie", "Jack", "John", "Jerry", "Jesus");
    nameList.stream()
            .filter(name -> name.startsWith("J"))
            .filter(name -> name.length() == 4)
            .forEach(System.out::println);
}
[OUTPUT]
Jack
John

Stream 特点:

  • Lazy Evaluation: 中间操作不会立即执行,只有在遇到终止操作时流才开始真正的遍历,即一次遍历中执行多个操作(映射,过滤等),这种特性称为惰性求值(lazy evaluation),这种特性使得 Stream API 可以进行更高效的操作
  • Internal Iteration: 传统的集合操作是外部迭代(external iteration),即用户需要手动迭代集合中的每一个元素,而 Stream API 是内部迭代(internal iteration),用户只需要告诉 Stream API 需要对集合进行什么操作,而不需要关心具体的迭代过程
  • Immutability: Stream 不会改变原有的数据结构,它只是对原有的数据进行操作,并返回一个新的 Stream
  • Parallel Processing : JDK 源码注解中提到 Stream pipelines may execute either sequentially or in parallel, 即 Stream 既可被顺序处理,也可并行处理,从而充分利用多核处理器的优势
Stream

Methods

Method方法作用Operation 类别
count计数terminal
forEach迭代处理terminal
reduce归约terminal
collect收集terminal
findAny查找terminal
anyMatch匹配terminal
filter过滤intermediate
sorted排序intermediate
distinct去重intermediate
limit取用前几个intermediate
skip跳过前几个intermediate
map映射intermediate
concat拼接intermediate

To perform a computation, stream operations are composed into a stream pipeline. A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as filter(Predicate)), and a terminal operation (which produces a result or side-effect, such as count() or forEach(Consumer)). Streams are lazy; computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed.

创建 Stream

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 Collection 的 stream()和 parallelStream()方法
List<Integer> list = Arrays.asList(1, 2, 3);
Stream<Integer> stream1 = list.stream();
Stream<Integer> stream2 = list.parallelStream();
// 使用 Arrays 的 stream() 方法
Stream<Integer> stream3 = Arrays.stream(new Integer[] {1, 2, 3});
// 使用 Stream 类的静态方法 of()、iterate()、generate()
Stream<Integer> stream4 = Stream.of(1, 2, 3); // of() 内部调用的是 Arrays.stream() method
Stream<Double> stream5 = Stream.iterate(10.0,num -> num > 0.5, num -> num / 2);
stream5.forEach(System.out::println);// 10.0 5.0 2.5 1.25 0.625 0.3125
Stream<Double> stream6 = Stream.generate(Math::random).limit(5);
stream6.forEach(System.out::println);// 5 个随机小数

Methods 分析

filter

Stream<T> filter(Predicate<? super T> predicate)
Returns a stream consisting of the elements of this stream that match the given predicate.

filter 方法接受一个 Predicate 函数式接口实例[[functional_interface]],用于过滤流中的元素,返回一个新的 stream,其中包含符合条件的元素

Predicate 是一个 Java 8 新增的函数式接口,它接受一个参数并返回一个布尔值,我们通常使用 Lambda 表达式来创建 Predicate 实例

1
2
3
4
5
6
7
// Commonly used methods in Predicate
stream.filter(element -> element.length() > 5)
stream.filter(element -> element.startsWith("J"))
stream.filter(element -> element.endsWith("y"))
stream.filter(element -> element.equals("Jerry"))
stream.filter(element -> element.contains("e"))
stream.filter(element -> element.matches(".*[aeiou].*"))

forEach,find,match

Stream 支持类似于 Collection 的遍历以及查询的操作,需要注意的是,Stream 中的元素是以Optional类型存在的
Traverse

  • void forEach(Consumer<? super T> action)
    forEach 接受一个 Consumer 函数式接口,用于遍历流中的每一个元素,并对其进行操作
    Consumer 是一个 Java 8 新增的函数式接口,它接受一个参数,无返回值,例如System.out::println;

Find

  • Optional<T> findFirst()
    Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty. If the stream has no encounter order, then any element may be returned.
  • Optional<T> findAny()
    Returns an Optional describing the first element of this stream, or an empty Optional if the stream is empty. If the stream has no encounter order, then any element may be returned.

Match

  • boolean anyMatch(Predicate<? super T> predicate)
    只要有一个元素匹配传入的条件,就返回 true
  • boolean allMatch(Predicate<? super T> predicate)
    所有元素都匹配传入的条件,才返回 true
  • boolean noneMatch(Predicate<? super T> predicate)
    所有元素都不匹配传入的条件,才返回 true
1
2
3
4
5
6
7
8
//forEach
list.stream().forEach(System.out::println);
// Find
Optional<String> first = list.stream().findFirst();
Optional<String> any = list.stream().findAny();
// Match
boolean isExist = list.stream().anyMatch(element -> element.length() > 5);
boolean isAllMatch = list.stream().allMatch(element -> element.startsWith("J"));

map,flatMap

map 意为映射,可将一个 Stream 中的元素映射成另一个 Stream,如下代码将一个 String 类型的 Stream 映射成一个 Integer 类型的 Stream

  • <R> Stream<R> map(Function<? super T,? extends R> mapper)
    Returns a stream consisting of the results of applying the given function to the elements of this stream.

  • <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
    Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents have been placed into this stream. (If a mapped stream is null an empty stream is used, instead.)

  • map方法接受一个函数式接口实例mapper,根据 mapper 将流中的元素映射成新的元素,返回一个新的stream,其中包含映射后的元素

  • flatMap方法接受一个函数式接口实例mapper ,用于将流中的元素映射成流,并将映射后的流扁平化,即将多个流连接成一个流,返回拼接后的 stream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// map
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> nameList = Arrays.asList("Valerie", "Tom");
        nameList.stream()
            .map(String::toUpperCase)
            .map(name -> "Hello, " + name)
            .forEach(System.out::println);

        nameList.stream()
            .map(String::toUpperCase)
            .flatMap((String name) -> Arrays.stream(name.split("")))
            .forEach(System.out::println);
    }
}
[OUTPUT]
Hello, VALERIE
Hello, JACK
Hello, JOHN
Hello, TOM
Hello, JERRY
Hello, JESUS
// flatMap
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
	public static void main(String[] args) {
		    // 给定两个数字列表 获取所有的数对
        List<Integer> numbers1 = Arrays.asList(1, 2, 3);
        List<Integer> numbers2 = Arrays.asList(3, 4);
        // numbers2.stream().map(y -> new int[] { x, y })返回Stream<int[]>,flatMap将每个x对应的Stream<int[]>连接
        List<int[]> pairs = numbers1.stream().flatMap(x -> numbers2.stream().map(y -> new int[] { x, y }))
                .collect(Collectors.toList());
        for (int[] pair : pairs) {
            System.out.print(Arrays.toString(pair));
        }
	}
}
[OUTPUT]
[1, 3][1, 4][2, 3][2, 4][3, 3][3, 4]

reduce

reduce 意为归约,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

  • Optional<T> reduce(BinaryOperator<T> accumulator)
    无初始值,返回一个 Optional,参数为一个 BinaryOperator 函数式接口实例,用于将流中的元素两两结合
    Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any.
    Parameters:

    • accumulator - an associative, non-interfering, stateless function for combining two values

    Returns:
    an Optional describing the result of the reduction

  • T reduce(T identity, BinaryOperator<T> accumulator)
    有初始值,返回类型与初始值的类型一致,参数为初始值和一个 BinaryOperator 函数式接口实例

  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

    Parameters:

    • identity the identity value for the combiner function
    • accumulator an associative, non-interfering, stateless function for incorporating an additional element into a result
    • combiner an associative, non-interfering, stateless function for combining two values, which must be compatible with the accumulator function

    Returns:
    the result of the reduction


Associative(可结合的):这意味着累积器函数在处理多个值时,其操作的顺序不影响最终结果。即无论值是如何组合的,累积器函数总是产生相同的结果。例如,加法和乘法都是可结合的操作。
Non-interfering(不干扰的):累积器函数在处理值时不会改变这些值的状态。它只是简单地将值组合起来,而不影响原始值。
Stateless(无状态的):累积器函数不依赖于或修改任何外部状态。它只根据传入的参数来计算结果,不涉及任何外部的、可变的或全局的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 求所有员工的工资之和、最高工资
// 求工资之和方法1:Stream<Person> -> Stream<Integer> -> Optional<Integer>
Optional<Integer> sumSalary =
                personList.stream().map(Person::getSalary).reduce(Integer::sum);
// 求工资之和方法2:
Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
                Integer::sum);
// 求最高工资方法1:Stream<Person> -> Stream<Integer> -> Integer
Integer maxSalary1 =
                personList.stream().map(Person::getSalary).reduce(Integer::max).get();
// 求最高工资方法2:
Integer maxSalary2 = personList.stream().reduce(0,
                (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                (max1, max2) -> max1 > max2 ? max1 : max2);
// 求最高工资方法3:
Integer maxSalary3 = personList.stream().reduce(0,
                                (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                                Integer::max);

collect

stream()方法可以将集合,数组转换为 Stream,而collect()方法则可以将 Stream 转换为集合

1
2
3
String[] strArr = list.stream().toArray(String[]::new);
List<Integer> strLengthList = list.stream().map(String::length).collect(Collectors.toList());
List<String> strList = list.stream().collect(Collectors.toCollection(ArrayList::new));

count,max,min

如下方法均为 terminal operation

  • long count()
    Returns the count of elements in this stream.
  • Optional<T> max(Comparator<? super T> comparator)
    Returns the maximum element of this stream according to the provided Comparator.
  • Optional<T> min(Comparator<? super T> comparator)
    Returns the minimum element of this stream according to the provided Comparator.

Ref

Stream iterate
oracle docs
Java8 新特性之 Stream 流(含具体案例)
由浅入深体验 Stream 流(附带教程)
Java 8 Stream 和 Optional: 实践指南
Reducing a Stream

作者

Jiaxing Gao

发布于

2024-02-19

更新于

2024-11-16

许可协议

评论

}