如何学习 Java Stream API [+5 Resources]

在Java编程中,流(Stream)是指可以进行顺序或并行处理的元素序列。

一个流可以包含多个中间操作,最终以一个终端操作结束,并返回一个结果。

什么是流?

流的概念是通过Java 8引入的Stream API进行管理的。

可以将流想象成一个生产线,其中物品需要被制造、分类,然后包装以便运输。 在Java中,这些物品可以是对象或者对象的集合,操作则是制造、分类和包装,而生产线本身就是流。

流的组成部分主要包括:

  • 初始输入
  • 中间操作
  • 终端操作
  • 最终结果

让我们来探讨一下Java流的一些特性:

  • 流并非内存中的数据结构,而是一系列数组、对象或对象集合,通过特定方法进行操作。
  • 流本质上是声明式的,即您指定要做什么,而不是如何去做。
  • 流只能被消费一次,因为它们不存储任何数据。
  • 流不会修改原始数据结构,它只是从中派生出新的结构。
  • 它返回通过管道中最终方法得出的最终结果。

流API与集合处理

集合是一种内存中的数据结构,用于存储和操作数据。集合提供了诸如Set、Map、List等数据结构来存储数据。而流,则是一种在数据通过管道处理后有效传输数据的方式。

以下是一个ArrayList集合的示例:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(0, 3);
        System.out.println(list);
    }
}

Output:
[3]

如上述示例所示,您可以创建一个ArrayList集合,在其中存储数据,并使用各种方法对这些数据进行操作。

通过使用流,您可以对现有的数据结构进行操作,并返回新的、经过修改的值。以下示例展示了如何创建一个ArrayList集合并使用流进行过滤。

import java.util.ArrayList;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList();

        for (int i = 0; i < 20; i++) {
            list.add(i+1);
        }

        System.out.println(list);

        Stream<Integer> filtered = list.stream().filter(num -> num > 10);
        filtered.forEach(num -> System.out.println(num + " "));
    }
}

#Output

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

在以上示例中,我们使用已存在的列表创建了一个流,并遍历该列表以过滤出所有大于10的值。需要注意的是,流并不存储任何内容,它只是遍历列表并输出结果。如果您尝试打印流本身,您只会获得一个流的引用,而不是具体的值。

使用 Java Stream API

Java Stream API接收一个元素源集合或元素序列,然后对它们执行各种操作以获得最终结果。流可以被视为一个管道,一系列元素通过该管道并被以某种方式转换。

流可以从多种源创建,包括:

  • 集合,例如List或Set。
  • 数组。
  • 使用缓冲区的文件及其路径。

流中的操作分为两种类型:

  • 中间操作
  • 终端操作

中间与终端操作

每个中间操作都会返回一个新的流,这个新流使用指定的方法转换输入。实际上,它不会立刻遍历任何元素,而是将转换传递给下一个流。只有在执行终端操作时,流才会被遍历以获得最终结果。

例如,如果有一个包含10个数字的列表,您希望过滤这些数字,然后将其映射到其他值。并不会立即遍历列表中的每个元素来获取过滤结果并进行映射。相反,只会检查单个元素,如果元素满足条件,它才会被映射,形成一个包含新元素的流。

映射操作只会在满足过滤条件的单个元素上执行,而不是在整个列表上。在终端操作阶段,这些结果会被遍历并组合成最终的输出。

一旦执行了终端操作,流就会被消耗,不能再次使用。如果需要再次执行相同操作,必须创建一个新的流。

资料来源:无聊的开发

我们现在对流的工作原理有了初步了解,接下来深入探讨Java中流的实现细节。

#1. 空流

可以使用Stream API的`empty()`方法创建一个空流。

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream emptyStream = Stream.empty();
        System.out.println(emptyStream.count());
    }
}

Output:
0

如果您打印这个流中的元素数量,您会得到0作为输出,因为它是一个不包含任何元素的空流。空流在避免空指针异常时非常有用。

