关于Java&JavaScript中(伪)Stream式API对比的一些笔记
追求轻微痛感,掌控快感释放,先做困难的事情,降低奖励期待,控制欲望,延迟消费多巴胺
写在前面
- 前些时日开发遇到,想着把这些对比总结下
- 博文内容包括:
- Stream 相关概念简述
- Java和JavaScript的Stream式API对比Demo
- 食用方式
- 博文适合会一点前端的Java后端&会一点Java后端的前端
- 需要了解Java&JavaScript基础知识
- 理解不足小伙伴帮忙指正
追求轻微痛感,掌控快感释放,先做困难的事情,降低奖励期待,控制欲望,延迟消费多巴胺
什么是流(Stream)
关于 Stream
, 在Java中我们叫 流
,但是在JavaScript中,好像没有这种叫,也没有Stream
API,我么姑且称为伪流
,JS一般把参与流处理的函数称为高价函数
,比如特殊的柯里化
之类,Java 中则是通过函数式接口
实现,
其实一个编译型语言,一个解释型语言没有什么可比性,这里只是感觉行为有写类似放到一起比较记忆。而且通过链式调用,可读性很高
,JS里我们主要讨论Array的伪流处理。Set和Map的API相对较少,这里不讨论,为了方便,不管是Java还是JavaScript,数据处理我们都称为流或者Stream处理
这里的高阶函数
,即满足下面两个条件:
- 函数作为
参数被传递
:比如回调函数
- 函数作为
返回值输出
:让函数返回可执行函数
,因为运算过程是可以延续的
这里讲Stream
,即想表达从一个数据源生成一个想要的元素序列的过程
。这个过程中,会经历一些数据处理的操作
,我们称之为流(Stream)处理
Stream
与传统的数据处理最大的不同在于其 内部迭代
,与使用迭代器显式迭代不同,Stream的迭代操作是在背后进行的。数据处理的行为大都遵循函数式编程的范式
,通过匿名函数
的方式实现行为参数化
,利用Lambad表达式
实现。
但是Java
的流和JavaScript
是伪流
不同的,Java的Stream是在概念上固定的数据结构(你不能添加或删除元素),JavaScript中的Stream是可以对原始数据源处理
的。但是Java的Stream可以利用多核
支持像流水线一样并行处理
.
Java
中通过parallelStream
可以获得一个并行处理的Stream
1 | // 顺序进行 |
JS可以在流处理的回调函数
上可以传递一个当前处理的数据源
1 | let colors = ["red", "blue", "grey"]; |
一般我们把可以连接起来的Stream
操作称为中间操作
,关闭Stream
的操作称为我们称为终端操作
。
中间操作
:一般都可以合并起来,在终端操作时一次性全部处理终端操作
:会从流的流水线生成结果。其结果是任何不是流的值
总而言之,流的使用一般包括三件事:
- 一个数据源(如数组集合)来执行一个查询
- 一个中间操作链,形成一条流的流水线
- 一个终端操作,执行流水线,并能生成结果
关于流操作,有无状态和有状态之分 :
- 诸如
map或filter
等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。 这些操作一般都是无状态的:它们没有内部状态,称为无状态操作
- 诸如
sort或distinct,reduce
等操作一开始都和filter和map差不多——都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史
。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题。我们把这些操作叫作有状态操作
中间操作
JavaScript | Java | 说明 |
---|---|---|
filter | filter | 筛选 |
map | map | 映射 |
flatMap | flatMap | 扁平化 |
slice | limit | 截断 |
sort | sorted | 排序 |
不支持 | distinct | 去重 |
slice | skip | 跳过 |
group/groupToMap | groupingBy | 分组 |
终端操作
JavaScript | Java | 说明 |
---|---|---|
forEach | forEach | 消费 |
length | count | 统计 |
reduce/reduceRight | reduce | 归约 |
every/some | anyMatch/allMatch/noneMatch | 谓词/短路求值 |
findLast(findLastIndex)/find(findIndex) | findAny/findFirst | 查找 |
Java和JavaScript的Stream Demo
Java 和Node版本
1 | java version "1.8.0_251" |
1 | Welcome to Node.js v16.15.0. |
通过Demo来看下Java和JavaScript的Stream
filter 筛选
filter用布尔值筛选,。该操作会接受一个谓词(一个返回 boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
Java
Stream<T> filter(Predicate<? super T> predicate);
boolean test(T t);
1 | List<Integer> list = Arrays.asList(12, 3, 4, 5, 4); |
JS
arr.filter(callback(element[, index[, array]])[, thisArg])
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
map 映射
对流中每一个元素应用函数:流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映 射成一个新的元素(使用映射
一词,是因为它和转换
类似,但其中的细微差别在于它是“创建
一个新版本”而不是去“修改
”)。
java
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
R apply(T t);
1 | List<Integer> list = Arrays.asList(12, 3, 4, 5, 4); |
JS
arr.map(function callback(currentValue[, index[, array]]) {}[, thisArg])
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
flatMap 扁平化
流的扁平化
,对于一张单词表,如何返回一张列表,列出里面各不相同的字符呢?例如,给定单词列表 ["Hello","World"]
,你想要返回列表["H","e","l", "o","W","r","d"]
java
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
R apply(T t);
1 | List<String> strings = Arrays.asList("Hello","World"); |
JS
arr.flatMap(function callback(currentValue[, index[, array]]) {}[, thisArg])
1 | let string = ["Hello","World"] |
当然这里JS
提供了flat
方法可以默认展开数组,flat() 方法会按照一个可指定的深度
递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
1 | [1, 2, [3, [4, 5]]].flat() |
slice|limit 截断
截断流
:该方法会返回一个不超过给定长度的流。所需的长度作为参数传递 给limit
。如果流是有序的,则多会返回前n个元素
。
通过截断流
我们可以看到Java的JavaScript在Stream上本质的不同
,Java通过Stream 对象本身OP_MASK
属性来截断,而JS没有实际意义上的Stream对象, 但是可以通过filter结合index
来完成,或者使用slice
,
java
Stream<T> limit(long maxSize);
1 | List<Integer> list = Arrays.asList(12, 3, 4, 5, 4); |
JS
JS 的截断处理可以使用slice
,或者通过filter结合index
来完成
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
sort|sorted 排序
排序,这个不多讲,
java
Stream<T> sorted(Comparator<? super T> comparator);
int compare(T o1, T o2);
1 | List<Integer> list = Arrays.asList(12, 3, 4, 5, 4); |
JS
arr.sort([compareFunction])
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
distinct 去重
筛选不同的元素:java流支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的 hashCode和equals
方法实现)的流
java
Stream<T> distinct();
1 | List<Integer> list = Arrays.asList(12, 3, 4, 5, 4); |
JS
distinct是Stream本身的方法,JS没有类似的代替,不过可以转化为Set处理
1 | let numbers = [2,3,4,3,5,2] |
Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”
,它类似于精确相等运算符(===)
,主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。set 中两个对象总是不相等的。
skip 跳过
跳过元素
:返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,limit(n)和skip(n)是互补的
java
Stream<T> skip(long n);
1 | List<Integer> list = Arrays.asList(12, 3, 4, 5, 4); |
JS
Js 中可以通过slice
方法来实现
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
group/groupToMap|groupingBy 分组
分组操作的结果是一个Map
,把分组函数返回的值作为映射的键
,把流中所有具有这个分类值的项目的列表作为对应的映射值
。
java
Java 的分组通过Stream API 的collect
方法传递Collector
静态方法groupingBy
,该方法传递了一个Function
(以方法引用的形式)我们把这个Function叫作分类函数,因为它用来把流中的元素分成不同的组。
<R, A> R collect(Collector<? super T, A, R> collector);
1 | public static <T, K, A, D> |
1 |
|
这块涉及的API蛮多的,不但可以分组,也可以分区,这里简单介绍几个,感兴趣小伙伴可以去看看API文档
getter分组
1 | //getter分组 |
自定义逻辑分组
1 | //2.自定义逻辑分组 |
多级分组展示
1 | // 多级分组 |
分组统计
1 | List<String> list_ = Arrays.asList("123", "1234", "4564", "1234"); |
把收集器的结果转换为另一种类型
1 | // 把收集器的结果转换为另一种类型,按照长度排序得到最大值,然后给Optional修饰 |
JS
JavaScript 新增了数组实例方法group()和groupToMap()
,可以根据分组函数的运行结果,将数组成员分组。目前还是一个提案,需要考虑浏览器兼容,按照字符串分组就使用group()
,按照对象分组就使用groupToMap()
。所以groupToMap()
和Java的分组很类似。
1 | Experimental: This is an experimental technology |
group(function(element, index, array) {}, thisArg)
1 | const array = [1, 2, 3, 4, 5]; |
groupToMap(function(element, index, array) { }, thisArg)
groupToMap()的作用和用法与group()完全一致,唯一的区别是返回值是一个 Map 结构
,而不是对象
1 | const array = [1, 2, 3, 4, 5]; |
如果分组函数是一个箭头函数,thisArg对象无效,因为箭头函数内部的this是固化的
,类似于Ajax回调内部的this。
forEach 消费
forEach 这个不多讲,用于消费
java
1 | List<String> list_ = Arrays.asList("123", "1234", "4564", "1234"); |
JS
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
count 统计
count 也不多讲
java
1 | List<String> lists_ = Arrays.asList("123", "1234", "4564", "1234"); |
JS
在JS中没有对应的方法,不过Set和Map有对应的API,Array的可以使用Array.prototype.length
reduce 归约
把数据源中的元素反复结合起来,得到一个值,即将流归约为一个值,用函数式编程语言叫折叠
java
Java 中的归约分为两种,一种为有初值的归约,一种为没有初值的归约。有初值的返回初值类型,没初值的返回一个Options
T reduce(T identity, BinaryOperator<T> accumulator);
1 | List<Integer> numbers1 = Arrays.asList(1, 2, 34, 5, 6); |
Optional<T> reduce(BinaryOperator<T> accumulator)
1 | List<Integer> numbers1 = Arrays.asList(1, 2, 34, 5, 6); |
JS
reduce((previousValue, currentValue, currentIndex, array) => {},initialValue)
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
every/some|anyMatch/allMatch/noneMatch 谓词
所谓 谓词
,即是否有满足条件的存在,返回一个布尔值。和filter特别像,只不过一个是中间操作,一个终端操作。
java
Java中检查谓词是否至少匹配一个元素
,使用anyMatch
方法,即流中是否有一个元素能匹配给定谓词。boolean anyMatch(Predicate<? super T> predicate);
使用allMatch
方法,即流中都能匹配所有元素返回ture
, boolean allMatch(Predicate<? super T> predicate);
使用noneMatch
方法,即流中都不能匹配所有元素返回true
, boolean noneMatch(Predicate<? super T> predicate);
1 | List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); |
JS
every()
方法测试数组中的所有元素是否通过提供的函数实现的测试
, every((element, index, array) => { /* ... */ } )
some()
方法测试数组中的至少一个元素是否通过了提供的函数实现的测试
, some((element, index, array) => { /* ... */ } )
1 | let boo = Array.of(1, 2, 3, 4, 5, 6).every(o => o >5) |
findLast(findLastIndex)/find(findIndex)|findAny/findFirst 查找
查找元素
:返回当前流的任意元素。
java
findAny()
方法返回当前流的任意元素findFirst()
方法返回当前流的第一个元素。
1 | List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); |
JS
find()
方法返回提供的数组中满足提供的测试功能的第一个元素
findIndex()
方法返回满足提供的测试功能的数组中第一个元素的索引
1 | let users = [{ name: "毋意", value: "202201" }, { name: "毋必", value: "202202" }, |
这两个为ES2022 新增,当前Node版本不支持
findLast()
方法返回满足提供的测试功能的数组中最后一个元素的值
findLastIndex()
方法返回满足提供的测试功能的数组中最后一个元素的索引
1
2
3
4user = users.findLast(o => o.name === "毋固")
console.log(user)
useri = users.findLastIndex(o => o.name === "毋固")
console.log(useri)
嗯,时间关系,关于对比就分享到这啦,其实还有好多,比如Stream API
的收集器
等,还有好多奇技淫巧
,感兴趣小伙伴可以看看下的书籍和网站
博文参考
关于Java&JavaScript中(伪)Stream式API对比的一些笔记
https://liruilongs.github.io/2022/07/16/Java/关于 Java&JavaScript中Stream式API对比的一些笔记/