0


关于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处理

这里的

高阶函数

,即满足下面两个条件:

  1. 函数作为参数被传递:比如回调函数
  2. 函数作为返回值输出:让函数返回可执行函数,因为运算过程是可以延续的

这里讲

Stream

,即想表达

从一个数据源生成一个想要的元素序列的过程

。这个过程中,会经历一些

数据处理的操作

,我们称之为

流(Stream)处理
Stream

与传统的数据处理最大的不同在于其

内部迭代

,与使用迭代器显式迭代不同,Stream的迭代操作是在背后进行的。数据处理的行为大都遵循

函数式编程的范式

,通过

匿名函数

的方式实现

行为参数化

,利用

Lambad表达式

实现。

但是

Java

的流和

JavaScript

伪流

不同的,Java的Stream是在概念上固定的数据结构(你不能添加或删除元素),JavaScript中的Stream是可以对

原始数据源处理

的。但是Java的Stream可以利用

多核

支持像流水线一样

并行处理

.

Java

中通过

parallelStream

可以获得一个并行处理的

Stream
// 顺序进行List<Apple> listStream = list.stream().filter((Apple a)-> a.getWeight()>20||"green".equals(a.getColor())).collect(Collectors.toList());//并行进行List<Apple> listStreamc = list.parallelStream().filter((Apple a)-> a.getWeight()>20||"green".equals(a.getColor())).collect(Collectors.toList());

JS可以在流处理的

回调函数

上可以传递一个当前处理的

数据源
let colors =["red","blue","grey"];

colors.forEach((item, index, arr)==>{if(item ==="red"){
        arr.splice(index,1);}});

一般我们把可以连接起来的

Stream

操作称为

中间操作

关闭Stream

的操作称为我们称为

终端操作

  • 中间操作:一般都可以合并起来,在终端操作时一次性全部处理
  • 终端操作:会从流的流水线生成结果。其结果是任何不是流的值

总而言之,流的使用一般包括三件事:

  • 一个数据源(如数组集合)来执行一个查询
  • 一个中间操作链,形成一条流的流水线
  • 一个终端操作,执行流水线,并能生成结果

关于流操作,有无状态和有状态之分 :

  • 诸如 map或filter 等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。 这些操作一般都是无状态的:它们没有内部状态,称为无状态操作
  • 诸如sort或distinct,reduce等操作一开始都和filter和map差不多——都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题。我们把这些操作叫作有状态操作

中间操作
JavaScriptJava说明filterfilter筛选mapmap映射flatMapflatMap扁平化slicelimit截断sortsorted排序不支持distinct去重sliceskip跳过group/groupToMapgroupingBy分组
终端操作
JavaScriptJava说明forEachforEach消费lengthcount统计reduce/reduceRightreduce归约every/someanyMatch/allMatch/noneMatch谓词/短路求值findLast(findLastIndex)/find(findIndex)findAny/findFirst查找

Java和JavaScript的Stream Demo

Java 和Node版本

java version "1.8.0_251"Java(TM) SE RuntimeEnvironment(build 1.8.0_251-b08)JavaHotSpot(TM)64-BitServer VM (build 25.251-b08, mixed mode)
Welcome to Node.js v16.15.0.
Type ".help"for more information.>

通过Demo来看下Java和JavaScript的Stream

filter 筛选

filter用布尔值筛选,。该操作会接受一个谓词(一个返回 boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。

Java

Stream<T> filter(Predicate<? super T> predicate);
boolean test(T t);
List<Integer> list  =Arrays.asList(12,3,4,5,4);
list.stream().filter( i -> i %2==0).forEach(System.out::print);// 1244

JS

arr.filter(callback(element[, index[, array]])[, thisArg])
let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}]

users.filter(o=>+o.value ===202201).forEach(o=>console.log('out :%s',o))//out :{ name: '毋意', value: '202201' }

map 映射

对流中每一个元素应用函数:流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映 射成一个新的元素(使用

映射

一词,是因为它和

转换

类似,但其中的细微差别在于它是“

创建

一个新版本”而不是去“

修改

”)。

java

<R> Stream<R> map(Function<? super T, ? extends R> mapper); 
R apply(T t);
List<Integer> list  =Arrays.asList(12,3,4,5,4);
list.stream().map(o -> o+1).forEach(System.out::println);======134565

JS

