本文来自 Kotlin 中文博客:Kotliner

问题背景

话说周六下午团建回来,看到群里的小伙伴们在纠结一个问题,如何退出 forEach 循环,或者说有没有终止 forEach 循环的方法,就像 break 那样。我们来看个例子:

val list = listOf(1,3,4,5,6,7,9)

for(e in list){

if(e > 3) break

println(e)

}复制代码

如果 e 大于 3,那么循环终止,这是传统的写法,那么问题来了,我们现在追求更现代化的写法,要把代码改成 forEach 的函数调用,那么我们会怎么写呢?

list.forEach {

if(it > 3) ???

println(it)

}复制代码

就像上面这样吗?感觉应该是这样,不过大于 3 的时候,究竟该怎么办才能退出这个循环呢?

return 还是 return@forEach ?

作为基本上等价于匿名函数的 Lambda 表达式,我们可能希望它能够提前返回,这样是不是就相当于终止循环了呢?

fun main(args: Array) {

val list = listOf(1,3,4,5,6,7,9)

list.forEach {

if(it > 3) return

println(it)

}

}复制代码

这时候我们毫不犹豫的写下了这样的代码,大于 3 的时候,直接 return,结果呢?运行到 4 的时候,forEach 就真的被终止了,后面也就没有了输出。

嗯,这样是不是就算把问题解决啦?想想也不可能呀,不然我这周的文章岂不是太坑人了?

fun main(args: Array) {

val list = listOf(1,3,4,5,6,7,9)

list.forEach {

if(it > 3) return

println(it)

}

println("Hello")

}复制代码

当我们把代码改成这样的时候,我们运行时发现只输出 1 3,后面的 Hello 则是无法打印的。原因呢,当然也很简单,在 return 眼里,Lambda 表达式都不算事儿,所以我们在大于 3 时的 return,实际上是返回了 main 函数,于是 list.forEach 这个结构之后的代码就不能被执行了。

好吧,那这里用 return 肯定是有问题的,我们不用它了行了吧。那不用 return 用什么呢?好在 Kotlin 为我们提供了标签式的返回方法,也就是说,如果你想从一个 Lambda 表达式当中显式地返回,那么你只需要写 return@xxx 即可,例如:

fun main(args: Array) {

val list = listOf(1,3,4,5,6,7,9)

list.forEach {

if(it > 3) return@forEach

println(it)

}

println("Hello")

}复制代码

你也可以给这个 Lambda 表达式起个新标签名称,比如 block:

fun main(args: Array) {

val list = listOf(1,3,4,5,6,7,9)

list.forEach block@{

if(it > 3) return@block

println(it)

}

println("Hello")

}复制代码

这样,我们的程序运行结果就是:

1

3

Hello复制代码

这一步大家都会想到的,不过这并不是最终的解。

调用还是循环?

我来问大家一个问题,前面的 forEach 后面传入的 Lambda 表达式体是循环体吗?

当然不是。那其实就是一个函数体,因此对这个函数体的退出只能退出当前的调用。为了说明这个问题,我们还是需要对原有的例子做下小修改:

fun main(args: Array) {

val list = listOf(1,3,4,5,6,7,9)

list.forEach block@{

println("it=$it")

if(it > 3) return@block

println(it)

}

println("Hello")

}复制代码

结果呢?

it=1

1

it=3

3

it=4

it=5

it=6

it=7

it=9

Hello复制代码

好家伙,尽管我们在大于 3 的时候 return@block,但看上去仍然没有什么软用,显然,后面的循环仍然执行了。

简单总结一下,在 Lambda 表达式中,return 返回的是所在函数,return@xxx 返回的是 xxx 标签对应的代码块。由于 forEach 后面的这个 Lambda 实际上被调用了多次,因此我们没有办法像 for 循环那样直接 break 。

额。。这可如何是好?

流式数据处理

实际上我们在 Kotlin 当中用到的 forEach、map、flatMap 等等这样的高阶函数调用,都是流式数据处理的典型例子,我们也看到不甘落后却又跟不上节奏的 Java 在 8.0 推出了 stream Api,其实也无非就是为流式数据处理提供了方便。

