Stream流是java8引入的特性,极大的方便了我们对于程序内数据的操作,提高了性能。通过函数式编程解决复杂问题。
他是流处理的基石概念,重点不在于这个接口定义了什么方法,而是它独特的参数类型。
首先约定好:T——入参类型 S——出参类型
S是继承于自己的同样的类型,从而形成一种递归,每一次返回的结果类型都是自己或它的子类。这样做是因为我们在流处理时,不会在原有的流上进行操作,而是形成新的流返回会去。这样设计免去了类型转换出错和增强了灵活性
BaseStream有4大子类,我们讲一个使用范围最广的——Stream
它定义了我们常用的一些方法如
Stream<T> filter (Predicate<? super T> predicate)
这里的Predicate就是一个函数式例如判断对象是否为空 s->s!=null
无状态 (Stateless) | 有状态 (Stateful) |
---|---|
|
|
|
|
|
|
|
|
| |
| |
| |
| |
| |
| |
|
非短路操作 | 短路操作 (short-circuiting) |
---|---|
|
|
|
|
|
|
|
|
|
|
| |
| |
|
咱们这里通过它的一个实现类ReferencePipeline来举个例子来体验一下
List<Integer> numbers = Arrays.asList(2, 1, 3, 8, 5, 6, 7, 4, 9, 10);
List<String> evenNumbers = numbers.stream()
.map(o->o.toString())//将元素转为字符串
.filter(n -> n.length() == 1)//剔除大于两位数的元素
.sorted()//排序
.collect(Collectors.toList());//整合出一个新的流返回
System.out.println(evenNumbers);
先定义了一个List,通过.stream()新建一个流管道,函数式编程的好处就是他可以把操作整合到一起,这里的 o->o.toString()和n->n.length()==1会被Java整合为
(o -> o.toString()) -> (n -> n.length() == 1)一个操作链
接下来,您是否好奇这个链条是如何组装的,反正我很好奇,let's dive into water
它继承了AbstractPipeline,而在其中保存了三个引用,类型都是自己,分别是sourceStage指向第一个Sink(后续展开),接下来就是previousStage和nextStage分别链接上下Sink。
下图给出了具体的流程
中间操作是一种烂加载处理,只有当触发了collect()方法才会真正的调用每个Steam流中的wrapSink方法去处理数据。之后调用sort()进行排序。我们来具体看一个方法是如何处理的
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
在ReferencePipeline中它定义三个静态内部类——StatelessOp,StatafulOp,Head。先是对函数式判空,然后返回一个无状态流。关键在于它内部定义的opWrapSinlk
通过返回一个Sink类并在其中定义了具体的操作accept()。然后调用函数式,并通过accept()触发下游Stream进行进一步处理。
抽象的来讲,上面所说的ReferencePipeline就像是流水线上不停流动的传输带,而真正在加工物品的就是我们的Sink类
这里的Consumer接口就是我们将不同的流处理函数式拼接起来的关键
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
andThen()方法,首先是自己调用自己的accept(),再调用下游的accept()
像我们前面写到的例子,map(o->o.toString)和filter(n->n.length)中的参数就是一个Consumer
我们将最后一步collect()定义为归约,将流中的所有元素归约为一个最终的结果。
它通过操作一个Collector进行操作,包含三个步骤
累加(accumulation):将流中的每个元素依次累加到一个容器中。
合并(combining):如果存在并行流,多个部分的结果需要合并。
完成(finishing):在所有元素处理完后,生成最终的结果。
public interface Collector<T, A, R> {
Supplier<A> supplier(); // 提供一个容器,容器类型是 A
BiConsumer<A, T> accumulator(); // 累加器,负责将元素添加到容器
BinaryOperator<A> combiner(); // 合并器,用于并行处理时合并多个容器
Function<A, R> finisher(); // 结果转换器,返回最终的结果
Set<Collector.Characteristics> characteristics(); // 一些特征,指示这个 Collector 是否具有某些优化特性
}
例如我们最常用的toList()将流中的所有元素收集到一个Lsit中。
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>(ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
ArrayList::new,返回一个List容器,通过List::add方法添加进入容器,后续处理并行流。
以下是关于 Java Stream API 中 collect
归约操作的表格,其中总结了常见的归约操作、作用说明及示例:
归约操作 | 作用说明 | 示例代码 |
---|---|---|
| 将流中的元素收集到一个 |
|
| 将流中的元素收集到一个 |
|
| 将流中的元素连接成一个字符串,支持指定分隔符、前缀和后缀。 |
|
| 根据某个条件将流中的元素分组,返回一个 |
|
| 将流中的元素分成两组,通常用于二元分类。 |
|
| 对流中的元素进行统计,返回 |
|
| 对流中的元素进行归约操作(例如累加、求最大值等),返回一个单一结果。 |
|
| 将流中的元素根据某个键值映射规则收集到一个 |
|
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。