ToB企服应用市场:ToB评测及商务社交产业平台

标题: JAVA基础之六-Stream(流)简介 [打印本页]

作者: 莫张周刘王    时间: 2024-9-15 15:23
标题: JAVA基础之六-Stream(流)简介
我在别的篇幅已经说过:流这个东西偶尔可以用用,但我个人最大的学习动力(目前)仅仅是为了有助于阅读spring越发繁复的源码
 
本文主要介绍Stream接口(包括主要的方法)和相干的几个主要接口:Predicate、Consumer、Supplier
还有Collector接口,Collectors工具类。
由于网上已经有太多的文章介绍,所以,本文侧重点在于简单介绍基本的概念,并不会罗列所有的接口成员的说明(这些可以看javaDoc或者
官方的javaDoc)。
 
虽然主要说的是流,但也顺便说了一些别的东西:函数式接口之类
 
注:本文所相干代码和图是在J17环境下得到的。
一、紧张接口、类及其定义

1.1、流直接相干接口/类

仅介绍部分:
Stream,Collector,Collectors是java.util.stream之下的,剩下的都是在java.util.function之下
 
1.Stream(普通接口)
流接口的类层次结构如下图:

可以看到Stream继承自BaseStream,后者则继承了AutoCloseable。顺便提一句,并不是什么都可以自动关闭,如果是文件流需要自己关闭,Stream的javaDoc有提到。
别的,有可以看到,还有几个继承自BaseStream的别的Stream,包括DoubleStream,IntStream,LongStream,..
有点不太明白的是,为什么java17中没有提供别的可以聚集的数据类的Stream,例如为什么没有BigDecimalStream,BigIntStream? 有空再补充。
 
BaseStream紧张定义了和计算不太相干的一些行为(并发、迭代等),如下图:

 
Stream自己则专注于计算/转换有关的,如下图:

如上图,都是诸如min,max,count,distinct之类的熟悉字眼,还有份额不小的各种mapXXX。
基本上,需要流做的,或者善于让流做的事情,都定义好了。
 
2.Predicate(函数式接口)
Predicate ,中文有许多翻译:谓词、断言、表明、猜测、断定等。
不过结合大量的方法定义,我觉得翻译为“断定”可能更好理解,一个根据输入确定是否真伪的对象,好似人类的推官、断事官、审判员。
为了便于行文,后面都会写成Predicate或者断定
这个断定接口定义了几个紧张的方法:

可以看到,只有一个test是需要实现的,别的几个都是默认的或者是静态的。
这个接口要实现类返回一个布尔值,断定传入的参数/内容是否合乎某个标准。
 
3.Consumer(函数式接口)
Consumer-消费者,这个翻译没有异议。
所以消费者接口比较简单,就两个方法:

其中的accept不返回任何东西,紧张的是andThen,可以让人不绝andThen下去,只要没有异常。
也就是说,消费者可以不绝消费,只要愿意。
 
4.Supplier(函数式接口)
和消费者对应的是供应者
和消费者不同的是,供应者只有一个get方法。
所以供应者提供了之后,就只能等着,然后消费者可以不绝消费。
 
5.各种Operator
Operator有的地方翻译为利用符,有的地方翻译为利用数。
结合已有的各种xxxOperator:BinaryOperator、DoubleUnaryOperator、UnaryOperator..
翻译为"利用者",以应合提供者、消费者。
各种利用者都是有返回的。  JCP大概把难于清晰界定其作用的各种函数式接口定义为利用者。
 
6..Bi,Binary是什么鬼?
Bi
在java.util.function下带有许多Bi开头的,例如BiFunction,BiConsumer,BiPredicate,xxxBixxx。
Bi是一个前缀,源自拉丁bis,表示两个、两次、两倍,总之就是2xxx。
所以带Bi开头的都是指有2个参数,而不带Bi的对应的都是一个参数为主(目前)。看看Predicate,BiPredicate就明白了:
  1. @FunctionalInterface
  2. public interface BiPredicate<T, U> {
  3.     boolean test(T t, U u);
  4. }
  5. @FunctionalInterface
  6. public interface Predicate<T> {
  7.     boolean test(T t);
  8. }
复制代码
注:为了节省篇幅,没有复制完整的源码和表明。
 
可以看到,Predicate带一个参数,BiPredicate有2个参数。
Jcp定义了不少Bixxx,大概是由于两个参数的场景也不少,否则为什么不定义三个,四个,...n个的Predicate等接口?
为了便于交流,我有的时间称谓Bixxx之类的函数为双参函数/方法,避免有些同事不知道这是什么玩意
 
