Kotlin的Iterable和Sequence看起来完全一样。 为什么需要两种类型?
这两个接口都只定义了一种方法
public operator fun iterator(): Iterator<T>
文件说Sequence
是懒惰的。 但是也不是可以Iterable
懒(除非有一个Collection
支持)?
关键的区别在于Iterable<T>
和Sequence<T>
的stdlib扩展函数的语义和实现。
-
对于
Sequence<T>
,扩展函数在可能的情况下执行延迟,类似于Java Streams 中间操作。 例如,Sequence<T>.map { ... }
返回另一个Sequence<R>
,实际上并不处理这些项目,直到调用像toList
或fold
这样的终端操作为止。考虑这个代码:
val seq = sequenceOf(1, 2) val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } print("before sum ") val sum = seqMapped.sum()
它打印:
before sum 1 2
当您想要尽可能减少在终端操作中完成的工作时,
Sequence<T>
用于惰性使用和高效管道,与Java Streams相同。 然而,懒惰引入了一些开销,这对于较小集合的常见简单转换是不可取的,并且使得它们性能较差。一般来说,没有什么好的方法来确定什么时候需要它,所以在Kotlin中stdlib懒惰是明确的,并提取到
Sequence<T>
接口,以避免在所有的Iterable
默认使用它。 -
对于
Iterable<T>
,相反,具有中间操作语义的扩展函数急切地工作,立即处理项目并返回另一个Iterable
。 例如,Iterable<T>.map { ... }
返回一个带有映射结果的List<R>
。Iterable的等效代码:
val lst = listOf(1, 2) val lstMapped: List<Int> = lst.map { print("$it "); it * it } print("before sum ") val sum = lstMapped.sum()
这打印出来:
1 2 before sum
如上所述,默认情况下,
Iterable<T>
是非惰性的,这个解决方案很好地展示了它:在大多数情况下,它具有良好的参考局部性,从而利用CPU缓存,预测,预取等优势,收集仍然工作得很好,并在小集合的简单情况下表现更好。如果您需要对评估流水线进行更多的控制,则会使用
Iterable<T>.asSequence()
函数显式转换为惰性序列。