我写了以下代码:
val src = (0 until 1000000).toList()
val dest = ArrayList<Double>(src.size / 2 + 1)
for (i in src)
{
if (i % 2 == 0) dest.add(Math.sqrt(i.toDouble()))
}
IntellJ(在我的案例中是AndroidStudio)问我是否要用stdlib中的操作替换for循环。这导致以下代码:
val src = (0 until 1000000).toList()
val dest = ArrayList<Double>(src.size / 2 + 1)
src.filter { it % 2 == 0 }
.mapTo(dest) { Math.sqrt(it.toDouble()) }
现在我必须说,我喜欢改变的代码。当我提出类似的情况时,我发现写入比循环更容易。但是在阅读filter
函数的功能后,我意识到与for循环相比,这是一个很慢的代码。 filter
函数创建一个新列表,其中仅包含src中与谓词匹配的元素。因此,在stdlib版本的代码中创建了另外一个列表和一个循环。对于小型列表来说,它可能并不重要,但总的来说这听起来不是一个好的选择。特别是如果要连接更多这样的方法,你可以通过编写for循环来获得许多可以避免的额外循环。
我的问题是什么被认为是Kotlin的良好做法。我应该坚持使用循环还是我错过了某些东西而且它不起作用,因为我觉得它有效。
答案 0 :(得分:14)
如果您担心表现,您需要的是Sequence
。例如,您的上述代码将是
val src = (0 until 1000000).toList()
val dest = ArrayList<Double>(src.size / 2 + 1)
src.asSequence()
.filter { it % 2 == 0 }
.mapTo(dest) { Math.sqrt(it.toDouble()) }
在上面的代码中,filter
返回另一个Sequence
,表示中间步骤。什么都没有真正创建,没有对象或数组创建(除了新的Sequence
包装器)。只有在调用终端运算符mapTo
时才会创建生成的集合。
如果你已经学过java 8流,你可能会发现上面的解释有些熟悉。实际上,Sequence
大致相当于java 8 Stream的kotlin等价物。它们具有相似的目的和性能特征。唯一的区别是Sequence
并非设计为与ForkJoinPool
一起使用,因此更容易实现。
当涉及多个步骤或收集可能很大时,建议使用Sequence
代替普通.filter {...}.mapTo{...}
。我还建议您使用Sequence
表单而不是命令式表单,因为它更容易理解。当数据处理涉及5个或更多步骤时,命令形式可能变得复杂,因此难以理解。如果只有一步,您就不需要Sequence
,因为它只会产生垃圾,并且没有任何用处。
答案 1 :(得分:3)
你错过了什么。 : - )
在这种特殊情况下,您可以使用IntProgression
:
0 0 false
1 0 true
0 1 true
1 1 true
然后,您可以通过各种方式创建所需的方块列表:
val progression = 0 until 1_000_000 step 2
答案 2 :(得分:1)
我的建议是选择您喜欢的编码风格。 Kotlin既是面向对象的又是功能语言,意味着你的两个命题都是正确的。
通常,功能结构有利于可读性而不是性能;但是,在某些情况下,程序代码也会更具可读性。您应该尽可能地坚持使用一种风格,但如果您觉得它更适合您的约束(可读性,性能或两者兼而有之),请不要害怕切换一些代码。
答案 3 :(得分:0)
转换后的代码不需要手动创建目的地列表,可以简化为:
val src = (0 until 1000000).toList()
val dest = src.filter { it % 2 == 0 }
.map { Math.sqrt(it.toDouble()) }
正如@ glee8e在优秀答案中所提到的,你可以使用一个序列来做一个懒惰的评估。使用序列的简化代码:
val src = (0 until 1000000).toList()
val dest = src.asSequence() // change to lazy
.filter { it % 2 == 0 }
.map { Math.sqrt(it.toDouble()) }
.toList() // create the final list
请注意,最后添加toList()
是从序列更改回最终列表,这是处理过程中制作的一个副本。您可以省略该步骤以保留为序列。
重要的是要强调@hotkey的评论说你不应该总是假设另一个迭代或列表的副本导致比懒惰评估更差的性能。 @hotkey说:
有时有几个循环。即使他们复制了整个系列,也因为良好的参考位置而表现出良好的性能。请参阅:Kotlin's Iterable and Sequence look exactly same. Why are two types required?
摘自该链接:
...在大多数情况下,它具有良好的locality of reference,因此利用了CPU缓存,预测,预取等功能,因此即使对集合进行多次复制仍然可以很好地工作,并且在小集合的简单情况下表现更好
@ glee8e表示Kotlin序列与Java 8流之间存在相似之处,详细比较请参阅:What Java 8 Stream.collect equivalents are available in the standard Kotlin library?