arr.map(function callback(currentValue[, index[, array]]) {}[, thisArg])
let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}]             
users.map(o=> o.name ).forEach(o=>console.log('out :%s',o))===========out:毋意
out:毋必
out:毋固
out:毋我

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);
List<String> strings =Arrays.asList("Hello","World");
strings.stream().map(o -> o.split("")).flatMap(Arrays::stream).forEach(System.out::println);====H
e
l
l
o
W
o
r
l
d        

JS

arr.flatMap(function callback(currentValue[, index[, array]]) {}[, thisArg])
let string =["Hello","World"]
string.flatMap(o=> o.split("")).forEach(o=>console.log('out :%s',o))=====out:Hout:e
out:l
out:l
out:o
out:Wout:o
out:r
out:l
out:d

当然这里

JS 

提供了

flat

方法可以默认展开数组,flat() 方法会按照一个

可指定的深度

递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

[1,2,[3,[4,5]]].flat()// [1, 2, 3, [4, 5]][1,2,[3,[4,5]]].flat(2)// [1, 2, 3, 4, 5]

slice|limit 截断

截断流

:该方法会返回一个不超过给定长度的流。所需的长度作为参数传递 给

limit

。如果流是有序的,则多会返回

前n个元素

通过

截断流

我们可以看到

Java的JavaScript在Stream上本质的不同

,Java通过Stream 对象本身

OP_MASK

属性来截断,而JS没有实际意义上的Stream对象, 但是可以通过

filter结合index

来完成,或者使用

slice

java

Stream<T> limit(long maxSize);
List<Integer> list  =Arrays.asList(12,3,4,5,4);
list.stream().limit(2).forEach(System.out::println);=====123

JS

JS 的截断处理可以使用

slice

,或者通过

filter结合index

来完成

let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}]   
users.slice(0,2).forEach(o=>console.log('out :%s',o))======================================out:{name:'毋意',value:'202201'}out:{name:'毋必',value:'202202'}

users.filter((_, i)=> i <=1).forEach(o=> console.log('out :%s', o))============out:{name:'毋意',value:'202201'}out:{name:'毋必',value:'202202'}

sort|sorted 排序

排序,这个不多讲,

java

Stream<T> sorted(Comparator<? super T> comparator);
int compare(T o1, T o2);
List<Integer> list  =Arrays.asList(12,3,4,5,4);
list.stream().sorted((o1,o2)-> o1 > o2 ?1:(o1 < o2 ?-1:0)).forEach(System.out::println);===========344512

JS

arr.sort([compareFunction])
let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}]  
users.map(o=>{return{name: o.name,value:+o.value }}).sort((o1, o2)=> o1.value > o2.value ?-1:(o1.value < o2.value ?1:0)).forEach(o=> console.log(o))=================================={name:'毋我',value:202204}{name:'毋固',value:202203}{name:'毋必',value:202202}{name:'毋意',value:202201}

distinct 去重

筛选不同的元素:java流支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的

hashCode和equals

方法实现)的流

java

Stream<T> distinct();
List<Integer> list  =Arrays.asList(12,3,4,5,4);
list.stream().distinct().forEach(System.out::println);=========12345

JS

distinct是Stream本身的方法,JS没有类似的代替,不过可以转化为Set处理

let numbers =[2,3,4,3,5,2]
Array.from(newSet(numbers)).forEach(o=> console.log(o))

Set 内部判断两个值是否不同,使用的算法叫做

“Same-value-zero equality”

,它类似于精确相等运算符

(===)

,主要的区别是向 Set 加入值时认为NaN等于自身,而精确相等运算符认为NaN不等于自身。set 中两个对象总是不相等的。

skip 跳过

跳过元素

:返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,

limit(n)和skip(n)是互补的

java

Stream<T> skip(long n);
List<Integer> list  =Arrays.asList(12,3,4,5,4);
list.stream().skip(2).forEach(System.out::println);==================454

JS

Js 中可以通过

slice

方法来实现

let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}] 
users.slice(2).forEach(o=> console.log(o))========={name:'毋固',value:'202203'}{name:'毋我',value:'202204'}

group/groupToMap|groupingBy 分组

分组操作的结果是一个

Map

,把

分组函数返回的值作为映射的键

,把流中所有具有这个分类值的项目的列表作为对应的

映射值

java

Java 的分组通过Stream API 的

collect

方法传递

Collector

静态方法

groupingBy

,该方法传递了一个

Function

(以方法引用的形式)我们把这个Function叫作分类函数,因为它用来把流中的元素分成不同的组。