采用流式 api 处理数据,我们就不能再像以前那样思考问题啦,以前的思维方式多单薄呀,只要是遍历,那就是 for 循环,只要是条件那就是 if...else,殊不知世界在变,api 也在变,你不跟着变,那你就请便啦。

那么,回到我们最开始的问题,需求其实很明确,遇到某一个大于 3 的数,我们就终止遍历,这样的代码用流式 api 写出来应该是这样的:

val list = listOf(1,3,4,5,6,7,9)

list.takeWhile { it <= 3 }.forEach(::println)

println("Hello")复制代码

我们首先通过 takeWhile 来筛选出前面连续不大于 3 的元素,也就是说一旦遇到一个大于 3 的元素我们就丢弃从这个开始所有后面的元素;接着,我们把取到的这些不大于 3 的元素通过 forEach 打印出来,这样的话,程序的效果与文章最开头的 for 循环 break 的实现就完全一致了。

val list = listOf(1,3,4,5,6,7,9)

for(e in list){

if(e > 3) break

println(e)

}复制代码

当然,你可能会说如果我想要打印其中的偶数,那我该怎么写呢?这时候我告诉大家,如果你写出了下面这样的代码,那么我只能告诉你,。。额,我刚想说啥来着??

list.forEach {

if(it % 2 == 0){

println(it)

}

}复制代码

上面这样写的代码呢,让我想起了辫帅张勋:张将军,你知不知道,咱大清已经亡了呢?

list.filter { it % 2 == 0 }.forEach(::println)复制代码

哈哈,如果真的希望使用流式 api,那么上面这样的写法才算是符合风格的写法。当然了,如果你愿意,你还可以定义一个 isEven 的方法,代码写出来就像下面这样:

fun Int.isEven() = this % 2 == 0

fun main(args: Array) {

val list = listOf(1,3,4,5,6,7,9)

list.filter(Int::isEven).forEach(::println)

}复制代码

性能

前不久看到有一篇文章对 Java 8 的流式 api 做了评测,说流式 api 的执行效率比传统的 for-loop 差出一倍甚至更多,所以建议大家慎重考虑选择。

其实对于这个东西我认为我们没必要把神经绷这么紧。原因也很简单呀,流式 api 的执行效率从实现上来讲,确实很难达到纯 for-loop 那样的高效,例如我们前面的:

list.filter(Int::isEvent).forEach(::println)复制代码

在 filter 的时候就调用了一次完整的 for-loop,而后面的 forEach 同样再来一遍,也就是说我们用传统的 for-loop 一遍搞定的事儿,用流式 api 写了两遍,如果条件比较复杂,出现两遍三遍的情况也是比较正常的。

不过这并不能说明流式 api 就一定要慎重使用。流式 api 更适用于数据的流式处理,特别是涉及到较多 UI 交互的场景,这样的业务逻辑用流式 api 表达起来会非常的简洁直观,也易于维护,相应的,这样的场景对于性能的要求并没有到吹毛求疵的地步;而对于性能比较敏感的程序,通常来说也没有很复杂的业务逻辑,流式 api 在这里也难以发挥作用。

另外,仅仅多个几次循环也并不会改变算法本身的运算效率的数量级,所以对于适用于流式 api 的场景,大家还是可以放心去使用的。

--

如果你有兴趣加入我们,请直接关注公众号 Kotlin ,或者加 QQ 群:162452394 联系我们。

