Kotlin:循环通过下一个函数返回的有限值



背景信息

在一些编程语言中,一种常见的模式是有一个函数,当被调用时,它会返回下一个值,直到到达有限序列的末尾,在这种情况下,它会一直返回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(): ObjecthasNext(): 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断言。

最新更新