【问题标题】:Are Java's Streams like JavaScript's Arrays? [closed]Java Streams 像 JavaScripts 数组吗? [关闭]
【发布时间】:2018-02-04 08:26:39
【问题描述】:

我尝试为 Java 的 IntStream.range(0, 5).forEach(System.err::println); 构建 Javascript 等效项并达到

const IntStream = (function () {
    function range(start, end, numbers = []) {
        if (start === end) {
            return numbers
        }
        return range(start + 1, end, numbers.concat(start))
    }

    return {
        range
    }
})()

IntStream.range(0, 5).forEach(number => console.log(number))

Java 的所有流魔法都内置在一个普通的 JavaScript 数组中。为什么 Java 中的 ArrayList 不能与 Stream 做所有相同的事情,或者有什么我还没弄清楚的目的?

【问题讨论】:

  • 外观相似,行为不同。

标签: javascript java arrays functional-programming java-stream


【解决方案1】:

数组高阶函数会在每一步都热切地完成整个事情。

const isOdd = v => v % 2 == 1;
const multiply = by => v => v * by;    

const arrRange = IntStream.range(10, 20);
const arrOdd = arrRange.filter(isOdd);
const arrOddM3 = arrOdd.map(multiply(3));

这里所有的绑定都是由每个步骤创建的不同数组。即使将它们链接起来,中间数组也总是会生成,并且每个步骤的整个数组都需要在下一步开始之前完成。

const arrOddM3 = IntStream.range(10, 20).filter(isOdd).map(multiply(3));
arrOddM3; // ==> [33, 39, 45, 51, 57]

流是不同的,因为它们只在访问时计算值。流版本看起来非常相似。

const streamOddM3 = Stream.range(10, Infinity).filter(isOdd).map(multiply(3));
streamOddM3; // ==> Stream

请注意,我已将结尾更改为无穷大。我可以这样做,因为它最多计算第一个值,而某些实现在您要求值之前根本不进行任何计算。要强制计算,您可以获取一些值并将它们作为数组返回:

streamOddM3.take(3); // ==> [33, 39, 45]

这是一个基于 SICP videos 的 Stream 实现,其工作方式类似于 Java 的流。

class EmptyStream {
    map() {
        return this;
    }

    filter() {
        return this;
    }

    take() {
        return [];
    }
}

class Stream extends EmptyStream {
    constructor(value, next) {
        super();
        this._next = next;
        this.value = value;
    }

    /**
     * This prevents the value to be computed more than once
     * @returns {EmptyStream|Stream}
     */
    next() {
        if( ! (this._next instanceof EmptyStream) ) {
            this._next = this._next();
        }
        return this._next;
    }

    map(fn) {
        return new Stream(fn(this.value), () => this.next().map(fn));
    }

    filter(fn) {
        return fn(this.value) ?
            new Stream(this.value, () => this.next().filter(fn)) :
            this.next().filter(fn);
    }

    take(n) {
        return n == 0 ? [] : [this.value, ...this.next().take(n && n - 1)];
    }

    static range(from, to, step = 1) {
        if (to !== undefined && ( step > 0 && from > to || step < 0 && from < to )) {
            return Stream.emptyStream;
        }
        return new Stream(from, () => Stream.range(from + step, to, step));
    }
}

Stream.emptyStream = new EmptyStream();

Stream 有一些替代品可以代替它们。

在 JavaScript 中,你有 generators(又名协程),你可以创建一个 map 和 filter 生成器函数,它接受一个生成器源并通过该转换成为一个新的生成器。由于它已经在该语言中,因此它可能比 Streams 更匹配,但我对它的研究还不够多,无法制作上述的生成器示例。

在 Clojure 中,您有 transducers,它允许您组合步骤,以便最终列表仅针对使其成为最终结果的元素发生。它们很容易用 JavaScript 实现。

【讨论】:

    【解决方案2】:

    不,它们不一样,因为它们处理数据的方式不同。 在 LINQ (C#) 或 javascript 中,集合上的每个操作都必须在调用管道中的下一个操作之前结束。

    在流中,它是不同的。例如:

    Arrays.asList(1,2,3).stream()
            .filter((Integer x)-> x>1)
            .map((Integer x)->x*10)
            .forEach(System.out::println);
    

    源集合:1、2、3

    1. filter(1) -> 你不行。元素 1 不会传递到下一个操作 在管线中。现在处理元素 2。
    2. filter(2) -> 你没问题。元素 2 传递到下一个操作。 map(2) -> 创建新元素 20 并将其放入新流中。
    3. forEach(20) -> 打印 20。结束处理源集合中的元素 2。 现在处理元素 3。
    4. filter(3) -> 你没问题。元素 3 传递到下一个操作
    5. map(3) -> 创建新元素 30 并将其放入新流中。
    6. forEach(20) -> 打印 30。源集合中没有更多元素。 完成流的执行。

      输出: 20 30

    插图:

    这种方法的结果之一是有时管道中的某些操作不会遍历每个元素,因为其中一些在过程中被过滤掉了。

    此解释摘自:Streams In Depth By Stav Alfi

    【讨论】:

      【解决方案3】:

      Streams 和 Javasvript 数组有很大区别:

      [1,2,3,4]
       .filter(el => {
        console.log(el);
        return el%2 === 0;
      })
      .forEach( el => console.log(el));
      

      javascript 中的结果将是:

       1,2,3,4 2,4
      

      对于 Stream,它将是:

      1,2 2 3,4 4
      

      如您所见,javascript 会改变集合,然后迭代集合。传递给 Stream 的元素会遍历流。如果将集合传递给 Stream,则会在 Stream 中一个接一个地传递元素。

      可能的 Stream 实现是:

      class Stream {
        constructor(){
          this.queue = [];
        }
        //the modifying methods
        forEach(func){
         this.queue.push(["forEach",func]);
         return this;
        }
        filter(func){
         this.queue.push(["filter",func]);
         return this;
        }
        map(func){
         this.queue.push(["map",func]);
         return this;
        }
        subStream(v){
         this.forEach(d => v.get(d));
         return this;
        }
      
        //data methods
       get(value,cb){
        for( let [type,func] of this.queue ){
         switch(type){
           case "forEach":
             func(value);
           break;
           case "map":
             value = func(value);
           break;
           case "filter":
             if(! func(value)) return;
          }
         }
         cb(value);
        }             
        range(start,end){
         const result = [];
         Array.from({length:end-start})
           .forEach((_,i)=>  this.get(i+start, r => result.push(r)));
         return result;
        }
      }
      

      用例:

      const nums = new Stream();
      const even = new Stream();
       even.filter(n => !(n%2) ).forEach(n => console.log(n));
      const odd = new Stream();
       even.filter(n => (n%2) ).forEach(n => console.log(n));
      
      nums
       .subStream(even)
       .subStream(odd)
       .range(0,100);
      

      【讨论】:

      • 很好的解释。谢谢..
      • JavaScript filtermap 改变源数组。
      • Streams 也是惰性的。例如,如果你有一个从 0 到无穷大的范围,过滤奇数,乘以 3 并取前三个值,你只会产生 6 个数字,过滤掉 3 并相乘并产生一个数组。您的实现将失败,因为您首先创建了无限数组!
      猜你喜欢
      • 2020-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-09
      • 1970-01-01
      • 1970-01-01
      • 2021-05-09
      • 2021-12-17
      相关资源
      最近更新 更多