java foreach 中止_如何正确终止 forEach相关推荐

  1. java核心面试_不正确的核心Java面试答案

    java核心面试 总览 在Internet上,Java面试问题和答案从一个网站复制到另一个网站. 这可能意味着错误或过时的答案可能永远不会得到纠正. 这是一些不太正确或已经过时的问题和答案. 即是Ja ...

  2. java exec 关闭_如何正确关闭java ExecutorService

    从ExecutorService的 Oracle API文档页面推荐的方法:void shutdownAndAwaitTermination(ExecutorService pool) { pool. ...

  3. js中 如何终止foreach循环?

    forEach专门用来循环数组,可以直接取到元素,同时也可以取到index值 存在局限性,不能continue跳过或者break终止循环,没有返回值,不能return 终止foreach循环 :运用抛 ...

  4. java foreach order_Java 8流中的forEach vs forEachOrdered

    我知道这些方法的执行顺序不同,但在所有测试中,我都无法实现不同的执行顺序. 例子: System.out.println("forEach Demo"); Stream.of(&q ...

  5. java for循环迭代_JAVA中的for-each循环与迭代

    在学习java中的collection时注意到,collection层次的根接口Collection实现了Iterable接口(位于java.lang包中),实现这个接口允许对象成为 "fo ...

  6. Java并发 正确终止与恢复线程

    为什么80%的码农都做不了架构师?>>>    前面提到了stop().suspend()等方法在终止与恢复线程的弊端,那么问题来了,应该如何正确终止与恢复线程呢?这里可以使用两种方 ...

  7. 不允许使用java方式启动_细品 Java 中启动线程的正确和错误方式

    细品 Java 中启动线程的正确和错误方式 前文回顾详细分析 Java 中实现多线程的方法有几种?(从本质上出发) start 方法和 run 方法的比较 代码演示:/** * * start() 和 ...

  8. 如何终止forEach循环

    如何终止forEach循环 由于forEach无法在所有元素都传递给调用函数之前终止遍历,所以说它没有像for循环中使用的break语句,如果要提前终止的话就必须把forEach方法放在一个try/c ...

  9. js中终止forEach循环的方法

    正常终止for循环我们可以使用break关键字来实现,而在forEach循环中是不能使用break和continue这两个关键字的,为什么呢? 因为这两个关键字要在循环中使用,而forEach中所执行 ...

  10. Kotlin -正确退出forEach

    最近在代码评审的时候,发现看到同事使用 kotlin 的forEach,他以为使用 return@forEach 就可以退出了,相当于 break,但其实并不是,只是相当于 for 的 continu ...

最新文章

  1. 【MATLAB】矩阵分析之向量和矩阵的范数运算
  2. tkinter实现文件加密和解密
  3. 计算机发展初期 承载信息的媒体,《多媒体技术与应用》(本)阶段练习一
  4. 初学PX4之飞控算法
  5. audiorecord怎么释放_Android 开发 AudioRecord音频录制
  6. 443. 压缩字符串
  7. AliOS Things自组织网络安全认证架构概述
  8. Bootstrap 表单控件的状态
  9. 数据库MySQL安装
  10. wordpress中文教程
  11. 转载: WMS、WFS、WCS、WPS、WMTS、WMSC、TMS等常见地图服务的区别
  12. 推荐iOS模拟器截图工具iOS-Simulator Cropper
  13. Atitit.编程语言的基础句型and汉语英文比较
  14. 智能手机的硬件组成部分及结构图
  15. cad批量打印快捷键_CAD布局批量打印必备工具之一
  16. 基于虚拟仿真技术的数字化工厂管理系统
  17. 【转载】C#中List集合First和FirstOrDefault方法有何不同
  18. 多边形标注收缩python代码实现
  19. 微博数据解析:国产彩妆品牌对比 | 完美日记 VS 花西子
  20. 转换工具推荐:如何将PDF文档转换为PPT演示文稿

热门文章

  1. RabbitMq 406错误
  2. 使用GPG加密通讯,设置git提交验证密钥
  3. labview中DAQ采集多个数据通道(温度传感器)(TTL信号的曲轴位置传感器)总结
  4. canvas 填充圆内正方形
  5. linux开pulseaudio服务,PulseAudio
  6. LFS详细搭建指南——适合初学者使用
  7. 你努力工作会让老板感动吗?
  8. Javascript搭建selenium测试环境
  9. 米家扫地机器人按键没反应_好到没理由不推荐 米家扫地机器人评测
  10. php亲戚称谓计算源码,亲戚称呼(亲戚关系计算器在线)