【Java集合框架】3 - 16 Stream 流
§3-16 Stream 流
3-16.1 Stream 流的获取方法
Stream 是一个位于 java.util.stream 包下的接口。
该接口的思想类似于流水线,流水线对原料逐步进行各种操作,原料经历完整的流水线后,形成最终的产品,离开流水线。
Steam 流借鉴了这种思想,提供了许多方法作为 “处理原料的操作”,可称为中间方法(例如过滤、转换等),最终 “原料形成产品离开流水线”,可称为终结方法(例如输出、统计等)。
中间方法允许持续地进行链式调用其他方法,而终结方法是流的最后一个执行方法,执行完后不可再调用流的其他方法。
Stream 流的使用步骤:
- 先得到一条
Stream流(流水线),并将数据放上去; - 利用
Stream中的 API 进行各种操作;
获取方法:
| 获取来源 | 方法 | 描述 |
|---|---|---|
Collection 单列集合接口 |
default Stream<E> stream() |
返回一条以指定单列集合为源的顺序流 |
Map 双列集合接口 |
\ | 双列集合无法使用 Stream 流 |
Arrays 数组工具类 |
Stream<T> stream(T[] array) |
返回一条以指定数组为源的顺序流 |
Stream 接口 |
Stream<T> of(T... values) |
Stream 接口中的静态方法,以指定数据为源获取流 |
注意:
-
一般而言,我们不会用独立变量接收流对象,而是直接采用链式编程,用连点式不断地调用方法;
-
双列集合无法直接使用
Stream流,应当先获取键或键值对的单列集合,再通过单列集合获取流; -
Stream.of方法的形参是一个可变参数,本质上是一个数组;若传递的是基本数据类型数组,则不会自动装箱,反而会将整个数组而不是其中的数据放入流水线中;int[] arr1 = {1, 2, 3, 4, 5}; String[] arr2 = {"aaa", "bbb", "ccc", "ddd", "eee"}; //基本数据类型 Stream.of(arr1).forEach(i -> System.out.println(i)); //[I@1d81eb93:数组地址 //引用数据类型 Stream.of(arr2).forEach(e -> System.out.println(e));
3-16.2 中间方法
中间方法就好比原料在流水线上的加工。
常用方法:
| 方法 | 描述 |
|---|---|
Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) |
返回一个弱连接的流,其中后者连接在前者后面 |
Stream<T> distinct() |
返回一条不同元素的流 |
Stream<T> filter(Predicate<? super T> predicate) |
返回一条匹配指定谓词元素的流(过滤) |
Stream<T> limit(long maxSize) |
将该流截短至指定长度后返回(限制) |
Stream<R> map(Function<? super T, ? extends R> mapper) |
对流中的元素应用指定函数后,返回含有该结果的流 |
Stream<T> skip(long n) |
返回一条丢弃前 n 个元素的流 |
Stream<T> sorted() |
返回一条经过自然排序的流 |
Stream<T> sorted(Comparator<? super T> comparator) |
返回一条经比较器排序的流 |
注意:
- 中间方法返回的是一个新的流对象,原来的流只能使用一次,记录为单独的变量没有意义,建议使用链式编程;
- 在流上对数据元素所进行的任何修改均不会反映到原始数据源;
- 应当尽可能地保证
concat方法中的两条流中的数据类型相同,不相同则会转换为二者的父类,这样会使得子类的特有方法无法被调用; distinct方法可用于元素去重,其底层使用了HashSet,使得元素去重依赖于hashCode和equals方法;Predicate是一个位于java.util.function的函数式接口,表示一个单参数的谓词(布尔值函数),该方法为test(Object),方法返回true表示该元素留下,反之舍弃;Function是一个位于java.util.function的函数式接口,表示一个接受单参数且产生结果的一个函数,方法为apply(Object);
3-16.3 终结方法
终结方法好比原料最后的出厂。
常用方法:
| 方法 | 描述 |
|---|---|
long count() |
返回流上元素的个数 |
R collect(Collector<? super T,A,R> collector) |
使用收集器对流上的每一个元素执行动态规约运算(收集到集合中) |
void forEach(Consumer<? super T> action) |
对流上的每个元素执行操作 |
Object[] toArray() |
返回一个含有流上所有元素的数组(收集到数组中) |
A[] toArray(IntFunction<A[]> generator) |
返回一个含有流上所有元素的数组(收集到数组中) |
List<T> toList() |
返回一个含有流上所有元素的列表 |
注意:
-
上述方法的返回值不再是流,因此上述方法用于链式编程的最后一个方法调用;
-
Collector是一个位于java.util.stream的接口,动态规约(mutable reduction)运算将输入元素累积到一个可变的结果容器中,当所有输入元素都已处理后,将所积累的结果转换为最终表示(可选); -
Collector<T,A,R>中的泛型依次表示执行规约运算的输入元素类型、规约运算的所积累元素的类型(通常隐藏为实现细节)、规约运算的结果类型; -
一般而言,会调用工具类
Collectors中的对应方法,这些方法返回收集器,可用作collect的参数,该类位于java.util.stream下; -
IntFunction<R>是java.util.function的一个函数式接口,表示一个接受单整型值参数并产生结果的一个函数,其方法为apply; -
IntFunction的匿名类泛型为<? extends Object[]>(可根据实际情况手动更改,但必须为数组),该泛型表示某个类型的数组,toArray以该接口的实现类对象为参数,用于创建一个指定类型数组,而toArray底层会依次得到流上的每一个数据,并放到数组中;String[] arr = names.stream().toArray(new IntFunction<String[]>() { @Override public String[] apply(int value) { //value表示流上元素个数 return new String[value]; } });
3-16.4 collect 使用方法
Stream 中的 collect 方法提供了从流转换到集合的方法。
现有如下集合,以下的转换都基于此集合进行:
ArrayList<String> names = new ArrayList<>();
Collections.addAll(names, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20"
, "张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
//要求先过滤出男性成员信息
转换到列表:
使用 Collectors.toList() 即可。
List<String> list = names.stream()
//尽量使用固定数据来调用方法,这样可以尽力地避免空指针异常
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toList());
System.out.println(list.getClass()); //ArrayList
System.out.println(list);
System.out.println("==================");
转换到集合:
使用 Collectors.toSet() 即可,方法会自动去重。
Set<String> set = names.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toSet());
System.out.println(set.getClass()); //HashSet
System.out.println(set);
System.out.println("==================");
转换到映射表:
使用 Collectors.toMap() 即可,方法传入两个参数,都为 Function 接口实现类对象,分别用于指定键和值的生成规则。
接口泛型参数:记住所表示的一一对应关系即可
- 两个接口都具有两个泛型,其中第一个泛型表示流的元素类型;
- 对于第一个接口,其第二个泛型指的是键的类型。内部方法形参来自流的元素,返回键;
- 对于第二个接口,其第二个泛型指的是值的类型。内部方法形参来自流的元素,返回值;
注意:键不可重复,若发现重复的键,则会抛出异常 duplicateKeyException。
Map<String, Integer> map = names.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
//键
return s.split("-")[0];
}
}, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
//值
return Integer.parseInt(s.split("-")[2]);
}
}));
改写成 Lambda 表达式:
Map<String, Integer> map = names.stream()
.filter(s -> "男".equals(s.split("-")[1]))
.collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
System.out.println(map.getClass()); //HashMap
System.out.println(map);
System.out.println("==================");
3-16.5 Stream.Builder 流构建器
由名字可知,流构建器是 Stream 的内部接口,其功能用于构建一个流,以供后续在流上的聚合操作。
获取方法:通过 Stream 接口的静态方法获得。
| 静态方法 | 描述 |
|---|---|
Stream.Builder<T> builder() |
返回流的构建器 |
获得流的构建器后,就可以使用流的构建器构建流。流的构建器通过一个个生成元素并将其添加到构建器中创建流,这节省了使用 ArrayList 作为临时缓存的复制开销。
流构建器具有生命周期。生命周期起始于构建期,该阶段允许向构建器添加元素。然后过渡到构建完成期,在这之后不再允许向构建器添加元素。构建器再调用 build() 方法后过渡到构建完成期,这时构建器会返回一个流,流中元素的顺序同元素的添加顺序。
构建器的方法:
| 方法 | 描述 |
|---|---|
void accept(T t) |
在构建期中,向构建器添加元素 |
Stream.Builder<T> add(T t) |
在构建期中,向构建器添加元素,返回该构建器本身 |
Stream<T> build() |
构建流,并将构建器过渡到构建完成的状态 |
注意:
-
add(T)方法:方法的默认实现行为是:accept(t); return this; -
build()方法:调用该方法后,任何进一步操作该构建器的尝试都会导致IllegalStateException;
在 java.util.stream 包中还有一些专门化的流,这些流也有自己的构建器,这些构建器的行为都是一致的,具有生命周期,都可以用于构建指定的流。9
3.19.6 stream 包中的基本类型序列流
java.util.stream 包中提供了三种基本类型的序列流,分别是 IntStream, DoubleStream 和 LongStream 接口。这些接口支持顺序和并行聚合操作,是对应基本类型的专门化 Stream。
这些流在专门处理大量对应基本类型的数据时十分有用。
获取方法:有三种获取对应基本类型序列流的方法。
- 直接调用对应接口的静态方法,构建对应流;
- 通过
Stream中的映射方法,将流转化为序列流; - 通过已有的序列流中的成员方法,将流转换为新的序列流;
这里重点介绍第一种获取方法,但只列出部分方法。
| 所属接口 | 静态方法 | 描述 |
|---|---|---|
IntStream |
IntStream.Builder builder() |
返回 IntStream 的构建器 |
DoubleStream |
DoubleStream.Builder builder() |
返回 DoubleStream 的构建器 |
LongStream |
LongStream.Builder builder() |
返回 LongStream 的构建器 |
| 各自接口中 | IntStream concat(IntStream a, IntStream b)DoubleStream concat(DoubleStream a, DoubleStream b)LongStream concat(LongStream a, LongStream b) |
创建一个弱连接的流,流中元素顺序为 a 先 b 后 |
| 各自接口中 | empty() |
创建一个空的流 |
| 各自接口中 | IntStream generate(IntSupplier s)DoubleStream generate(DoubleSupplier s)LongStream generate(LongSupplier s) |
创建一个无限无序的流,元素由对应类型提供者提供,可用于创建持续流或随机元素的流 |
| 各自接口中 | of(int/double/long t)of(int/double/long... value) |
返回含有指定元素的有序流 |
仅 IntStream 和 LongStream |
IntStream range(int startInclusive, int endExclusive) |
返回一个有序流,含有指定左闭右开区间范围内的元素,步长为 1 |
仅 IntStream 和 LongStream |
IntStream rangeClosed(int startInclusive, int endInclusive) |
返回一个有序流,含有指定闭合区间范围内的元素,步长为 1 |
成员方法:
序列流支持非常多的操作,因而有非常多的支持方法,这里只列出一部分,详见官方文档。
所有的序列流都支持 Stream 的方法,如 allMatch, anyMatch, collect, filter, findAny, min, max, sorted 等,这里不再列出。
这里仅以 IntStream 为例。
| 方法 | 描述 |
|---|---|
DoubleStream asDoubleStream() |
将流转换为 DoubleStream |
LongStream asLongStream() |
将流转换为 LongStream |
OptionalDouble average() |
返回流中所有元素的算术平均值,若流为空,则返回空可选值 |
Stream<Integer> boxed() |
返回包含流中所有元素的普通流,所有元素都被装箱为包装类对象 |
int sum() |
返回流中所有元素的和 |
IntSummaryStatistics summaryStatistics() |
返回描述流中元素多个概要数据的 IntSummaryStatistics |
示例:
package com.jcf.stream;
import java.util.IntSummaryStatistics;
import java.util.IntStream;
public class IntStreamDemo {
public static void main(String[] args) {
// 调用静态方法直接创建流
System.out.println(IntStream.rangeClosed(0, 100).sum());
IntSummaryStatistics stats = IntStream.of(10, 15, 13, 5, 20, 0).sorted().summaryStatistics();
System.out.println("最大值:" + stats.getMax());
System.out.println("最小值:" + stats.getMin());
System.out.println("个数:" + stats.getCount());
System.out.println("平均值:" + stats.getAverage());
System.out.println("总和:" + stats.getSum());
System.out.println(stats);
}
}
运行结果:
5050
最大值:20
最小值:0
个数:6
平均值:10.5
总和:63
IntSummaryStatistics{count=6, sum=63, min=0, average=10.500000, max=20}
浙公网安备 33010602011771号