Java 8中如何实现惰性流?

21

我正在阅读Java 8,尤其是“Streams API”。我想知道流如何实现延迟执行?

我认为流只是作为库添加的,并没有对语言进行任何更改来支持惰性。此外,如果有人告诉我这是通过反射实现的,我会感到震惊。


我接受了Alexi的答案,因为它更好地表达了我所寻找的内容。但是,HuStmpHrrr的答案看起来更加精炼。感谢大家。 - Learner
3个回答

25
为什么需要使用反射来实现懒加载呢?例如,考虑下面这个类:
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);
                }
            }
        }
    }
}

当你在惰性序列上使用filter函数时,过滤操作并不会立即执行。例如:
LazySeq<Integer> lazySeq = new LazySeq<>(Arrays.asList(1, 2, 3, 4));
lazySeq = lazySeq.filter(i -> i%2 == 0);

如果你在调用filter后查看序列的内容,你会发现它始终是1, 2, 3, 4。然而,当调用终端操作,比如forEach时,过滤将在使用消费者之前完成。例如:

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

将打印2和4。

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

调用终端操作时,此操作链开始执行,以便最终获得预期结果。

可以通过在应用中间操作时在管道上存储新状态来实现惰性计算,当调用终端操作时,您按顺序遍历所有状态。

Stream API并非真正以这种方式实现(它更加复杂),但原则确实存在。


6
不使用反射或代理。除非没有其他选择且性能是Java中的首要问题,否则应避免使用反射和代理。
使懒加载成为可能的是函数式编程风格。基本上,流(例如List)从源开始,有多个中间操作(例如过滤器、映射等),以及一个终端操作(例如计数、求和等)。 中间步骤执行时是惰性的,因为您传递函数(lambda),它们在管道中链接以在终端步骤中执行。
ex: filter(Predicate<? super T>)

在这个例子中,filter期望一个函数,告诉我们流中的对象是否符合某些条件。
许多来自Java 7的功能被用于使其更加高效。例如:使用invoke dynamic来执行lambda而不是代理或匿名内部类以及使用ForkJoin池进行并行执行。
如果您对Java 8内部感兴趣,那么您必须观看由该领域专家Brian Goetz所做的演讲,它在Youtube上。

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变得延迟。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接