如何在Java 8中实现延迟流?

时间:2015-05-14 20:21:08

标签: java-8 java-stream

我正在阅读Java 8,特别是" Streams API"。我想知道流是如何懒惰的?

我认为流只是作为一个库添加,并且没有对语言进行任何更改来支持懒惰。另外,如果有人告诉我通过反思实现了它,我会感到震惊。

3 个答案:

答案 0 :(得分:23)

为什么你需要反思才能得到懒惰?例如,考虑这个类:

class LazySeq<T> {

    private final List<T> list;
    private Predicate<? super T> predicate;

    public LazySeq(List<T> input) {
        this.list = new ArrayList<>(input);
    }

    //Here you just store the predicate, but you don't perform a filtering
    //You could also return a new LazySeq with a new state
    public LazySeq<T> filter(Predicate<? super T> predicate) {
        this.predicate = predicate;
        return this;
    }

    public void forEach(Consumer<? super T> consumer){
        if(predicate == null) {
            list.forEach(consumer);
        } else {
            for(T elem : list) {
                if(predicate.test(elem)) {
                    consumer.accept(elem);
                }
            }
        }
    }
}

当你在懒惰的seq上调用filter时,过滤不会立即发生,例如:

LazySeq<Integer> lazySeq = new LazySeq<>(Arrays.asList(1, 2, 3, 4));
lazySeq = lazySeq.filter(i -> i%2 == 0);

如果在调用过滤器后看到序列的内容,您会发现它总是1, 2, 3, 4。但是,在调用终端操作(例如forEach)时,将在使用消费者之前完成过滤。例如:

lazySeq.filter(i -> i%2 == 0).forEach(System.out::println);

将打印2和4。

这与Stream s的原理相同。从源中,链接具有某些属性的操作。这些操作是中间的,它返回一个惰性流(例如filtermap)或终端(例如forEach)。其中一些终端操作是短路的(例如findFirst),因此您可能无法遍历所有管道(您可以想到for循环中的早期返回,它返回数组中值的索引)例子)。

当调用终端操作时,这一系列操作开始执行,以便最终得到预期的结果。

当应用中间操作时,可以通过在管道上存储新状态来实现惰性,当您调用终端操作时,您将逐个遍历数据上的所有状态。

Stream API并没有真正实现(它有点复杂)但实际上原则就在这里。

答案 1 :(得分:6)

没有反思或代理。除非没有替代方案且性能在Java中排名第一,否则反思和代理需要避免性能成本。

使懒惰成为可能的是做事的功能风格。基本上stream以源(ex:List),中间操作数(例如:filters,map ..)和终端操作(例如:count,sum等等)开始。 中间步骤懒惰地执行,因为你传递了在管道中链接的函数(lambdas),以便在终端步骤执行。

ex: filter(Predicate<? super T>)
此示例中的

filter需要一个函数来告诉我们流中的对象是否符合某些条件。

Java 7提供了许多功能,可以提高效率。例如:调用动态执行lambdas而不是代理或匿名内部类和ForkJoin池来执行并行执行。

如果你对Java 8内部感兴趣,那么你必须看看Brian Goetz领域的专家给出的这个演讲,它在Youtube上。

答案 2 :(得分:3)

流式传输不是数据容器,而是逻辑容器。你只需要传入一个接口实例来记住逻辑。

考虑以下代码:

class FilterIterable<T> implements Iterable<T>
{
    private Iterable<? extends T> iter;
    private Predicate<? super T> pred;

    public FilterIterable(Iterable<? extends T> iter, Predicate<? super T> pred) {
        this.iter = iter;
        this.pred = pred;
    }

    public Iterator<T> iterator() {
        return FilterIterator<T>();
    }

    class FilterIterator<T> implements Iterator<T>
    {
        private Iterator<? extends T> iterator = iter.iterator();
        private T next = null;

        FilterIterator() {
            getNext();
        }

        private void getNext() {
            next = null;
            while (iterator.hasNext()) {
                T temp = iterator.next();
                if (pred.test(temp)) {
                    next = temp;
                    break;
                }
            }
        }

        public boolean hasNext() { return next != null; }

        public T next() {
            T temp = next;
            getNext();
            return temp;
        }       
    }
}

逻辑包含在pred内,但仅在对象被迭代时调用。事实上,这个类不存储任何数据,它只保留一个可能包含数据的迭代,甚至只保留另一个逻辑持有者本身。

并且元素也会按需返回。这种范式使所谓的流api懒惰。