Kotlin 中的 Lambda 与 Inline

在这里插入图片描述

在Kotlin中,有很多很酷很实用的特性,比如Lambda和高阶函数,利用这些特性,我们可以更加快速的实现开发,提升效率。

比如我们实现一个捕获Throwable,安全执行部分代码的高阶函数

fun safeRun(runnable: () -> Unit) {try {runnable()} catch (t: Throwable) {t.printStackTrace()}
}fun testNormalSafeRun() {safeRun {System.out.println("testNormalSafeRun")}
}

由于Kotlin默认是面向JDK 6,而Java 8 才引入Lambda表达式支持,Kotlin实际上是将Lambda翻译成了匿名内部类的实现形式。所以当我们反编译上面的代码,得到的如下的代码

Lambda被翻译成的class文件

static final class InlineSampleKt.testNormalSafeRun.1
extends Lambda
implements Function0<Unit> {public static final InlineSampleKt.testNormalSafeRun.1 INSTANCE = new /* invalid duplicate definition of identical inner class */;public final void invoke() {System.out.println("testNormalSafeRun");}InlineSampleKt.testNormalSafeRun.1() {}
}

Lambda表达式被调用处的代码

public final class InlineSampleKt {public static final void safeRun(@NotNull Function0<Unit> runnable) {Intrinsics.checkParameterIsNotNull(runnable, (String)"runnable");try {runnable.invoke();}catch (Throwable t) {t.printStackTrace();}}public static final void testNormalSafeRun() {InlineSampleKt.safeRun(testNormalSafeRun.1.INSTANCE);}
}

上面的 Lambda 表达式 比较简单,那是因为

  • Lambda 表达式仅仅使用了一次
  • Lambda 表达式未捕获外部的变量

为了更深入的研究,我们尝试一下更加复杂的情况

  • Lambda 表达式会多次调用
  • Lambda 表达式捕获外部的变量

这里我们使用下面的代码,实现一个多次调用捕获外部变量的 Lambda 表达式的调用。

fun toManyLambdaCalls() {for (i in 0..100) {safeRun {System.out.println("toManyLambdaCalls currentCount=$i")}}
}

上面的Lambda 表达式 捕获了外部的变量i,同时执行了很多次。

我们反编译上述的代码,得到的类似的Java实现代码如下

public static final void toManyLambdaCalls() {int n = 0;int n2 = 100;while (n <= n2) {void i;InlineSampleKt.safeRun((Function0<Unit>)((Function0)new Function0<Unit>((int)i){final /* synthetic */ int $i;public final void invoke() {System.out.println("toManyLambdaCalls currentCount=" + this.$i);}{this.$i = n;super(0);}}));++i;}}

在上面的代码中

  • Lambda 表达式 翻译成的Function0 的实例被创建了近101多次,生成101个Fuction0 实例
  • 由于Lambda 表达式捕获了外部的变量,生成的Fuction0类 接受变量作为参数

上述代码存在的性能问题

  • Function0 实例创建过多,而这些实例大多数会很快被回收,造成短时间内GC压力增大
  • 次数过多的方法调用(Function0构造方法和invoke方法),造成一定的耗时

显然,这种实现,我们不能接受的。

inline

好在Kotlin提供了,处理上面问题的方法,这就是所谓的inline 关键字。如下,

  • 我们使用inline修饰safeRunInlined方法
  • testInlinedSafeRun 中调用 safeRunInlined 方法
inline fun safeRunInlined(runnable: () -> Unit) {try {runnable()} catch (t: Throwable) {t.printStackTrace()}
}fun testInlinedSafeRun() {safeRunInlined {System.out.println("testInlinedSafeRun")}
}

再次我们反编译上面的Kotlin代码,得到对应的Java 代码。

public static final void safeRunInlined(@NotNull Function0 runnable) {int $i$f$safeRunInlined = 0;Intrinsics.checkParameterIsNotNull(runnable, "runnable");try {runnable.invoke();} catch (Throwable var3) {var3.printStackTrace();}}public static final void testInlinedSafeRun() {boolean var0 = false;try {int var1 = false;System.out.println("testInlinedSafeRun");} catch (Throwable var2) {var2.printStackTrace();}}

我们分析上述代码发现,testInlinedSafeRun 的方法体包含了

  • safeRunInlined 的方法体
  • Lambda 表达式的内容

是的,inline 的作用就是把上面两项的内容,提取到调用处 testInlinedSafeRun 中。通过这种形式,避免了最一开始的类实例生成的问题了。

When Lambada meets return