<R, A> R collect(Collector<? super T, A, R> collector);
publicstatic<T,K,A,D>Collector<T,?,Map<K,D>>groupingBy(Function<?superT,?extendsK> classifier,Collector<?superT,A,D> downstream){returngroupingBy(classifier,HashMap::new, downstream);}
@FunctionalInterfacepublicinterfaceFunction<T,R>{Rapply(T t);}

这块涉及的API蛮多的,不但可以分组,也可以分区,这里简单介绍几个,感兴趣小伙伴可以去看看API文档

getter分组
//getter分组 List<String> lists  =Arrays.asList("123","123","456","789");
lists.stream().collect(Collectors.groupingBy(String::hashCode)).forEach((o1,o2)->System.out.printf("%s:%s\n",o1,o2));==========48690:[123,123]51669:[456]54648:[789]
自定义逻辑分组
//2.自定义逻辑分组List<String> lists  =Arrays.asList("123","1234","4564","789");
lists.stream().collect(Collectors.groupingBy( o -> o.length())).forEach((o1,o2)->System.out.printf("%s:%s\n",o1,o2));=========3:[123,789]4:[1234,4564]
多级分组展示
// 多级分组List<String> list_  =Arrays.asList("123","1234","4564","1234");
list_.stream().collect(Collectors.groupingBy(o -> o.length(),Collectors.groupingBy(o1 -> o1.hashCode()))).forEach((o1,o2)->{System.out.printf("--length:%s\n",o1);
            o2.forEach((o3,o4)->System.out.printf(" |-hashCode:%s:%s\n",o3,o4));});========--length:3|-hashCode:48690:[123]--length:4|-hashCode:1509442:[1234,1234]|-hashCode:1601791:[4564]
分组统计
List<String> list_  =Arrays.asList("123","1234","4564","1234");
list_.stream().collect(Collectors.groupingBy(o -> o.length(),Collectors.groupingBy(o1 -> o1.hashCode(),Collectors.counting()))).forEach((o1,o2)->{System.out.printf("--length:%s\n",o1);
            o2.forEach((o3,o4)->System.out.printf(" |-hashCode:%s:sum:%s\n",o3,o4));});==========--length:3|-hashCode:48690:sum:1--length:4|-hashCode:1509442:sum:2|-hashCode:1601791:sum:1
把收集器的结果转换为另一种类型
// 把收集器的结果转换为另一种类型,按照长度排序得到最大值,然后给Optional修饰List<String> list_  =Arrays.asList("123","1234","4564","1234");
list_.stream().collect(Collectors.groupingBy(o -> o.length(),Collectors.groupingBy(o1 -> o1.hashCode(),Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(String::length)),Optional::get)))).forEach((o1,o2)->{System.out.printf("--length:%s\n",o1);
    o2.forEach((o3,o4)->System.out.printf(" |-hashCode:%s:max:%s\n",o3,o4));});=========--length:3|-hashCode:48690:max:123--length:4|-hashCode:1509442:max:1234|-hashCode:1601791:max:4564

JS

JavaScript 新增了数组实例方法

group()和groupToMap()

,可以根据分组函数的运行结果,将数组成员分组。目前还是一个提案,需要考虑浏览器兼容,按照字符串分组就使用

group()

,按照对象分组就使用

groupToMap()

。所以

groupToMap()

和Java的分组很类似。
在这里插入图片描述

Experimental: This is an experimental technology
Check the Browser compatibility table carefully before using this in production.
group(function(element, index, array) {}, thisArg)
const array =[1,2,3,4,5];

array.group((num, index, array)=>{return num %2===0?'even':'odd';});// { odd: [1, 3, 5], even: [2, 4] }
groupToMap(function(element, index, array) { }, thisArg)

groupToMap()的作用和用法与group()完全一致,唯一的区别是返回值是一个

 Map 结构

,而不是

对象
const array =[1,2,3,4,5];const odd  ={odd:true};const even ={even:true};
array.groupToMap((num, index, array)=>{return num %2===0? even: odd;});//  Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
如果分组函数是一个箭头函数,thisArg对象无效,因为箭头函数内部的this是固化的

,类似于Ajax回调内部的this。

forEach 消费

forEach 这个不多讲,用于消费

java

List<String> list_  =Arrays.asList("123","1234","4564","1234");
list_.forEach(System.out::print);==============123123445641234

JS

let users =[{ name:"毋意", value:"202201"},{ name:"毋必", value:"202202"},{ name:"毋固", value:"202203"},{ name:"毋我", value:"202204"}]

