Kotlin:stream vs sequence——为什么用多种方法做同样的事情?
Kotlin: stream vs sequence - why multiple ways to do the same thing?
为什么在 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 }
您的三个变体之一与使用列表本身相同:
items.asIterable()
.filter { it%2 == 0 }
在这里,您调用了与刚刚调用 items.filter
完全相同的 filter
函数。 List 本身是一个 Iterable
所以它是被调用的 Iterable 过滤器。此过滤器查看所有可用元素和 returns 完整列表。
所以问题是为什么我们同时拥有流和序列。
流是 Java 的一部分。许多终端操作会产生 Optional
。其他操作,例如 toList()
,将产生 non-null-safe 平台类型,例如 List<Integer!>
。另一方面,序列是 Kotlin 原生的,它们可以与 Kotlin 自己的 compile-time null-safety 特性一起使用。此外,序列在 non-JVM Kotlin 变体中可用。
Kotlin 设计者可能不得不创建一个新的 class,因为如果他们刚刚向 Stream 添加了新操作,例如作为扩展函数,它们会与现有名称冲突(例如 max()
returns Optional
in Java,这对 Kotlin 来说并不理想,但选择自然名称以外的名称max
也不理想。)
因此在大多数情况下,您应该更喜欢序列,因为它们更多 Kotlin-idiomatic。但是,有些事情 Java Streams 可以做,但序列尚不可用(例如,SummaryStatistics 或并行操作)。当您需要一个仅在 Streams 中可用的操作,但您有一个 Sequence 时,您可以使用 asStream()
将 Sequence 转换为 Stream(反之亦然)。
Streams 的另一个优点是您可以使用 IntStream 等原始流,以避免不必要的 boxing/unboxing.
Streams 来自 Java,其中没有内联函数,因此 Streams 是在 Java 的集合上使用这些函数链的唯一方法。 Kotlin 可以直接在 Iterables 上执行它们,在许多情况下这对性能更好,因为不需要创建中间 Stream 对象。
Kotlin 将序列作为流的替代品,具有以下优势:
- 他们回到 Java 1.6。流需要 Java 8 或更高。
- 他们使用 null 来表示缺失项而不是 Optional。可空值在 Kotlin 中更容易使用,因为它具有空安全功能。避免包装集合中的所有项目也会提高性能。
- 一些运算符和聚合函数更加简洁,避免了必须兼顾通用类型(比较
Sequence.groupBy
与 Stream.collect
)。
- 为 Sequences 提供了更多的运算符,这通过减少中间步骤带来了性能优势和更简单的代码。
- 许多终端运算符是内联函数,因此它们省略了 Stream 需要的最后一个包装器。
sequence
生成器允许您在协程中使用简单的顺序语法创建复杂的惰性项目序列。很给力。
另一个回答提到了Streams的优势。
为什么在 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 }
您的三个变体之一与使用列表本身相同:
items.asIterable()
.filter { it%2 == 0 }
在这里,您调用了与刚刚调用 items.filter
完全相同的 filter
函数。 List 本身是一个 Iterable
所以它是被调用的 Iterable 过滤器。此过滤器查看所有可用元素和 returns 完整列表。
所以问题是为什么我们同时拥有流和序列。
流是 Java 的一部分。许多终端操作会产生 Optional
。其他操作,例如 toList()
,将产生 non-null-safe 平台类型,例如 List<Integer!>
。另一方面,序列是 Kotlin 原生的,它们可以与 Kotlin 自己的 compile-time null-safety 特性一起使用。此外,序列在 non-JVM Kotlin 变体中可用。
Kotlin 设计者可能不得不创建一个新的 class,因为如果他们刚刚向 Stream 添加了新操作,例如作为扩展函数,它们会与现有名称冲突(例如 max()
returns Optional
in Java,这对 Kotlin 来说并不理想,但选择自然名称以外的名称max
也不理想。)
因此在大多数情况下,您应该更喜欢序列,因为它们更多 Kotlin-idiomatic。但是,有些事情 Java Streams 可以做,但序列尚不可用(例如,SummaryStatistics 或并行操作)。当您需要一个仅在 Streams 中可用的操作,但您有一个 Sequence 时,您可以使用 asStream()
将 Sequence 转换为 Stream(反之亦然)。
Streams 的另一个优点是您可以使用 IntStream 等原始流,以避免不必要的 boxing/unboxing.
Streams 来自 Java,其中没有内联函数,因此 Streams 是在 Java 的集合上使用这些函数链的唯一方法。 Kotlin 可以直接在 Iterables 上执行它们,在许多情况下这对性能更好,因为不需要创建中间 Stream 对象。
Kotlin 将序列作为流的替代品,具有以下优势:
- 他们回到 Java 1.6。流需要 Java 8 或更高。
- 他们使用 null 来表示缺失项而不是 Optional。可空值在 Kotlin 中更容易使用,因为它具有空安全功能。避免包装集合中的所有项目也会提高性能。
- 一些运算符和聚合函数更加简洁,避免了必须兼顾通用类型(比较
Sequence.groupBy
与Stream.collect
)。 - 为 Sequences 提供了更多的运算符,这通过减少中间步骤带来了性能优势和更简单的代码。
- 许多终端运算符是内联函数,因此它们省略了 Stream 需要的最后一个包装器。
sequence
生成器允许您在协程中使用简单的顺序语法创建复杂的惰性项目序列。很给力。
另一个回答提到了Streams的优势。