Java的Streams是否像JavaScript的Arrays?

时间:2017-08-26 13:13:32

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

我尝试为Java的class RoundedLabel: UILabel { private lazy var maskLayer: CAShapeLayer = { let maskLayer = CAShapeLayer() maskLayer.cornerRadius = 5 maskLayer.masksToBounds = true self.layer.mask = maskLayer return maskLayer }() override func layoutSubviews() { super.layoutSubviews() let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topRight, .bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10)) maskLayer.path = maskPath.cgPath maskLayer.frame = bounds } } 构建等效的Javascript并达到

IntStream.range(0, 5).forEach(System.err::println);

Java的所有流魔术都内置在普通的JavaScript数组中。为什么Java中的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)) 不能像ArrayList那样完成相同的事情,还是有一个我没想到的目的呢?

3 个答案:

答案 0 :(得分:3)

数组高阶函数将在每一步都急切地完成整个过程。

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]

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

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(又名协同程序),你可以制作一个地图和过滤器生成器函数,它接受一个生成器源并成为一个带有该转换的新生成器。由于它已经在语言中,它可能比Streams更好地匹配,但我还没有研究它足以制作上述的生成器示例。

在Clojure中,你有transducers,它允许你编写步骤,这样只有在使得它成为最终结果的元素时才会发生最终列表。它们很容易用JavaScript实现。

答案 1 :(得分:2)

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实现将是:

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

USECASE:

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

答案 2 :(得分:1)

不是因为他们如何处理数据而不一样。 在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. 过滤器(1) - &gt;你不行。元素1不会传递给下一个操作 在管线中。现在处理元素2。
  2. 过滤器(2) - &gt;你还行。元素2传递给下一个操作。 map(2) - &gt;创建新元素20并将其放入新流中。
  3. forEach(20) - &gt; print 20.结束处理源集合中的元素2。 现在处理元素3。
  4. 过滤器(3) - &gt;你还行。元素3传递给下一个操作
  5. map(3) - &gt;创建新元素30并将其放入新流中。
  6. forEach(20) - &gt; print 30.源集合中没有更多元素。 完成对流的冲击。

    输出: 20 30

  7. 插图:

    enter image description here

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

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