获取 Stream | JavaSE
获取 Stream
一、学习目标
完成本章后,你应该能够:
- 能够解释“获取 Stream”到底意味着什么。
- 能够从
Collection体系集合获得 Stream。 - 能够正确处理
Map,分别获取键、值和键值对的 Stream。 - 能够使用
Arrays.stream()从对象数组和基本类型数组获得 Stream。 - 能够使用
Stream.of()根据若干对象创建 Stream。 - 能够区分
Stream<T>与IntStream、LongStream、DoubleStream。 - 能够避免
Stream.of(int[])等基本类型数组场景中的类型误判。 - 能够根据数据源类型选择最自然的 Stream 获取方式。
二、核心知识
2.1 为什么必须先“获取 Stream”
上一章我们建立了 Stream Pipeline:
数据源 Source
↓
中间操作
↓
中间操作
↓
终结操作
那么第一步一定是:
让原始数据进入 Stream 流水线。
例如我们原本有:
List<String> names =
List.of(
"张无忌",
"赵敏",
"周芷若"
);
它本身是:
List<String>
如果想使用:
filter()
map()
sorted()
limit()
等 Stream API,需要先:
names.stream()
得到:
Stream<String>
于是:
List<String>
↓
stream()
↓
Stream<String>
↓
开始函数式数据处理
所以“获取 Stream”并不是:
把所有数据复制进一个新的 Stream 集合。
更准确的理解是:
基于现有数据源建立一条 Stream 数据处理流水线。
2.2 常见数据源
原课程主要要求掌握:
Collection
Map
数组
若干独立数据
对应的主流入口:
Collection
↓
collection.stream()
Map
↓
keySet() / values() / entrySet()
↓
stream()
对象数组
↓
Arrays.stream(array)
若干对象
↓
Stream.of(...)
这是本章主线。
三、从 Collection 获取 Stream
3.1 Collection 的 stream()
Collection<E> 提供:
Stream<E> stream()
因此所有常见的 Collection 子体系:
ListSetQueue
都可以直接:
集合对象.stream()
3.2 List 获取 Stream
例如:
import java.util.List;
import java.util.stream.Stream;
public class StreamDemo01 {
public static void main(String[] args) {
List<String> names =
List.of(
"张无忌",
"赵敏",
"周芷若"
);
Stream<String> stream =
names.stream();
}
}
类型关系:
List<String>
↓
stream()
↓
Stream<String>
Stream 中元素类型仍然是:
String
3.3 Set 获取 Stream
同理:
Set<String> names =
Set.of(
"Java",
"MySQL",
"Spring"
);
Stream<String> stream =
names.stream();
因为:
Set<E>
也是:
Collection<E>
3.4 为什么 List 和 Set 都能直接 stream()
因为:
Collection<E>
/ \
/ \
List<E> Set<E>
stream() 定义在:
Collection
体系中。
所以:
ArrayList
LinkedList
HashSet
LinkedHashSet
TreeSet
都可以通过继承到的 stream() 获得 Stream。
四、从 Map 获取 Stream
4.1 为什么 Map 不能直接 stream()
这是非常重要的区别。
我们前面学习集合体系时已经知道:
Collection
和
Map
是两套不同的顶层体系。
Map:
Map<K, V>
保存的是:
key → value
键值映射。
而:
stream()
是 Collection 接口提供的能力。
因此:
Map<String, Integer> map =
new HashMap<>();
不能直接写:
// 错误
// map.stream();
4.2 Map 的解决办法
Map 前面已经学习过三种集合视图:
map.keySet()
map.values()
map.entrySet()
它们分别得到:
Set<K>
Collection<V>
Set<Map.Entry<K,V>>
这些都是 Collection 体系。
所以可以继续:
Map
↓
转换成 Collection 视图
↓
stream()
4.3 获取键的 Stream
例如:
Map<String, Integer> map =
new HashMap<>();
map.put("Java", 100);
map.put("MySQL", 90);
map.put("Spring", 95);
获取键:
Stream<String> keyStream =
map.keySet().stream();
过程:
Map<String, Integer>
↓
keySet()
↓
Set<String>
↓
stream()
↓
Stream<String>
4.4 获取值的 Stream
Stream<Integer> valueStream =
map.values().stream();
过程:
Map<String, Integer>
↓
values()
↓
Collection<Integer>
↓
stream()
↓
Stream<Integer>
4.5 获取键值对 Stream
这是实际开发中非常常用的形式:
Stream<Map.Entry<String, Integer>>
entryStream =
map.entrySet().stream();
其中每一个元素都是:
Map.Entry<String, Integer>
也就是:
一个 key-value 键值对对象
因此以后可以处理:
entry.getKey()
entry.getValue()
4.6 Map 三种 Stream 入口总结
Map<String, Integer> map =
new HashMap<>();
可以:
Stream<String> keys =
map.keySet().stream();
Stream<Integer> values =
map.values().stream();
Stream<Map.Entry<String, Integer>> entries =
map.entrySet().stream();
选择哪一种取决于:
你的业务到底需要处理 key、value,还是完整键值对。
五、从数组获取 Stream
5.1 对象数组:Arrays.stream()
假设:
String[] names = {
"张三丰",
"张无忌",
"张翠山"
};
可以:
Stream<String> stream =
Arrays.stream(names);
完整代码:
import java.util.Arrays;
import java.util.stream.Stream;
public class StreamDemo02 {
public static void main(String[] args) {
String[] names = {
"张三丰",
"张无忌",
"张翠山"
};
Stream<String> stream =
Arrays.stream(names);
}
}
5.2 Arrays.stream() 的区间形式
Java 还支持:
Arrays.stream(
array,
startInclusive,
endExclusive
)
例如:
String[] names = {
"A",
"B",
"C",
"D"
};
Stream<String> stream =
Arrays.stream(
names,
1,
3
);
范围遵循 Java 常见的:
[startInclusive, endExclusive)
即:
[1, 3)
包含:
下标 1
下标 2
不包含下标 3。
六、基本类型数组与专用 Stream
6.1 int[] 会得到 IntStream
假设:
int[] numbers = {
10,
20,
30
};
使用:
Arrays.stream(numbers)
返回的并不是:
Stream<Integer>
而是:
IntStream
因此:
IntStream stream =
Arrays.stream(numbers);
需要导入:
import java.util.stream.IntStream;
6.2 为什么 Java 有 IntStream
Java 泛型不能直接使用基本类型:
Stream<int>
这是非法的。
如果全部包装为:
Stream<Integer>
大量数值处理可能产生:
int
↕
Integer
装箱和拆箱。
因此 Stream API 为常见基本数值类型提供:
IntStream
LongStream
DoubleStream
例如:
int[] → IntStream
long[] → LongStream
double[] → DoubleStream
6.3 对象数组与基本类型数组对比
对象数组:
String[] names = {
"Java",
"MySQL"
};
Stream<String> stream =
Arrays.stream(names);
基本类型数组:
int[] numbers = {
1,
2,
3
};
IntStream stream =
Arrays.stream(numbers);
要形成清晰认知:
T[]
↓
Stream<T>
int[]
↓
IntStream
long[]
↓
LongStream
double[]
↓
DoubleStream
七、Stream.of()
7.1 根据若干对象创建 Stream
除了从集合、数组获得 Stream,还可以直接:
Stream.of(...)
例如:
Stream<String> stream =
Stream.of(
"Java",
"MySQL",
"Spring"
);
得到:
Java
MySQL
Spring
三个 Stream 元素。
7.2 单个元素
也可以:
Stream<String> stream =
Stream.of("Java");
得到只包含一个元素的 Stream。
7.3 对象数组
原课程还给出了:
String[] names = {
"张三丰",
"张无忌",
"张翠山"
};
Stream<String> stream =
Stream.of(names);
这里 Stream.of(T...) 使用可变参数。
对于:
String[]
这样的引用类型数组,可以得到对应:
Stream<String>
因此对象数组场景下:
Arrays.stream(names)
与:
Stream.of(names)
都可以完成常见需求。
初学阶段,对已有数组更推荐使用语义直接的:
Arrays.stream(names)
八、一个非常重要的坑:Stream.of(int[])
8.1 错误直觉
假设:
int[] numbers = {
1,
2,
3
};
很多初学者会认为:
Stream.of(numbers)
能够得到:
1
2
3
但这不是正确理解。
8.2 实际类型
因为:
int[]
本身是一个对象类型。
因此:
Stream.of(numbers)
会把整个:
int[]
数组当成一个元素。
类型类似:
Stream<int[]>
而不是:
IntStream
更不是:
Stream<Integer>
8.3 正确做法
如果要逐个处理:
1
2
3
应该:
IntStream stream =
Arrays.stream(numbers);
所以必须记住:
String[]
↓
Stream.of(array)
通常可展开为对象元素
int[]
↓
Stream.of(array)
整个 int[] 可能成为一个元素
int[] 正确数值流入口
↓
Arrays.stream(array)
↓
IntStream
这是 Stream 获取阶段非常典型的类型陷阱。
九、stream() 与 parallelStream()
Collection 还提供:
parallelStream()
例如:
Stream<String> stream =
names.parallelStream();
它用于获得可能并行执行的 Stream。
但是当前 JavaSE 主线首先学习:
stream()
顺序流。
原因是:
并行 Stream 并不等于“自动更快”。
并行执行还涉及:
- 数据规模
- 操作成本
- 线程调度
- 顺序约束
- 共享状态
- Fork/Join
等问题。
所以本阶段不要养成:
“看到 Stream 就用 parallelStream()”
的习惯。
先把顺序 Stream 的语义掌握清楚。
十、不同数据源的统一模型
到这里,可以把 Stream 获取整理成:
数据源
┌────────────┼─────────────┐
│ │ │
Collection Map 数组
│ │ │
stream() 三种视图 Arrays.stream()
│
│
keySet / values
/ entrySet
│
stream()
另外:
Stream.of(...)
↓
直接根据若干对象
建立 Stream
真正需要掌握的是:
看到数据源以后,立即知道应该从哪里进入 Stream Pipeline。
十一、实践应用
11.1 List
List<Student> students =
new ArrayList<>();
Stream<Student> stream =
students.stream();
以后可以:
Student
↓
过滤
排序
映射
统计
11.2 Set
Set<String> tags =
new HashSet<>();
Stream<String> stream =
tags.stream();
11.3 Map
假设星雨笔录需要统计:
文章分类 → 文章数量
数据:
Map<String, Integer>
articleCount;
如果需要同时查看分类和数量:
articleCount
.entrySet()
.stream();
通常比单独:
keySet()
更合适。
11.4 数组
算法程序中可能存在:
int[] scores;
可以:
Arrays.stream(scores)
得到:
IntStream
然后进一步进行数值处理。
十二、常见问题
12.1 Stream 是从数据源复制出一份数据吗?
不要这样理解。
获取 Stream 的核心是:
基于数据源建立数据处理流水线。
12.2 List 和 Set 为什么都能 stream()?
因为它们属于:
Collection
体系。
12.3 Map 为什么不能直接 stream()?
因为:
Map
不是 Collection 子接口。
要先通过:
keySet()
values()
entrySet()
获得 Collection 视图。
12.4 Map 最推荐哪一种?
没有绝对答案。
只处理键:
map.keySet().stream()
只处理值:
map.values().stream()
同时需要键和值:
map.entrySet().stream()
12.5 数组一定得到 Stream 吗?
不是。
例如:
int[]
通过:
Arrays.stream()
得到:
IntStream
12.6 Stream.of(int[]) 有什么问题?
它可能产生:
Stream<int[]>
即整个数组成为一个 Stream 元素。
逐个处理整数应该使用:
Arrays.stream(intArray)
12.7 stream() 得到以后可以一直重复使用吗?
不能这样设计。
一个 Stream Pipeline 通常只能消费一次。
如果需要重新处理:
list.stream()
重新建立一条 Stream。
十三、练习与验收
13.1 知识问答
- 为什么使用 Stream API 前通常需要先“获取 Stream”?
Collection.stream()返回什么?- 为什么 List 和 Set 都能直接调用
stream()? - 为什么 Map 不能直接调用
stream()? - Map 有哪三种常见 Stream 获取方案?
entrySet().stream()中每个元素是什么类型?- 对象数组怎样获得 Stream?
int[]使用Arrays.stream()得到什么?Stream<String>和IntStream有什么区别?Stream.of()解决什么问题?Stream.of(int[])为什么容易产生误解?stream()与parallelStream()有什么基本区别?
13.2 代码阅读
判断类型:
List<String> list =
List.of("A", "B");
var stream =
list.stream();
stream 的实际泛型类型是什么?
分析:
Map<String, Integer> map =
new HashMap<>();
var stream =
map.entrySet().stream();
Stream 中每个元素是什么?
分析:
int[] numbers = {
1,
2,
3
};
var a = Arrays.stream(numbers);
var b = Stream.of(numbers);
回答:
a属于什么类型?b中有几个顶层 Stream 元素?- 两种写法的语义为什么不同?
13.3 手写代码
不查看资料分别完成:
- 从
ArrayList<String>获取 Stream。 - 从
HashSet<Student>获取 Stream。 - 从
Map<String,Integer>获取 key Stream。 - 获取 value Stream。
- 获取
EntryStream。 - 从
String[]获取 Stream。 - 从
int[]获取数值 Stream。 - 使用
Stream.of()创建包含"Java"、"MySQL"、"Spring"的 Stream。
13.4 Debug
下面代码有什么问题?
Map<String, Integer> map =
new HashMap<>();
// map.stream();
说明原因并给出三种正确思路。
分析:
int[] numbers = {
1,
2,
3
};
Stream<int[]> stream =
Stream.of(numbers);
代码本身可以表达什么语义?
如果需求是:
分别处理 1、2、3
应该如何调整?
13.5 综合训练
给出:
List<Student> students;
Map<String, Integer> scoreMap;
String[] names;
int[] scores;
分别写出最合适的 Stream 获取方式,并说明:
原数据类型
→
Stream 类型
→
Stream 中单个元素的类型
13.6 本章验收
闭卷画出:
Collection
↓
stream()
Map
↓
keySet / values / entrySet
↓
stream()
T[]
↓
Arrays.stream()
↓
Stream<T>
int[]
↓
Arrays.stream()
↓
IntStream
若干对象
↓
Stream.of(...)
如果能够根据数据源类型立即选择正确 Stream 入口,并能够解释:
Arrays.stream(intArray)
与:
Stream.of(intArray)
的根本区别,则本章达到掌握标准。