Kotlin:循环遍历下一个函数返回的有限值

Dev*_*abc 3 iteration iterator while-loop bufferedreader 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它与Iterator 设计模式iterator中的 类似,但迭代器有 a和 a ,而BufferedReader没有检查功能,只有 形式,其中返回的对象可以为 null 以标记序列的结束。我调用诸如“下一个函数”(或者可能是“ yield ”函数)之类的函数,但我不知道是否有一个词来形容这种模式。next(): ObjecthasNext(): BooleanhasNext()next(): Object?next()

在 Java 中,表达式可以包含赋值,这允许如下构造:(line = reader.readLine()) != null。此代码将可为空的值赋给readLine()to line,然后检查 in 的值是否line不为空。但 Kotlin 不允许这样的构造,因为在 Kotlin 中,赋值不是表达式,因此它不能用作 Kotlin 中的循环条件。

问题

Kotlin 中可能有哪些模式来循环 next 函数返回的有限数量的值,例如readLine()?(例如,还可以在ZipInputStream中找到 Next 函数,以转到下一个 zip 条目。)

我不仅仅是在寻找解决这个问题的 Kotlin 解决方法,因为我可以自己编程,不会出现任何问题。我正在探索可能的模式,以便人们可以选择适合自己需求的模式。

我自己发现了一些模式,我将把它们作为答案发布在这里,但可能还有更多模式,了解这些模式会很有趣。

Dev*_*abc 5

我已按(我认为)最佳解决方案按降序排列解决方案。

解决方案1:使用内置generateSequence(推荐)

我刚刚发现 Kotlin 有一个内置的独立generateSequence()函数(位于kotlin.sequences包中)。

generateSequence { br.readLine() }
    .forEach { line ->
        println("Line: $line")
    }
Run Code Online (Sandbox Code Playgroud)

generateSequence接受您可以提供的代码块,该代码块必须生成一个值。在本例中,br.readLine()是代码块,并且生成一个字符串,或者如果到达末尾则生成 null。generateSequence生成一个序列,readLine()当从序列中请求下一个值时,该序列会在内部调用,直到readLine()返回 null,这会终止序列。因此 Kotlin 中的序列是惰性的:它们不会提前读取也不知道所有值,readLine()例如在forEach处理单行时仅调用一个值。这种惰性通常正是您想要的,因为它可以节省内存并最大限度地减少初始延迟。要将其更改为 eagerly,您可以generateSequence { br.readLine() }附加.toList().

  • 优点1:没有额外的变量。
  • 优点 2:只有一个构造 ( generateSequence)。
  • 优点 3:返回 a Sequence,因此您可以链接其他方法,例如filter().
  • 优点 4:任何可空性的迹象都会被抽象掉。(没有null关键字,也?没有!运算符。)
  • 优点 5:坚持函数式编程风格。

IMO,这是迄今为止我见过的最干净的解决方案。

解决方案 2:使用 elvis break 调用的 while true 循环

while (true) {
    val line = br.readLine() ?: break
    println("Line: $line")
}
Run Code Online (Sandbox Code Playgroud)
  • 优点:没有额外的变量。
  • 缺点:有些人不喜欢 while-true 循环和 break 语句。

解决方案 3:使用安全调用 do-whilealso

do {
    val line = br.readLine()?.also { line ->
        println("Line: $line")
    }
} while (line != null)
Run Code Online (Sandbox Code Playgroud)
  • 优点:没有额外的变量。
  • 缺点:比其他解决方案可读性较差。

解决方案 4:next每次迭代开始前和结束时

对于刚刚接触 Kotlin 的 Java 程序员来说,这可能是最常见的解决方案。

var line = br.readLine()
while (line != null) {
    println("Line: $line")
    line = br.readLine()
}
Run Code Online (Sandbox Code Playgroud)
  • 缺点 1:重复的 next( readLine) 调用和重复的赋值。
  • 缺点 2:可重新分配变量。

解决方案 5:使用 while 循环进行赋值also

这是 IntelliJ 将 Java 转换为 Kotlin 代码时生成的解决方案:

var line: String?
while (br.readLine().also { line = it } != null) {
    println("Line: $line")
}
Run Code Online (Sandbox Code Playgroud)

缺点: line 被声明为可为空,即使它在循环内永远不能为空。因此,如果您想访问 的成员,则通常必须使用非空断言运算符line,您可以使用以下方法将其限制为一个断言:

var nullableLine: String?
while (br.readLine().also { nullableLine = it } != null) {
    val line = nullableLine!!
    println("Line: $line")
}
Run Code Online (Sandbox Code Playgroud)
  • 缺点 1:需要非空断言,即使它在循环内永远不能为空。
  • 缺点 2:可重新分配变量。
  • 缺点 3:比其他解决方案可读性较差。

请注意,如果更改var line: String?var line: String,代码仍然可以编译,但当line变为 null 时,即使没有使用非空断言,它也会抛出 NPE。