Binary
一样平常想到的是二进制,不过在这里Binary兼有Bi一致的含义,合起来就是2个参数,且参数范例一致(包括返回也是一样)。
拿非常典型的java.util.function.BinaryOperator看下:
public interface BinaryOperator extends BiFunction
BiFunciton的定义(局部):
  1. @FunctionalInterface
  2. public interface BiFunction<T, U, R> {
  3.     R apply(T t, U u);
  4. }
复制代码
BiFunction必然是两个参数,但它不要求三个参数一致,而BinaryOperator则要求一致。
所以,这里Binary可以理解为三参一致,即两个入参和一个出参(返回值)一致,或者是两个入仓和返回值一致。
按照这个思维,其实JCP等可以考虑把这个定名为:triple
 
7.Collector(常规接口)
收集者 public interface Collector
这是一个比较奇怪的接口,这是由于除了两个工具类的函数(of),别的的公共抽象方法的定名有背常规。
一样平常而言,方法的定名规范为:动词、动词+名词、形容词
例如 do(),doSomething(),isFinished()
但这里都是名词:supplier、accumulator、combiner、finisher、characteristics。
分别表示提供者、累积者、合并者、完成者、特征(注意,这里是一个聚集)
Collector也提供了两个公共静态函数of,用于返回一个新的Collector实现。
在of函数中,其实即使利用Collectors.CollectorImpl 来实现。
所以,如果不想开发新的Collector实现,基本上用Collectors或者Collect.of即可,但本质上都是用Collectors.CollectorImpl
这也应该是JCP所期望的吧!
 
8.Collectors(实现Collector接口为主的工具类)
这个类非常有效。
当我们利用流处理的时间,常常会有转换的需要。当转换完成后需要输出另外一个格式的时间,就需要利用到收集器。
Collectors就是一个收集器聚集,包含了各种各种的收集函数,实现诸如分组、转换(目标是list,map等)等利用
收集器聚集中所有的公共方法方法返回的都是收集者(Collector)范例。
当我们检察源码,可以发现一个非常紧张的地方:凡是返回Collector的public static 方法,最后都会调用:
return new CollectorImpl(supplier, accumulator, merger, finisher, characteristics);
CollectorImpl是Collector接口的实现,属于私有静态类(由于Collectors是final导致的)
java这些写的意图应该是如许的:你们不要费心(包括继承,扩展等等),就用Collectors的详细方法即可.
以常用toList()为例:
  1. public static <T>
  2.     Collector<T, ?, List<T>> toList() {
  3.         return new CollectorImpl<>(ArrayList::new, List::add,
  4.                                    (left, right) -> { left.addAll(right); return left; },
  5.                                    CH_ID);
  6.     }
复制代码
Collector范例的返回,可以被实现了Stream.collect利用,从而完成输出。
Stream的实现有很多,如前,最常见的是java.util.stream.ReferencePipeline
 
对于一样平常的工程师而言,想要Collect,可以这么做:
1.利用Collector.of构建一个,或者利用Colelctors的现货
2.把构建好的Collector当做入参,塞给Stream.collect方法.
3.完成
 
两个名词
mutable-可变的
immutable-不可变的
阅读stream代码的时间,需要格外注意哪些参数是可变的,还是不可变的。
特殊注意:对于管道利用、流利用,要求部分参数不可变是一个很常见的要求。
 
1.2、java对别的接口/类的改造

主要是对聚集范例的改造(从1.8开发)。
1.2.1、Colletion改造

聚集范例的基类Collection新增了几个接口(J8开始):
  1. default Stream<E> parallelStream() {
  2.         return StreamSupport.stream(spliterator(), true);
  3. }
  4. @Override
  5. default Spliterator<E> spliterator() {
  6.         return Spliterators.spliterator(this, 0);
  7. }
  8. default Stream<E> stream() {
  9.         return StreamSupport.stream(spliterator(), false);
  10. }
复制代码
上面这个Splitrator(可分割迭代器) 主要也是为了服务于并行流。如果不是采用并行流,那么也会用到,只不过不执行分隔而已(存疑)。
JCP都提供了默认实现,并为这些默认实现提供了对应的静态工具类:StreamSupport,Spliterators.
云云,所有Collection的实现类都可以利用steam,parallelStream方法。
1.2.2、Map?

Map中没有和流有关的改造,主要是对函数式编程的改造,或者详细一点就是增加了郎打的实现。
[code]default void forEach(BiConsumer




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4