注意Java 8的[原语]流中的递归


interesting question by Tagir Valeev on Stack Overflow最近引起了我的注意。简而言之(阅读问题的细节),下面的代码是有效的:

public static Stream<Long> longs() {
    return Stream.iterate(1L, i ->
        1L + longs().skip(i - 1L)
                    .findFirst()
                    .get());
}

longs().limit(5).forEach(System.out::println);

打印:





以下类似的代码不起作用:

public static LongStream longs() {
    return LongStream.iterate(1L, i ->
        1L + longs().skip(i - 1L)
                    .findFirst()
                    .getAsLong());
}

引起StackOverflowError

当然,这种递归迭代不是最佳的。它不是在Java 8之前,也肯定不是在新的应用编程接口中。但是有人可能认为它至少应该起作用,对吗?它不起作用的原因是因为两者之间的细微实现差异iterate()Java 8中的方法。而引用类型流的Iterator首先返回seed然后通过对前一个值应用迭代函数来继续迭代:

final Iterator<T> iterator = new Iterator<T>() {
    @SuppressWarnings("unchecked")
    T t = (T) Streams.NONE;

    @Override
    public boolean hasNext() {
        return true;
    }

    @Override
    public T next() {
        return t = (t == Streams.NONE) ? seed : f.apply(t);
    }
};

事实并非如此LongStream.iterate()版本(和其他原始流):

final PrimitiveIterator.OfLong iterator = new PrimitiveIterator.OfLong() {
    long t = seed;

    @Override
    public boolean hasNext() {
        return true;
    }

    @Override
    public long nextLong() {
        long v = t;
        t = f.applyAsLong(t);
        return v;
    }
};

迭代函数已经预先提取了一个值。这通常不是问题,但会导致:

  1. 迭代函数昂贵时的优化问题
  2. 递归使用迭代器时的无限递归

作为一种变通方法,最好在基元类型流中简单地避免使用此方法的递归。幸运的是,JDK 9的修复已经在进行中(作为特性增强的副作用):
https://bugs.openjdk.java.net/browse/JDK-8072727