Kotlin 学习之被我一直用错的“return@forEachIndexed/return@forEach”

news/2024/5/20 3:31:42 标签: android, kotlin, 局部返回, return forEach, 内联函数

目录

  • 一、集合遍历
  • 二、样例问题场景
  • 三、原因
  • 四、如何实现 Kotlin forEach 与 forEachIndexed 循环中的 break 与 continue
  • 五、心得

一、集合遍历

1. Java 集合遍历方式

在 Java 中我们在遍历一个集合的时候常常使用的是以下两种方式:

for (int i = 0; i < list.size(); i++) {}
for (int item : list) {}

要中断循环或者是进入下一次循环使用的通常是 breakcontinue 关键字

2. Kotlin 集合遍历方式

在 Kotlin 中对于集合操作有很多很好用的集合函数,针对遍历用的比较多的有 forEachforEachIndexed 函数(存粹使用 for-in 其实也好用的)

kotlin">forEachIndexed { index, i -> }
forEach {it -> }

但是在第一次使用这两个集合函数的时候,却遇到了麻烦,想要条件满足后结束循环,却发现无法使用关键字 break,犯难的我就去搜索了一番,发现了 return@forEachIndexedreturn@forEach 这两个“跳出”循环的代码,于是就很开心的一直用到了写这篇博客的前夕,那如何进入下一次循环呢 continue 关键字当然也无法使用,使用 if-else 判断咯,就是这么天真

二、样例问题场景

目标 1: 遍历一个数组,仅仅输出遇到的第一个偶/奇数
目标 2: 遍历一个数组,仅仅输出偶/奇数

实现上很是简单,遍历数组,判断数字的奇偶性,中断循环或者进入下一次循环

大家先来看这段代码,想一想 testFun() 函数的执行结果

kotlin">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} 我是奇数")
        }
    }
}

期望结果:

kotlin">E/hzf: it==1 我是奇数
E/hzf: it==2 我是偶数

E/hzf: it==1 我是奇数
E/hzf: it==3 我是奇数

实际结果:

kotlin">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">@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

  1. continue 就不多说了,就是使用 return@forEach 或者 return@forEachIndexed
  2. break 方式一:使用 return,好处是简单,坏处是大多数时候我们只是想要结束循环,而非结束整个函数
  3. break 方式二:嵌套一层 Lambda 表达式,循环中使用 return@标签的方式实现局部返回到外层表达式来模拟 break 的效果
kotlin">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","我是最外面")
}
kotlin">E/hzf: it==1 我是奇数
E/hzf: it==2 我是偶数
E/hzf: 我是最外面
  1. 更多方式:https://code-examples.net/en/q/2109bb4

五、心得

再小再基础的内容也不能够一眼了事,要认真学习,仔细思考而后慎重对待,不然可能会在关键时候坑你一把,好在项目还没有上线,哈哈


整理学习自互联网资料与《第一行代码》第 3 版


http://www.niftyadmin.cn/n/1252340.html

相关文章

面 5 家挂 5 家,现在找工作这么难了吗?

今年&#xff0c;由于疫情的影响&#xff0c;很多互联网企业都在缩减招聘成本。作为前端工程师&#xff0c;市场上的人才本来就很多&#xff0c;而现在的求职局面又完全是企业在挑人的状态。小丽原本一心想进大厂&#xff0c;但是在这样的情况下&#xff0c;她也有些力不从心了…

js的内存泄漏与垃圾回收机制

垃圾回收机制 浏览器的js具有自动垃圾回收机制&#xff08;GC&#xff09;&#xff0c;执行环境会管理代码在执行中所使用的内存&#xff0c;垃圾回收器会定期寻找不再使用的变量&#xff0c;释放其内存&#xff0c;垃圾回收器会按照时间间隔周期性的执行 变量的死亡 全局作…

Gradle 学习之如何配置依赖

在提倡分享与开源的时代&#xff0c;各式各样的“工具”层出不穷&#xff0c;免去了很多重头再来的冗余工作&#xff0c;我们只要利用好合适的“工具”就可以快速&#xff0c;便捷的实现目标功能&#xff0c;那么 Gradle 是如何帮助找到这些“工具”的呢&#xff1f;重点其实就…

是什么尤大选择放弃Webpack?——vite 原理解析

本文同步在掘金博主:「橙红年代」个人博客shymean.com上。掘金原文链接: https://juejin.im/post/5ea2361de51d454714428b44前些天尤大在Vue 3.0 beta直播中提到了一个vite的工具&#xff0c;其描述是&#xff1a;针对Vue单页面组件的无打包开发服务器&#xff0c;可以直接在浏…

Android 学习之多状态布局的一种实现方案

开发应用的过程中&#xff0c;首页的控件越来越多&#xff0c;布局文件的代码已经到了爆表的程度&#xff0c;而且不同状态下首页各个控件的 Visibility 不同&#xff0c;每次新增状态都是一件头疼的事情&#xff0c;时常遗漏控件导致出错&#xff0c;和 YYY 大佬交流讨论后他给…

HTTP/3 原理与实践

2015 年 HTTP/2 标准发表后&#xff0c;大多数主流浏览器也于当年年底支持该标准。此后&#xff0c;凭借着多路复用、头部压缩、服务器推送等优势&#xff0c;HTTP/2 得到了越来越多开发者的青睐。不知不觉的 HTTP 已经发展到了第三代&#xff0c;鹅厂也紧跟技术潮流&#xff0…

这 2 个坏习惯,让你离大厂越来越远

上周见了个老朋友&#xff0c;老吴&#xff0c;也是做前端的&#xff0c;今年刚被提拔到部门Leader&#xff0c;饭桌上他说他们公司在缩减人员&#xff0c;纠结了几周&#xff0c;最后裁了一个跟了他 3 年的人。这人我也认识&#xff0c;工作上没啥大问题&#xff0c;挺踏实一人…

九个超级实用的 ES6 特性

这篇文章介绍了一些特性&#xff0c;在我看来&#xff0c;一个严肃的 JavaScript 开发者每天都多多少少会用到这些特性。1、展开操作符顾名思义&#xff0c;用于对象或数组之前的展开操作符&#xff08;…&#xff09;&#xff0c;将一个结构展开为列表。演示一下&#xff1a;l…