Kotlin 科特林';Iterable和Sequence看起来完全一样。为什么需要两种类型?
这两个接口只定义一个方法Kotlin 科特林';Iterable和Sequence看起来完全一样。为什么需要两种类型?,kotlin,iterable,lazy-sequences,Kotlin,Iterable,Lazy Sequences,这两个接口只定义一个方法 public operator fun iterator(): Iterator<T> 公共运算符fun iterator():iterator 文档中说,Sequence意味着懒惰。但是Iterable不是也很懒吗(除非有集合的支持)关键区别在于Iterable和Sequence的语义和stdlib扩展函数的实现 对于Sequence,扩展函数尽可能地延迟执行,类似于Java Streams中间操作。例如,返回另一个序列,在调用toList或fold
public operator fun iterator(): Iterator<T>
公共运算符fun iterator():iterator
文档中说,
Sequence
意味着懒惰。但是Iterable
不是也很懒吗(除非有集合的支持)关键区别在于Iterable
和Sequence
的语义和stdlib扩展函数的实现
- 对于
Sequence
,扩展函数尽可能地延迟执行,类似于Java Streams中间操作。例如,返回另一个序列
,在调用toList
或fold
等终端操作之前,不会实际处理项目
考虑以下代码:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Sequence
用于在您希望尽可能减少终端操作中完成的工作时进行延迟使用和高效的管道化,与Java流相同。但是,惰性会带来一些开销,这对于较小集合的普通简单转换是不可取的,并且会降低它们的性能
一般来说,没有很好的方法来确定何时需要它,因此在Kotlin中,stdlib惰性是显式的,并提取到序列
接口中,以避免在默认情况下在所有Iterable
上使用它
- 相反,对于
Iterable
,具有中间操作语义的扩展函数工作得很快,立即处理项并返回另一个Iterable
。例如,返回一个包含映射结果的列表
Iterable的等效代码:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
如上所述,Iterable
在默认情况下是非惰性的,并且这个解决方案表现得很好:在大多数情况下,它具有良好的性能,因此利用了CPU缓存、预测、预取等。因此,即使是对集合的多次复制,仍然可以很好地工作,并且在具有小集合的简单情况下性能更好
如果您需要对求值管道进行更多的控制,可以使用函数显式转换为惰性序列
填写热键答案:
请注意序列和Iterable如何在元素中迭代,这一点很重要:
序列示例:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
日志结果:
过滤器-映射-每个;过滤器-映射-每个
举个例子:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
过滤器-过滤器-映射-映射-每个-每个
Iterable
映射到
JVM
,并由常用的集合实现,如列表或
设置将对这些上的集合扩展函数进行求值
急切地,这意味着他们都会立即处理
并返回一个包含结果的新集合
下面是一个使用集合函数获取
名单中年龄至少为21岁的前五个人的姓名:
val people: List<Person> = getPeople()
val allowedEntrance = people
.filter { it.age >= 21 }
.map { it.name }
.take(5)
目标平台:JVM在kotlin v上运行。1.3.61在这种情况下
序列中的每个人员实例都会检查其年龄(如果有)
他们通过,他们有自己的名字提取,然后添加到
结果清单。对原始列表中的每个人重复此操作
直到找到五个人。此时,toList函数
返回一个列表,序列中的其余人员不在列表中
已处理
序列还有一个额外的功能:它可以包含
无限多的项目。从这个角度来看,这是有道理的
运算符的工作方式是-无穷大上的运算符
如果序列号热切地工作,它就永远不会回来
作为一个例子,这里有一个序列,它将产生尽可能多的
2根据其终端运营商的要求(忽略以下事实:
很快就会溢出):
您可以找到更多信息。对于Java
(主要是Guava
)来说,这可能是一个很大的惊喜fans@VenkataRaju对于功能型的人来说,他们可能会惊讶于默认情况下懒惰的替代方案。默认情况下懒惰通常对较小和更常用的集合性能较差。如果利用CPU缓存等,拷贝速度可能比延迟评估快。所以对于常见的用例,最好不要懒惰。不幸的是,像map
、filter
等函数的通用约定除了源集合类型之外,没有足够的信息来决定其他类型,而且由于大多数集合也是可编辑的,这不是“懒惰”的好标记,因为它通常无处不在。lazy必须是显式的才能安全。@naki最近发布的Apache Spark公告中有一个例子,他们显然对此感到担忧,请参阅。。。但是他们担心数以十亿计的事情在迭代,所以他们需要走到极致。此外,延迟计算的一个常见陷阱是捕获上下文并将生成的延迟计算与所有捕获的局部变量以及它们所持有的任何内容一起存储在一个字段中。因此,很难调试内存泄漏。这是两者区别的一个很好的例子。这是一个很好的例子。
val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
.filter { it.age >= 21 }
.map { it.name }
.take(5)
.toList()
generateSequence(1) { n -> n * 2 }
.take(20)
.forEach(::println)