自从了解了 Lambda 可以被inline后,我们貌似可以自由自在地使用它。然而事实或许不是这样,比如我们看一下这段代码。

inline fun higherOrderFunctionFirst(runnable: () -> Unit) {System.out.println("higherOrderFunction.before")runnable()System.out.println("higherOrderFunction.after")
}fun testLambdaReturn() {higherOrderFunctionFirst {System.out.println("testLambdaReturn")return}}

上面的代码我们执行预期的输出应该是这样

higherOrderFunction.before
testLambdaReturn
higherOrderFunction.after

然后实际的执行结果却有点事与愿违

higherOrderFunction.before
testLambdaReturn

原因还是发生了inline,higherOrderFunctionFirst的执行因为runnable中的return,造成了该方法的返回。

解决方法也比较简单,就是不直接使用return,而是使用指定label的return方式

fun testLambdaReturn() {higherOrderFunctionFirst {System.out.println("testLambdaReturn")return@higherOrderFunctionFirst //valid}}

解决了上面的问题,我们还需要带出一个技术概念,就是控制流。

control flow 控制流

维基百科中的英文定义

In computer science, control flow (or flow of control) is the order in which individual statements, instructions or function calls of an imperative program are executed or evaluated.

对应的中文意思是 在计算机科学中,控制流是单个语句(指令,或命令式编程中函数调用)的执行顺序。

local control flow 本地控制流

本地控制流和上面的功能描述一致,只是限定了范围为方法内部。比如下面的代码

fun testControlFlow() {functionA()functionB()functionC()//..... other code
}

如果上面的方法遵循本地控制流,则functionA,functionBfunctionC依次执行,但是存在违背本地控制流的情况,即非本地控制流(Non local control flow),常见的有

  • inline 的 Lambda 表达式含有return
  • 上述的functionAfunctionBfunctionC发生未捕获异常
  • 协程也会导致 Non local control flow出现

对于Lambda中的return,除了上述的解决方法,还有下面两种解决方法

noinline

  • noinline 用来限定 lambda表达式
  • noinline 强制lambda表达式 不进行inline处理,对应的方式就是翻译成内部类实现。
  • noinline 需要配合inline使用

使用示例如下

inline fun bigHigherOrderFunction(firstRunnable: () -> Unit, noinline secondRunnable: () -> Unit, thirdRunnable: () -> Unit) {firstRunnable()secondRunnable()thirdRunnable()
}fun testNoInline() {bigHigherOrderFunction({System.out.println("firstRunnable")}, {System.out.println("secondRunnable")//return //not allowed if the lambda is noinlined}, {System.out.println("thirdRunnable")})
}

反编译验证一下。

public final class NoinlineSampleKt {public static final void bigHigherOrderFunction(@NotNull Function0 firstRunnable, @NotNull Function0 secondRunnable, @NotNull Function0 thirdRunnable) {int $i$f$bigHigherOrderFunction = 0;Intrinsics.checkParameterIsNotNull(firstRunnable, "firstRunnable");Intrinsics.checkParameterIsNotNull(secondRunnable, "secondRunnable");Intrinsics.checkParameterIsNotNull(thirdRunnable, "thirdRunnable");firstRunnable.invoke();secondRunnable.invoke();thirdRunnable.invoke();}public static final void testNoInline() {Function0 secondRunnable$iv = (Function0)null.INSTANCE;int $i$f$bigHigherOrderFunction = false;int var2 = false;System.out.println("firstRunnable");secondRunnable$iv.invoke();var2 = false;System.out.println("thirdRunnable");}

注意,对于不进行inline处理的 lambda 表达式中 不允许使用return。

crossinline

但是我们使用了上面的noinline,可能还是担心一些性能问题,好在这里,还有一个叫做crossinline的东西。

  • crossinline 需要配合inline一起起作用
  • crossinline 限定的 lambda 不允许使用return,避免了non local control flow问题

使用示例

//crossinline必须和inline结合使用
inline fun higherOrderFunctionCrossline(crossinline runnable: () -> Unit) {runnable()
}fun testCrossline() {higherOrderFunctionCrossline {System.out.println("testCrossline")
//        return  not allowed here}
}

再次反编译验证代码

public static final void higherOrderFunctionCrossline(@NotNull Function0 runnable) {int $i$f$higherOrderFunctionCrossline = 0;Intrinsics.checkParameterIsNotNull(runnable, "runnable");runnable.invoke();}public static final void testCrossline() {int $i$f$higherOrderFunctionCrossline = false;int var1 = false;System.out.println("testCrossline");
}

Kotlin inline与 JIT inline的区别

提到inline,听说过的朋友可能第一个想到的是 JIT 的 inline。JIT inline 是JVM虚拟机提供的运行时的一种优化方式。

来一段代码举例来说

public int add(int x, int y) {return x + y;
}public void testAdd() {//some code hereint result = add(a, b);
}

当JVM的JIT编译决定将add方法执行inline操作后,testAdd的方法实现会变成类似这样

public void testAdd() {int result = a + b;
}

即 将add的方法体实现提取到调用处(testAdd方法中)。inline带来的好处也不言而喻,那就是减少了方法调用产生的进栈和出栈操作,提升运行时的效率。

Kotlin的inline作用和JIT inline大体差不多,稍有一些不同

  • Kotlin的inline发生在编译时,而不是运行时
  • Kotlin的inline可以明确指定,而jit inline则无法指定发生。

inline 带来的其他问题 can not access private variable

private val aPrivateValue = "A Private Value"internal val internalValue = "Internal Value"@PublishedApi
internal  val taskId = "1"val publicValue = ""var publicVariable = ""inline fun beToInlinedMethod(runnable: () -> Unit) {//aPrivateValue //Public-API inline function cannot access non-public-API// 'private val aPrivateValue: String' defined in root package in file InlineAccessPrivateMember.kt//  internalValue  同样也报错上面的错误taskIdpublicValuepublicVariable
}

上面的beToInlinedMethod 无法访问声明在同一文件中的aPrivateValueinternalValue,因为

  • beToInlinedMethod的方法体很有可能被提取到别的模块的方法中
  • 而private 只能在定义的文件中访问
  • internal 只能限定在同一模块访问

解决访问有很多

  • 使用上面的public,默认的访问限定符就是public
  • 也可以使用更加推荐的,internal 加上 @PublishedApi 注解的方式。

注:反编译代码受工具影响,可能有些微问题,但不影响总体理解。

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/88778.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

034_小驰私房菜_[问题复盘] Qcom平台,某些三方相机拍照旋转90度

全网最具价值的Android Camera开发学习系列资料~ 作者:8年Android Camera开发,从Camera app一直做到Hal和驱动~ 欢迎订阅,相信能扩展你的知识面,提升个人能力~ 【一、问题】 某些三方相机,预览正常,拍照旋转90度 【二、问题排查】 1 ) HAL这边Jpeg编码数据在哪个地方…

【动态map】牛客挑战赛67 B

登录—专业IT笔试面试备考平台_牛客网 题意&#xff1a; 思路&#xff1a; 考虑动态的map 可以先定义一个状态&#xff0c;然后用map统计前缀这个状态的出现次数 在这里&#xff0c;定义{a,b}为cnt1 - cnt0和cnt2 - cnt0 当cnt0 和 cnt1都和cnt2相同时&#xff0c;统计贡献…

在 IntelliJ IDEA 中使用 Docker 开发指南

目录 一、IDEA安装Docker插件 二、IDEA连接Docker 1、Docker for Windows 连接 2、SSH 连接 3、Connection successful 连接成功 三、查看Docker面板 四、使用插件生成镜像 一、IDEA安装Docker插件 打开 IntelliJ IDEA&#xff0c;点击菜单栏中的 "File" -&g…

百度屏蔽词有哪些?其中就有移民关键词指数被屏蔽?

我是百收网SEO&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 今日tombkeeper消息爆料&#xff1a;百度指数已经屏蔽“移民”等关键词指数。 大家好&#xff0c;我是百收网SEO商学院的狂潮微课老师&#xff0c;今天我们来讲解第 12 节课关键词优化难度分析…

【JavaEE基础学习打卡03】Java EE 平台有哪些内容?

目录 前言一、Java EE平台说明二、Java EE平台容器及组件1.平台容器2.平台组件 三、JavaEE平台API服务1.API服务概览2.平台API 总结 前言 &#x1f4dc; 本系列教程适用于Java Web初学者、爱好者&#xff0c;小白白。我们的天赋并不高&#xff0c;可贵在努力&#xff0c;坚持不…

每天一道leetcode:72. 编辑距离(动态规划困难)

今日份题目&#xff1a; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符 删除一个字符 替换一个字符 示例1 输入&#xff1a;word1 "horse", word…

静态库和动态库制作

文章目录 前言一、静态库和动态库介绍1、静态库2、动态库 二、静态库的制作及使用1、准备好源码2、编译源码生成 .o 文件3、制作静态库4、使用静态库 三、动态库的制作及使用1、生成位置无关的 .o 文件2、制作动态库3、使用动态库4、指定动态库路径并使其生效 四、对比1、静态库…

链表OJ详解

&#x1f495;人生不满百&#xff0c;常怀千岁忧&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;链表oj详解 题目一&#xff1a;移除元素 题目要求&#xff1a; 画图分析&#xff1a; 代码实现&#xff1a; struct ListNode* removeElements(struct List…

Linux实用命令合集

适用于CentOS7系统&#xff0c;其他系统有些命令不支持 yum install epel-release 失败 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo vi/vim检索关键字 命令模式:/****"n"可以跳转到下一个关键字位置 cat 查看配置文件不显示…

Redis 6.5 服务端开启多线程源码

redis支持开启多线程&#xff0c;只有从socket到读取缓冲区和从输出缓冲区到socket这两段过程是多线程&#xff0c;而命令的执行还是单线程&#xff0c;并且是由主线程执行 借鉴&#xff1a;【Redis】事件驱动框架源码分析&#xff08;多线程&#xff09; 一、main启动时初始化…

第4章:决策树

停止 当前分支样本均为同一类时&#xff0c;变成该类的叶子节点。当前分支类型不同&#xff0c;但是已经没有可以用来分裂的属性时&#xff0c;变成类别样本更多的那个类别的叶子节点。当前分支为空时&#xff0c;变成父节点类别最多的类的叶子节点。 ID3 C4.5 Cart 过拟合 缺…

超导热催生meme,换汤不换药的投机轮回

文/章鱼哥 出品/陀螺财经 币圈对炒作meme概念的热情从未消亡过。 随着一种名为LK-99的物质被发现&#xff0c;围绕超导的兴奋不仅激发了科学界&#xff0c;加密货币相关概念也与之沸腾。不出所料&#xff0c;与此前围绕元宇宙、AI大肆炒作一样&#xff0c;许多meme代币已经出现…

Spring 使用注解开发、代理模式、AOP

使用注解开发 在Spring4之后&#xff0c;要使用注解开发&#xff0c;必须要保证AOP的包导入了 项目搭建&#xff1a; 在配置文件中导入约束&#xff0c;增加注解支持 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.spri…

华为新版ENSP PRO模拟器测评:性能表现与功能扩展一览

一、引言 在网络领域不断涌现的新技术和复杂的网络拓扑要求&#xff0c;推动了网络设备模拟器的持续发展和创新。华为作为一家领先的通信技术解决方案提供商&#xff0c;不断致力于为网络工程师和技术从业人员提供更优秀的仿真环境。最近&#xff0c;华为推出了ensp pro模拟器的…

html 计算器界面

其他链接&#xff1a; https://www.freecodecamp.org/news/how-to-build-an-html-calculator-app-from-scratch-using-javascript-4454b8714b98/ https://codepen.io/pen/tour/welcome/start 下面展示一些 内联代码片。 <!DOCTYPE html> <html lang"en">…

苹果正在测试新款Mac mini:搭载M3芯片 配备24GB大内存

据悉苹果目前正在测试新的Mac机型&#xff0c;亮点是采用最新的M3芯片。 据报道&#xff0c;首款搭载M3芯片的设备应该是13英寸的MacBook Pro和重新设计的MacBook Air&#xff0c;Mac mini机型并不在名单上。 M3和M2同样拥有最多8个核心&#xff0c;分别为4个性能核和4个能效核…

MySQL高阶知识点(一)一条SQL【更新】语句是如何执行的

一条SQL【更新】语句是如何执行的 首先&#xff0c;可以确定的说&#xff0c;【查询】语句的那一套流程&#xff0c;【更新】语句也是同样会走一遍&#xff0c;与查询流程不一样的是&#xff0c; 更新语句涉及到【事务】&#xff0c;就必须保证事务的四大特性&#xff1a;ACID&…

Java-IO模型分析

BIO&#xff08;同步阻塞&#xff09; 利用网络连接传输数据为例&#xff1a; 服务端单线程 服务端只有一个主线程处理客户端的连接和读写处理&#xff0c;此时如果有第二个客户端欲连接并发送消息服务端是接收不到的。 因为读写和等待accept连接都是阻塞的。 sever端代码…

【TypeScript】TS类型守卫(六)

【TypeScript】TS类型守卫&#xff08;六&#xff09; 【TypeScript】TS类型守卫&#xff08;六&#xff09;一、什么是类型守卫二、in操作符三、typeof操作符四、instanceof操作符五、自定义类型谓词函数 一、什么是类型守卫 TypeScript类型守卫&#xff08;Type Guards&…