#2. 从集合流

诸如List和Set之类的集合类提供了一个`stream()`方法,允许您从集合中创建流。然后,您可以遍历创建的流以获取最终结果。

ArrayList<Integer> list = new ArrayList();

for (int i = 0; i < 20; i++) {
    list.add(i+1);
}

System.out.println(list);

Stream<Integer> filtered = list.stream().filter(num -> num > 10);
filtered.forEach(num -> System.out.println(num + " "));

#Output

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 

#3. 从数组流

可以使用`Arrays.stream()`方法从数组中创建流。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] stringArray = new String[]{"this", "is", "techblik.com"};
        Arrays.stream(stringArray).forEach(item -> System.out.print(item + " "));
    }
}

#Output

this is techblik.com 

您还可以指定创建流的元素的起始和结束索引。起始索引是包含的,而结束索引是排除的。

String[] stringArray = new String[]{"this", "is", "techblik.com"};
Arrays.stream(stringArray, 1, 3).forEach(item -> System.out.print(item + " "));

Output:
is techblik.com

#4. 使用流查找最小和最大数字

可以使用Java中的比较器来查找集合或数组中的最大和最小数字。`min()`和`max()`方法接受一个比较器,并返回一个Optional对象。

Optional对象是一个容器对象,可能包含或不包含非空值。如果它包含非空值,则调用其`get()`方法会返回该值。

import java.util.Arrays;
import java.util.Optional;

public class MinMax {
    public static void main(String[] args) {
        Integer[] numbers = new Integer[]{21, 82, 41, 9, 62, 3, 11};

        Optional<Integer> maxValue = Arrays.stream(numbers).max(Integer::compare);
        System.out.println(maxValue.get());

        Optional<Integer> minValue = Arrays.stream(numbers).min(Integer::compare);
        System.out.println(minValue.get());
    }
}

#Output
82
3

学习资源

现在您已经对Java中的流有了基本的了解,这里有5个资源可以帮助您掌握Java 8:

#1. Java 8实战

本书是一本介绍Java 8新特性的指南,包括流、lambda表达式和函数式编程。书中还包含测验和知识检查题,以帮助您巩固所学知识。

您可以在亚马逊上购买本书的平装本和有声读物版本。

#2. Java 8 Lambdas:面向大众的函数式编程

本书主要讲解Lambda表达式如何影响Java语言,尤其针对核心Java SE开发人员。书中包含了清晰的解释、代码练习和示例,帮助您掌握Java 8的lambda表达式。

亚马逊上有平装版和Kindle版本。

#3. Java SE 8为真正没有耐心的人准备

如果您是一位经验丰富的Java SE开发人员,本书将指导您了解Java SE 8中的改进,包括流API、lambda表达式的引入、Java并发编程的改进,以及一些人们不熟悉的Java 7功能。

本书仅在亚马逊上提供平装版。

#4. 使用Lambdas和Streams学习Java函数式编程

Udemy上的这门课程探讨了Java 8和9中函数式编程的基础知识。Lambda表达式、方法引用、流和函数式接口是本课程的重点。

本课程还包含许多与函数式编程相关的Java难题和练习。

#5. Java类库

Java类库是Coursera提供的核心Java专业化课程的一部分。它将教您如何使用Java泛型编写类型安全的代码、了解由4000多个类组成的类库、如何使用文件以及处理运行时错误。但是,参加本课程需要一些先决条件:

  • Java入门
  • Java面向对象编程简介
  • Java中的面向对象层次结构

最后的话

Java Stream API和Java 8中引入的Lambda函数简化并改进了Java中的许多方面,例如并行迭代、函数式接口、更少的代码等。

但是,流也有一些限制。它们最大的限制是只能使用一次。如果您是一名Java开发人员,上面提到的资源可以帮助您更深入地理解这些主题,因此请务必查看它们。

您可能还想了解Java中的异常处理。