Kotlin:流与序列——为什么用多种方式做同一件事



在Kotlin 中有多种方法可以做同样的事情是有原因的吗

val viaSequence = items.asSequence()
.filter { it%2 == 0 }
.map { it*2 }
.toList()
println(viaSequence)
val viaIterable = items.asIterable()
.filter { it%2 == 0 }
.map { it*2 }
.toList()
println(viaIterable)
val viaStream = items.stream()
.filter { it%2 == 0 }
.map { it*2 }
.toList()
println(viaStream)

我知道下面的代码会在每个步骤上创建一个列表,这会给GC增加负载,因此应该避免:

items.filter { it%2 == 0 }.map { it*2 }

流来自Java,那里没有内联函数,因此流是在Java中的集合上使用这些函数链的唯一方法。Kotlin可以直接在Iterables上执行这些操作,这在许多情况下对性能更好,因为不需要创建中间的Stream或Sequence对象。

Kotlin将Sequences作为Streams的替代方案,具有以下优势:

  • 它们使用null表示缺少的项,而不是Optional。由于Kotlin具有null安全功能,因此在Kotlin中更容易使用null值。为了提高性能,避免包装集合中的所有项目
  • 一些运算符和聚合函数要简洁得多,可以避免处理泛型类型(比较Sequence.groupByStream.collect(
  • 为序列提供了更多的运算符,通过省去中间步骤,可以获得性能优势和更简单的代码
  • 许多终端运算符都是内联函数,因此它们省略了Stream所需的最后一个包装器
  • sequence构建器允许您在协同程序中使用简单的顺序语法创建一个复杂的惰性项序列。动力很好
  • 他们回到Java 1.6。流需要Java 8或更高版本。这一点与Kotlin 1.5及更高版本无关,因为Kotlin现在需要JDK 8或更高版本

另一个答案提到了Streams的优势。

在大多数情况下,直接使用Iterable内联运算符函数而不是Sequences或Streams对于性能和代码清晰度来说是最好的。除了使用sequence构建器延迟生成项之外,我默认选择Iterable运算符,并且只有在存在需要优化的性能瓶颈时才考虑并尝试使用Sequences。通常情况下,您可能会发现无论如何都无法使用Sequence方法来执行比Iterable运算符更好的操作。

这是一篇比较它们的好文章。

您的三种变体之一与使用列表本身相同:

items.asIterable()
.filter { it%2 == 0 }

在这里,您调用的filter函数与刚才调用items.filter的函数完全相同。List本身是一个Iterable,因此它是被调用的Iterable过滤器。这个过滤器查看所有可用的元素并返回一个完整的列表。

所以问题是为什么我们同时拥有流和序列。

流是Java的一部分。许多终端操作产生CCD_ 8。其他操作,如toList(),将生成非空安全平台类型,如List<Integer!>。另一方面,序列是Kotlin固有的,它们可以与Kotlin自己的编译时null安全特性一起使用。此外,序列在Kotlin的非JVM变体中也可用。

Kotlin的设计者可能不得不创建一个新的类,因为如果他们刚刚向Stream添加了新的操作,例如作为扩展函数,它们将与现有的名称发生冲突(例如,max()在Java中返回Optional,这对Kotlin来说并不理想,但选择自然名称max以外的名称也不理想。(

因此,在大多数情况下,您应该更喜欢序列,因为它们更符合Kotlin习惯用法。然而,Java Streams可以做一些序列尚不可用的事情(例如,SummaryStatistics或并行操作(。当您需要一个仅在Streams中可用的操作,但您有一个Sequence时,您可以使用asStream()将Sequence转换为Stream(反之亦然(。

Streams的另一个优点是可以使用IntStream等基本流,以避免不必要的装箱/取消装箱。

最新更新