Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”
Posted Nicholas_hzf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”相关的知识,希望对你有一定的参考价值。
目录
一、集合遍历
1. Java 集合遍历方式
在 Java 中我们在遍历一个集合的时候常常使用的是以下两种方式:
for (int i = 0; i < list.size(); i++)
for (int item : list)
要中断循环或者是进入下一次循环使用的通常是 break
和 continue
关键字
2. Kotlin 集合遍历方式
在 Kotlin 中对于集合操作有很多很好用的集合函数,针对遍历用的比较多的有 forEach
与 forEachIndexed
函数(存粹使用 for-in
其实也好用的)
forEachIndexed index, i ->
forEach it ->
但是在第一次使用这两个集合函数的时候,却遇到了麻烦,想要条件满足后结束循环,却发现无法使用关键字 break
,犯难的我就去搜索了一番,发现了 return@forEachIndexed
和 return@forEach
这两个“跳出”循环的代码,于是就很开心的一直用到了写这篇博客的前夕,那如何进入下一次循环呢 continue
关键字当然也无法使用,使用 if-else
判断咯,就是这么天真
二、样例问题场景
目标 1: 遍历一个数组,仅仅输出遇到的第一个偶/奇数
目标 2: 遍历一个数组,仅仅输出偶/奇数
实现上很是简单,遍历数组,判断数字的奇偶性,中断循环或者进入下一次循环
大家先来看这段代码,想一想 testFun()
函数的执行结果
private fun testFun()
(1..4).forEach
if (it % 2 == 0)
Log.e("hzf","it==$it 我是偶数")
return@forEach
Log.e("hzf","it==$it 我是奇数")
(1..4).forEach
if (it % 2 != 0)
Log.e("hzf","it==$it 我是奇数")
期望结果:
E/hzf: it==1 我是奇数
E/hzf: it==2 我是偶数
E/hzf: it==1 我是奇数
E/hzf: it==3 我是奇数
实际结果:
E/hzf: it==1 我是奇数
E/hzf: it==2 我是偶数
E/hzf: it==3 我是奇数
E/hzf: it==4 我是偶数
E/hzf: it==1 我是奇数
E/hzf: it==3 我是奇数
不知道大家注意到没有,return@forEach
并没有发挥出它本来或者说是我认为的中断循环的作用,而是进入了下一次循环也就是起到了 continue
的作用!要不是同事无意间提起,我可能就在错误的道路上越走越远了。
三、原因
要想知道原因,其实还是要从代码本身入手去看(最简单的当然是百度或者看官网对于语法的解释啦),以 forEach
为例去看,这是一个 内联的 Iterable<T>
的扩展高阶函数,参数是函数类型的
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit
for (element in this) action(element)
private fun testFun()
(1..4).forEach
if (it % 2 == 0)
Log.e("hzf","it==$it 我是偶数")
return@forEach
Log.e("hzf","it==$it 我是奇数")
扩展,内联,高阶函数以及函数类型?@forEach
标签?return@标签
的作用?
由于我们这里不是 Kotlin 语法课堂,我们这里对这些概念只是做一下简单理解,然后串起来看就可以很好的明白弄错的原因了
- 扩展函数: 不修改某个类的源码的情况下,向该类添加新的函数
- 内联函数: 函数代码在编译的时候会替换到调用它的地方,可以消除 Lambda 表达式带来的运行时开销(这个开销就是 Lambda 表达式转成的 接口匿名类实现,减少开销就是减少匿名类实例创建带来的额外的内存和性能开销)
- 高阶函数: 如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数
- 函数类型: 如同函数一般的类型,基本规则是:(参数表) -> 返回值
- return 的作用: 默认从最直接包围它的函数或者匿名函数返回,也就是局部返回,但是内联函数是可以使用
return
来进行函数返回的,非内联函数只能进行局部返回 - 标签: 在 Kotlin 中任何表达式都可以用标签来标记,标签可以限制
return
返回的范围,指定break
打破的目标循环或者指定continue
进入下一次循环的目标循环 - 隐式标签: Lambda 表达式的隐式标签标签与接受该表达式的函数同名
把这些串起来看:
- 在非内联的高阶函数中,Lambda 表达式在底层会被转换成匿名类的实现方式,那么使用
return
就会也只会发生局部返回,结束的是匿名类的方法 - 而在内联的
forEach
函数中可以使用return
或者return@forEach
来进行返回,前者是函数返回,直接将testFun()
这个函数结束,后者是局部返回,结束的是 Lambda 表达式,而该表达式实际是存在于for-in
循环当中的,也就是结束一次循环直接进入到下一次循环,即起到关键字continue
的作用
四、如何实现 Kotlin forEach 与 forEachIndexed 循环中的 break 与 continue
continue
就不多说了,就是使用return@forEach
或者return@forEachIndexed
break
方式一:使用 return,好处是简单,坏处是大多数时候我们只是想要结束循环,而非结束整个函数break
方式二:嵌套一层 Lambda 表达式,循环中使用return@标签
的方式实现非局部返回到外层表达式来模拟break
的效果
private fun testFun()
run out@
(1..4).forEach forEach@
if (it % 2 == 0)
Log.e("hzf","it==$it 我是偶数")
return@out
Log.e("hzf","it==$it 我是奇数")
Log.e("hzf","我是 run 里面")
Log.e("hzf","我是最外面")
E/hzf: it==1 我是奇数
E/hzf: it==2 我是偶数
E/hzf: 我是最外面
五、心得
再小再基础的内容也不能够一眼了事,要认真学习,仔细思考而后慎重对待,不然可能会在关键时候坑你一把,好在项目还没有上线,哈哈
整理学习自互联网资料与《第一行代码》第 3 版
以上是关于Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”的主要内容,如果未能解决你的问题,请参考以下文章