这个问题主要体现在forEach()repeat()函数中

通常,我们会如下方式调用这两个函数:

(1..100).forEach { println(it) }

但是如果我想像通常循环一样,在特定点跳出forEach()呢?

对于条件明确的,我们可以在forEach()函数前明确范围,比如:

(1..100).drop(5).take(50).forEach { println(it) }

但是如果条件不明确,需要在循环中判断,这种方法就不行了

break呢?

直接使用break肯定是不行的,因为这在一个lambda表达式内部

此时,你可能会说,既然是lambda内,我用return不就完了吗?

但是如果你这么写:

(1..100).forEach {
    println(it)
    if (it > 50) {
        return@forEach
    }
}

那你就掉坑里了,看似这是让函数返回到forEach()执行,实际上,这是一个局部返回

你只是返回到了forEach()函数的block而已,并没有离开forEach()函数!

这么写可能更直观:

(1..100).forEach loop@{
    println(it)
    if (it > 50) {
        return@loop
    }
}

我们此时根本没有实现break的效果,相反,实现的是continue的效果

如果想要break,就需要使用到非局部返回,查看kotlin中forEach()函数的源代码可以发现

forEach()函数的block并未被标记为crossinlinenoinline,所以是可以使用非局部返回的:

fun nonLocal() {
    (1..100).forEach {
        println(it)
        if (it > 50) {
            return@nonLocal
        }
    }
}

这样我们就实现了break效果,成功从forEach()函数离开了

你可能会觉得这也太不方便了,难道我用到forEach()的地方想要break,就要套一层函数吗?

其实不然,kotlin中,我们有可以随处运行的run()函数

当你需要break一个forEach的时候,只要用run函数包裹就可以了:

run {
    (1..100).forEach {
        println(it)
        if (it > 50) {
            return@run
        }
    }
}

// other logic

其实不仅仅是repeat()forEach(),所有传入lambda的函数,都是如此工作的

一般的函数中因为不存在循环,所以我们直接用局部返回,回到调用函数后,它完成其他工作自然就退出了

但是这类存在循环的函数中,如果对局部返回和非局部返回的理解不到位
就会发生 “我明明return了,为什么还是执行了多次?” 这样的问题