return@forEach 似乎没有退出 forEach{}

问题描述

下面代码末尾的 count 是 4。我期望是 0。为什么是 4?我怎样才能得到 0?

var count = 0;
"hello".forEach {
    if(it == 'h')
    {
        println("Exiting the forEach loop. Count is $count");
        return@forEach;
    }
    count++;
}
println("count is $count");

输出

Exiting the forEach loop. Count is 0
count is 4

解决方法

return@forEach 不会退出 forEach() 本身,而是传递给它的 lambda(forEach() 的“主体”)。请注意,此 lambda 会执行多次 - 每个项目一次。通过从它返回,你实际上只跳过了一个项目,所以这类似于 continue,而不是 break

要解决此问题,您可以在外部作用域中创建一个标签并返回:

var count = 0;
run loop@ {
    "hello".forEach {
        if(it == 'h')
        {
            println("Exiting the forEach loop. Count is $count");
            return@loop;
        }
        count++;
    }
}

此处描述:https://kotlinlang.org/docs/returns.html#return-at-labels

请注意,前三个示例中局部返回的使用类似于常规循环中 continue 的使用。 break 没有直接的等价物,但可以通过添加另一个嵌套 lambda 并从中非本地返回来模拟

,

它是 4,因为 forEach 调用传递给它的 lambda 对于字符串中的每个字符,因此代码中的 return@forEach 返回第一个元素。您可以使用 for 循环并使用 break 获得 0。

,

return@forEach 从 lambda 函数返回。但是 forEach 函数是一个高阶函数,它为迭代器中的每个项目重复调用 lambda。因此,当您从 lambda 返回时,您只会返回迭代器中的单个项目。这类似于在传统的 for 循环中使用 continue

如果要在高阶函数中完全退出迭代,则必须使用标签。当我输入这个时,我看到另一个答案已经显示了如何做到这一点,但如果不同的解释有帮助,我会留下这个。

,

如果您的目标是计算 'h' 之前的字符数,您可以执行以下操作:

val numCharsBeforeH = "hello".takeWhile { it != 'h' }.length

从您的评论到 Tenfour04 的回答:

这不是很方便。为什么 Kotlin 的开发者没有创造一个“break”等价物?

这里引用了 Coding conventions 的“循环”部分:

更喜欢使用高阶函数(filtermap 等)而不是循环。 例外:forEach(更喜欢使用常规的 for 循环,除非 forEach 的接收者可以为空或 forEach 被用作 更长的调用链)。

在使用多个的复杂表达式之间做出选择时 高阶函数和循环,了解成本 在每种情况下正在执行的操作并保持性能 考虑因素。

确实,使用带有 for 的常规 break 循环可以满足您的预期:

var count = 0;
for (char in "hello") {
    if (char == 'h') {
        println("Breaking the loop. Count is $count")
        break
    }
    count++
}
println("count is $count")

输出:

Breaking the loop. Count is 0
count is 0

除了非常简单的操作外,可能有比使用 forEach 更好的方法来完成您需要的操作。