users.forEach(o => console.log(o))==========={ name:'毋意', value:'202201'}{ name:'毋必', value:'202202'}{ name:'毋固', value:'202203'}{ name:'毋我', value:'202204'}

count 统计

count 也不多讲

java

List<String> lists_ =Arrays.asList("123","1234","4564","1234");// 统计数据量System.out.println(lists_.stream().collect(Collectors.counting()));// 简单写法:System.out.println(lists_.stream().count());=========44

JS

在JS中没有对应的方法,不过Set和Map有对应的API,Array的可以使用

Array.prototype.length

reduce 归约

把数据源中的元素反复结合起来,得到一个值,即将流归约为一个值,用函数式编程语言叫折叠

java

Java 中的归约分为两种,一种为有初值的归约,一种为没有初值的归约。有初值的返回初值类型,没初值的返回一个Options

T reduce(T identity, BinaryOperator<T> accumulator);
List<Integer> numbers1 =Arrays.asList(1,2,34,5,6);// 元素求和int set = numbers1.stream().reduce(0,(a,b)-> a + b);// 改进
set = numbers1.stream().reduce(0,Integer::sum);
Optional<T> reduce(BinaryOperator<T> accumulator)
List<Integer> numbers1 =Arrays.asList(1,2,34,5,6);//元素求最大值int set = numbers1.stream().reduce(Integer::max).get();// 元素求最小值
set = numbers1.stream().reduce(Integer::min).get();List<String> lists_ =Arrays.asList("123","1234","4564","1234");System.out.println(lists_.stream().reduce((o1, o2)-> o1 +','+ o2).get());==========123,1234,4564,1234

JS

reduce((previousValue, currentValue, currentIndex, array) => {},initialValue)
let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}]let zy = users.map(o=> o.name).reduce((o1,o2)=> o1+','+o2)
console.log("子曰:子绝四,",zy)======
子曰:子绝四, 毋意,毋必,毋固,毋我

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);
List<Integer> numbers =Arrays.asList(1,2,3,4,5,6);System.out.println(numbers.stream().anyMatch(o -> o >5));//trueSystem.out.println(numbers.stream().allMatch(o -> o >0));//trueSystem.out.println(numbers.stream().noneMatch(o -> o <0));//true

JS

every()

方法测试数组中的

所有元素是否通过提供的函数实现的测试

,

every((element, index, array) => { /* ... */ } )
some()

方法测试数组中的

至少一个元素是否通过了提供的函数实现的测试

,

some((element, index, array) => { /* ... */ } )
let boo = Array.of(1,2,3,4,5,6).every(o=> o >5)
console.log(boo)//false
boo = Array.of(1,2,3,4,5,6).some(o=> o >5)
console.log(boo)//true

findLast(findLastIndex)/find(findIndex)|findAny/findFirst 查找

查找元素

:返回当前流的任意元素。

java

  • findAny()方法返回当前流的任意元素
  • findFirst()方法返回当前流的第一个元素。
List<Integer> numbers =Arrays.asList(1,2,3,4,5,6);System.out.println(numbers.stream().findAny().get());//1System.out.println(numbers.stream().findFirst().get());//1

JS

  • find()方法返回提供的数组中满足提供的测试功能的第一个元素
  • findIndex()方法返回满足提供的测试功能的数组中第一个元素的索引
let users =[{name:"毋意",value:"202201"},{name:"毋必",value:"202202"},{name:"毋固",value:"202203"},{name:"毋我",value:"202204"}]let user = users.find(o=> o.name ==="毋固")
console.log(user)//{ name: '毋固', value: '202203' }let useri = users.findIndex(o=> o.name ==="毋固")
console.log(useri)//2

这两个为ES2022 新增,当前Node版本不支持

在这里插入图片描述

  • findLast()方法返回满足提供的测试功能的数组中最后一个元素的值
  • findLastIndex()方法返回满足提供的测试功能的数组中最后一个元素的索引
user = users.findLast(o=> o.name ==="毋固")
console.log(user) 
useri = users.findLastIndex(o=> o.name ==="毋固")
console.log(useri)

嗯,时间关系,关于对比就分享到这啦,其实还有好多,比如

Stream API 

收集器

等,还有好多

奇技淫巧

,感兴趣小伙伴可以看看下的书籍和网站

博文参考


标签: java javascript 前端

本文转载自: https://blog.csdn.net/sanhewuyang/article/details/125827716
版权归原作者 山河已无恙 所有, 如有侵权,请联系我们删除。

“关于Java&amp;JavaScript中(伪)Stream式API对比的一些笔记”的评论:

还没有评论