Kotlin的Iterable和Sequence看起来完全一样.为什么需要两种类型? [英] Kotlin's Iterable and Sequence look exactly same. Why are two types required?

查看:271
本文介绍了Kotlin的Iterable和Sequence看起来完全一样.为什么需要两种类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这两个接口仅定义一种方法

public operator fun iterator(): Iterator<T>

文档显示Sequence意味着是懒惰的.但是Iterable也不懒惰吗(除非有Collection支持)?

解决方案

主要区别在于Iterable<T>Sequence<T>的语义以及stdlib扩展功能的实现.

  • 对于Sequence<T>,扩展功能在可能的情况下延迟执行,类似于Java Streams intermediate 操作.例如, Sequence<T>.map { ... } 返回另一个Sequence<R>并且在调用fold之类的 terminal 操作之前不会真正处理这些项目.

    考虑以下代码:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    它打印:

    before sum 1 2
    

    Sequence<T>与Java Streams一样,当您希望尽可能减少在 terminal 操作中完成的工作时,可用于延迟使用和高效的流水线操作.但是,懒惰会带来一些开销,这对于较小的collection的常见简单转换是不希望的,并且会使它们的性能降低.

    通常,没有确定何时需要它的好方法,因此在Kotlin stdlib中,懒惰是明确的并被提取到Sequence<T>接口,以避免默认情况下在所有Iterable上使用它.

  • 相反,对于Iterable<T>,具有 intermediate 操作语义的扩展功能急切工作,立即处理各项并返回另一个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缓存,预测,预取等优势,因此即使对集合进行多次复制仍然可以很好地工作,并且在具有小集合的简单情况下也可以表现更好.

    如果您需要对评估管道的更多控制,则可以使用 解决方案

The key difference lies in the semantics and the implementation of the stdlib extension functions for Iterable<T> and Sequence<T>.

  • For Sequence<T>, the extension functions perform lazily where possible, similarly to Java Streams intermediate operations. For example, Sequence<T>.map { ... } returns another Sequence<R> and does not actually process the items until a terminal operation like toList or fold is called.

    Consider this code:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    It prints:

    before sum 1 2
    

    Sequence<T> is intended for lazy usage and efficient pipelining when you want to reduce the work done in terminal operations as much as possible, same to Java Streams. However, laziness introduces some overhead, which is undesirable for common simple transformations of smaller collections and makes them less performant.

    In general, there is no good way to determine when it is needed, so in Kotlin stdlib laziness is made explicit and extracted to the Sequence<T> interface to avoid using it on all the Iterables by default.

  • For Iterable<T>, on contrary, the extension functions with intermediate operation semantics work eagerly, process the items right away and return another Iterable. For example, Iterable<T>.map { ... } returns a List<R> with the mapping results in it.

    The equivalent code for Iterable:

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    This prints out:

    1 2 before sum
    

    As said above, Iterable<T> is non-lazy by default, and this solution shows itself well: in most cases it has good locality of reference thus taking advantage of CPU cache, prediction, prefetching etc. so that even multiple copying of a collection still works good enough and performs better in simple cases with small collections.

    If you need more control over the evaluation pipeline, there is an explicit conversion to a lazy sequence with Iterable<T>.asSequence() function.

这篇关于Kotlin的Iterable和Sequence看起来完全一样.为什么需要两种类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