scala Java 8流,首尾相连
Java 8引入了一个类似于Scala的Stream的Stream类,这是一个强大的惰性构造,使用它可以非常简洁地执行以下操作:
def from(n: Int): Stream[Int] = n #:: from(n+1)
def sieve(s: Stream[Int]): Stream[Int] = {
s.head #:: sieve(s.tail filter (_ % s.head != 0))
}
val primes = sieve(from(2))
primes takeWhile(_ < 1000) print // prints all primes less than 1000
我想知道是否有可能在Java 8中实现这一点,所以我写了如下内容:
IntStream from(int n) {
return IntStream.iterate(n, m -> m + 1);
}
IntStream sieve(IntStream s) {
int head = s.findFirst().getAsInt();
return IntStream.concat(IntStream.of(head), sieve(s.skip(1).filter(n -> n % head != 0)));
}
IntStream primes = sieve(from(2));
相当简单,但它会生成java.lang.IllegalStateException: stream has already been operated upon or closed
,因为findFirst()
和skip()
都是Stream
上的终端操作,只能执行一次
我真的不必两次用完流,因为我只需要流中的第一个数字,其余的作为另一个流,即相当于Scala的Stream.head
和Stream.tail
。Java 8Stream
中是否有一种方法可以用来实现这一点
谢谢
# 1 楼答案
即使没有无法拆分
IntStream
的问题,代码也无法工作,因为您是递归地调用sieve
方法,而不是惰性地调用。因此,在查询结果流中的第一个值之前,需要进行无限递归将一个
IntStream s
分为一个头部和一个尾部IntStream
(尚未消耗)是可能的:在这个地方,您需要一个在尾部懒洋洋地调用
sieve
的构造Stream
没有规定concat
期望现有的流实例作为参数,而您不能用lambda表达式延迟地构造调用sieve
的流,因为延迟创建仅适用于lambda表达式不支持的可变状态。如果没有隐藏可变状态的库实现,则必须使用可变对象。但一旦您接受可变状态的要求,解决方案可能比第一种方法更简单:这将递归地创建一个过滤器,但最终不管你是创建一个
IntPredicate
树还是一个IntStream
树(就像你的IntStream.concat
方法,如果它确实有效的话)。如果不喜欢过滤器的可变实例字段,可以将其隐藏在内部类中(但不能隐藏在lambda表达式中)# 2 楼答案
下面的解决方案不进行状态突变,除了流的头/尾解构
懒散感是通过IntStream获得的。迭代Prime类用于保持生成器的状态
用法如下:
# 3 楼答案
这里提供了许多有趣的建议,但如果有人需要一个不依赖于第三方库的解决方案,我提出了以下建议:
# 4 楼答案
基本上可以这样实现:
在上面的例子中,
Tuple2
和Seq
是从jOOλ借来的类型,这是我们为jOOQ集成测试开发的库。如果不需要任何其他依赖项,不妨自己实现它们:# 5 楼答案
如果您不介意使用第三方库cyclops-streams,我写的库有很多潜在的解决方案
StreamUtils类有大量静态方法,用于直接与
java.util.stream.Streams
一起工作,包括headAndTail
Streamable类表示一个可重放的
Stream
,它通过构建一个懒惰的、高速缓存的中间数据结构来工作。因为它是缓存和可偿还的——头和尾可以直接单独实现cyclops-streams还提供了一个顺序的
Stream
扩展,该扩展反过来扩展jOOλ,并具有基于Tuple
的(来自jOOλ)和域对象(HeadAndTail)的头尾提取解决方案根据Tagir的请求进行更新->;使用
SequenceM
的Scala筛的Java版本另一个版本是通过
Streamable
注意
SequenceM
中的Streamable
都没有空的实现——因此对Streamable
进行大小检查并使用headAndTailOptional
最后是一个使用普通
java.util.stream.Stream
另一个更新——基于@Holger版本的惰性迭代,使用对象而不是原语(注意,原语版本也是可能的)
# 6 楼答案
我的StreamEx库现在有^{} 操作,它解决了这个问题:
headTail
方法采用BiFunction
,在流终端操作执行期间最多执行一次。所以这个实现是懒惰的:它在遍历开始之前不计算任何东西,只计算请求的素数。BiFunction
接收第一个流元素head
和其余元素的流tail
,并且可以以它想要的任何方式修改tail
。您可以将其与预定义的输入一起使用:但无限流也能起作用
还有另一种解决方案,使用
headTail
和谓词连接:比较递归解很有趣:它们能生成多少素数
@John McClean解决方案(
StreamUtils
)约翰·麦克莱恩(John McClean)的解决方案并不懒惰:你不能用无限的流量来满足他们。所以我只是通过反复试验找到了最大允许上限(
17793
)(在发生StackOverflower错误之后):@John McClean解决方案(
Streamable
)将上限增加到
39990
以上会导致堆栈溢出错误@frhack解决方案(
LazySeq
)结果:在质数=
53327
之后卡住,巨大的堆分配和垃圾收集占90%以上。从53323上升到53327需要几分钟的时间,因此等待更多时间似乎不切实际@vidi解决方案
结果:质数后的StackOverflowerr=
134417
我的解决方案(StreamEx)
结果:质数后的StackOverflowerr=
236167
@frhack解决方案(
rxjava
)结果:质数后的StackOverflowerr=
367663
@Holger解决方案
结果:质数后的StackOverflowerr=
368089
我的解决方案(带谓词连接的StreamEx)
结果:质数后的StackOverflowerr=
368287
因此,三个涉及谓词连接的解决方案获胜,因为每个新条件只会再添加两个堆栈帧。我认为,两者之间的差异是微不足道的,不应被视为定义一个赢家。不过,我更喜欢我的第一个StreamEx解决方案,因为它更类似于Scala代码