关于Java&JavaScript中(伪)Stream式API对比的一些笔记

Posted 山河已无恙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于Java&JavaScript中(伪)Stream式API对比的一些笔记相关的知识,希望对你有一定的参考价值。

写在前面


  • 前些时日开发遇到,想着把这些对比总结下
  • 博文内容包括:
    • Stream 相关概念简述
    • Java和javascript的Stream式API对比Demo
  • 食用方式
    • 博文适合会一点前端的Java后端&会一点Java后端的前端
    • 需要了解Java&JavaScript基础知识
  • 理解不足小伙伴帮忙指正

追求轻微痛感,掌控快感释放,先做困难的事情,降低奖励期待,控制欲望,延迟消费多巴胺


什么是流(Stream)

关于 Stream, 在Java中我们叫,但是在JavaScript中,好像没有这种叫,也没有StreamAPI,我么姑且称为伪流,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 Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server 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);

======
13
4
5
6
5

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 :H
out :e
out :l
out :l
out :o
out :W
out :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);
=====
12
3

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);
===========
3
4
4
5
12

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);
=========
12
3
4
5

JS

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

let numbers = [2,3,4,3,5,2]
Array.from(new Set(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);
==================
4
5
4

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);

public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                      Collector<? super T, A, D> downstream) 
    return groupingBy(classifier, HashMap::new, downstream);

@FunctionalInterface
public interface Function<T, R&

以上是关于关于Java&JavaScript中(伪)Stream式API对比的一些笔记的主要内容,如果未能解决你的问题,请参考以下文章

鼎育教育成都java培训机构学习javascript注意的4个问题

关于Java随机数

大道至简第一章读后感(java伪代码)

JAVA大道至简第一章伪代码

伪共享和缓存行填充,从Java 6, Java 7 到Java 8

关于java.util包下的Random类