一、Stream 简介
1.1 Stream 的流式结构
Stream 是在 Java 8 随着函数式编程一起引入的新特性,简称为“流”,可以非常方便地处理 Java 中的集合,极大地加快代码的编写速度。
那为什么它被称为“流”呢?首先,Stream 本身翻译过来就有“流”的意思,此外,利用 Stream API 处理集合的方式非常像在一个工厂的生产流水线上处理产品的方式。集合内的元素就是产品,而 Stream 提供的一系列 API 就是处理这些产品的处理器:
elements of collection ----------------------->|-----> new collection (产生新的集合)
↑ ↑ ↑
filter map collect
过滤 映射处理 收集
上述的 filter、sorted、map 和 collect 都是 Stream API,当然,上面的图只是举个形象的例子,并非说一定要这样用。当然,不仅仅是在逻辑处理上像“流”,其代码也非常像“流”:
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Java"); // 产品 1
arrayList.add("ArrayList"); // 产品 2
arrayList.add("Stream"); // 产品 3
Predicate<String> filter = (String string) -> string.length() > 4; // 第 1 个“处理器”的处理规则
Function<String, String> map = (String string) -> string.toLowerCase(); // 第 2 个“处理器”的处理规则
List<String> newCollection = new ArrayList<>(); // 装新产品的容器
newCollection = arrayList.stream().filter(filter).map(map).collect(Collectors.toList()); // 流式处理
// ↑ ↑ ↑ ↑
// 产生流 过滤 映射处理 收集
System.out.println(newCollection); // Output: [arraylist, stream]
}
}
当然,我们要完成上述工作也不一定要用 Stream 来做,也可以用循环或者迭代器来做,下文详细讲述。
1.2 生成流
在 Java 8 中,集合有两个产生流的接口,stream 和 parallelStream,分别是一般流和并行流。这里不着重讲解并行流,只对一般的流进行讲解。
ArrayList<String> arrayList = new ArrayList<>(); // 创建一个数组列表(属于集合类的一种)
arrayList.stream(); // stream 方法返回此数组列表的流
stream 方法没有参数,返回值就是集合的流。
二、Stream 的方法
Stream 提供了一系列有用的方法,列举如下部分:
方法 | 描述 | 返回值 |
forEach | 迭代每一个元素 | void |
map | 对每一个元素做映射 | Stream |
filter | 过滤掉一部分不符合要求的元素 | Stream |
limit | 限制一部分元素 | Stream |
sorted | 取前几个元素进行迭代 | void |
count | 统计个数 | void |
skip | 跳过前几个元素后迭代 | Stream |
collect | 产生新的集合类 | Stream |
下面对每一个进行详细的讲述。
2.1 forEach 方法
forEach 可以视为 for 循环迭代集合类的语法糖,可以简化代码。比如我们要打印一个 ArrayList 中的每一个元素,可以分别用循环和 Stream API 的方式去写:
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
for (Integer integer : arrayList) { // 采用 for 循环(迭代器)的方式
System.out.println(integer);
}
arrayList.stream().forEach(System.out::println); // 采用 Stream 方式
}
}
不过,集合类也有 forEach 这个方法,它和 Stream 的 forEach 功能近乎完全一样,但仍有以下爱区别:
- Stream 的 forEach 是操作的流式数据,而集合类操作的是原始的集合元素;
- Stream 的 forEach 方法是抽象方法,但集合类的不是抽象方法;
- Stream 的 forEach 可以并行(效率可以更高),但集合类的只能串行;
下面就是集合类的 forEach 方法,在没有特别的要求下,其实集合类的 forEach 会更加常用一些。
arrayList.forEach(System.out::println);
这两个 forEach 都没有返回值(void)。
2.2 filter 方法
filter 翻译过来就是过滤的意思,因此 filter 方法可以对流中的数据进行过滤,只留下符合条件的数据,返回值是处理后的流:
arrayList.stream().filter(x -> x < 3).forEach(System.out::println); // 将 3 滤去了
学过 Python 朋友们对这个可能非常熟悉,对,它和 Python 的内置函数 filter 不能说是很像,只能说是一模一样啊!我们来看一下 Python 里面的 filter:
print(*filter(lambda x: x < 3, [1, 2, 3])) # Output: 1 2
# ↑ ↑ ↑
# 过滤 Lambda表达式 列表
每一种编程语言之间或多或少都是相同点的,善用类比思维,学习起来会事半功倍。
2.3 map 方法
讲完上面的 filter,看到这个 map 方法,学过 Python 的朋友可能又要笑了,这不就是 Python 内置的 map 函数嘛!对,他俩也如出一辙地相似。map 翻译过来有地图、表格的意思,体现的是一种映射关系,可以类比数学上函数的映射关系,也可以视为一种转换:
arrayList.stream().map(x -> x * x).forEach(System.out::println); // 映射为初始值的平方
和上面一样,我们来看看 Python 是怎么写的:
print(*map(lambda x: x**2, [1, 2, 3])) # Output: 1 4 9
2.4 limit 方法
limit 翻译过来意为限制,有限的,因此它的作用也很好理解,就是只取流中前面的 n 个数据。那么很显然,它有一个参数,表示这个 n 的大小,返回限制后的流。n 可以大于集合类中的元素个数。
arrayList.stream().limit(2).forEach(System.out::println); // 只输出前 2 个
其实,Python 里面也有类似功能,就是切片。
2.5 sorted 方法
sort 翻译过来是排序的意思,加上 ed 后缀表示英语语法中的过去式,意为排序后的,因此不会对原来是数据产生影响,有返回值。sorted 方法有两个重载,一个是无参的,还有一个接收函数式接口,返回值都是排序后的 Stream。与此对应的是集合类本身就有的 sort 方法,是对其本身进行排序,没有返回值。(学 Python 的人又狂喜了)
没有参数的方法的功能很好猜,可以认为是默认的排序方式,而有参数的则是通过参数(比较器)的值自定义排序方式。
arrayList.sort(Comparator.naturalOrder()); // 按自然顺序对原始数据进行排序
arrayList.stream().sorted().forEach(System.out::println); // 从小到大排序(默认方式),不改变原始数据
arrayList.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println); // 倒序输出,不改变原始数据
这里也给出 Python 中类似的代码:
lst.sort() # 对原始数据排序
lst.sort(key=lambda x: x ** 2) # 按照平方和从小到大排序
print(sorted(lst)) # 不改变原始数据,返回新的列表
print(sorted(lst, key=lambda x: x ** 2)) # 按照平方和从小到大排序,返回新列表
2.6 count 方法
count 翻译过来为个数的意思,也就是说,它是用来统计流中有多少个数据的,没有参数,返回一个整数。
System.out.println(arrayList.stream().count()); // 输出数据个数
2.7 skip 方法
skip 意为跳过,作用和 limit 类似,只不过它是跳过前 n 个元素,与 limit 完全相反。n 可以大于集合类中的元素个数。
arrayList.stream().skip(2).forEach(System.out::println); // 不会输出前 2 个,其余正常
2.8 collect 方法
collect 意为收集,它可以将流数据重新收集起来并转换为某种集合类型。
import java.util.ArrayList;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
ArrayList<Integer> newArrayList = (ArrayList<Integer>) arrayList.stream().filter(x -> x < 4).map(x -> x * x).collect(Collectors.toList());
System.out.println(newArrayList); // Output: [1, 4, 9]
}
}