背景信息
在一些编程语言中,一种常见的模式是有一个函数,当被调用时,它会返回下一个值,直到到达有限序列的末尾,在这种情况下,它会一直返回null。
Java中的一个常见示例是:
void printAll(BufferedReader reader) {
String line;
// Assigns readLine value to line, and then check if not null
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
它类似于迭代器设计模式中的iterator
,但迭代器有next(): Object
和hasNext(): Boolean
,而BufferedReader没有hasNext()
检查功能,只有形式next(): Object?
,其中返回的对象可以为null以标记序列的结束。我将诸如CCD_下一个函数";(或者">yield"函数(,但我不知道是否有一个词可以用来形容这种模式。
在Java中,表达式可以包含赋值,这允许构造如:(line = reader.readLine()) != null
。此代码将readLine()
的可空值分配给line
,然后检查line
中的值是否为空。但是Kotlin不允许这样的构造,因为在Kotlin中,赋值不是表达式,所以它不能用作Kotlin的循环条件。
问题
Kotlin中循环通过下一个函数(如readLine()
(返回的有限数量的值的可能模式是什么?(下一个函数也可以在ZipInputStream中找到,例如转到下一个zip条目。(
我并不是简单地为这个问题寻找一个Kotlin解决方案,因为我可以自己编程而没有问题。我希望探索可能的模式,以便人们能够选择一种适合他们需求的模式。
我自己也发现了一些模式,我会在这里发布作为答案,但可能还有更多的模式,这将是一个有趣的了解。
我已经按照(我认为的(最佳解决方案的降序对解决方案进行了排序。
解决方案1:使用内置generateSequence
(推荐(
我刚刚发现Kotlin有一个内置的独立generateSequence()
函数(位于kotlin.sequences
包中(。
generateSequence { br.readLine() }
.forEach { line ->
println("Line: $line")
}
generateSequence
接受您可以提供的代码块,该代码块必须生成一个值。在这种情况下,br.readLine()
是代码块,并且生成String,或者在到达末尾时生成null。generateSequence
生成一个序列,当从该序列请求下一个值时,该序列在内部调用readLine()
,直到readLine()
返回null,从而终止该序列。因此,Kotlin中的序列是懒惰的:它们既不读取也不提前知道所有值,例如,当forEach
处理一行时,只调用一个readLine()
。这种懒惰通常正是你想要的,因为它可以节省内存并最大限度地减少初始延迟。要将其急切地更改为,可以将generateSequence { br.readLine() }
附加为.toList()
。
- 优点1:没有额外的变量
- Pros 2:只有一个构建体(
generateSequence
( - Pros3:返回一个
Sequence
,因此您可以链接其他方法,如filter()
- 优点4:任何可为null的迹象都被抽象掉了。(没有
null
关键字,也没有?
或!
运算符。( - 优点5:坚持函数式编程风格
IMO,这是迄今为止我见过的最干净的解决方案。
解决方案2:使用elvis break调用while true循环
while (true) {
val line = br.readLine() ?: break
println("Line: $line")
}
- 优点:没有其他变量
- 缺点:有些人不喜欢while true循环和break语句
解决方案3:使用安全呼叫also
同时执行
do {
val line = br.readLine()?.also { line ->
println("Line: $line")
}
} while (line != null)
- 优点:没有其他变量
- 缺点:可读性不如其他解决方案
解决方案4:每次迭代开始前和结束时的next
对于刚接触Kotlin的Java程序员来说,这可能是最常见的解决方案。
var line = br.readLine()
while (line != null) {
println("Line: $line")
line = br.readLine()
}
- 缺点1:重复的下一个(
readLine
(调用和重复的分配 - 缺点2:可重新分配变量
解决方案5:while循环使用also
进行赋值
这是IntelliJ在将Java转换为Kotlin代码时生成的解决方案:
var line: String?
while (br.readLine().also { line = it } != null) {
println("Line: $line")
}
Cons:line被声明为可以为null,即使它在循环中永远不能为null。因此,如果您想访问line
的成员,您通常必须使用非null断言运算符,您可以使用将其限制为一个断言
var nullableLine: String?
while (br.readLine().also { nullableLine = it } != null) {
val line = nullableLine!!
println("Line: $line")
}
- Cons 1:需要非null断言,即使它在循环中永远不能为null
- 缺点2:可重新分配变量
- 缺点3:可读性比其他解决方案差
请注意,如果将var line: String?
更改为var line: String
,代码仍然可以编译,但当line
变为null时,它将抛出一个NPE,即使没有使用非null断言。