全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-10-05_看到Kotlin里满屏的 inline,我真的想 Java 了

您的位置:首页 >> 新闻 >> 行业资讯

看到Kotlin里满屏的 inline,我真的想 Java 了 ?多年以后,当我面对 Kotlin 源码满屏的inline/crossinline/noinline时,将会想起用 Eclipse 手动创建Java匿名内部类的那个遥远的下午。 ?一切要用Java和匿名内部类讲起。 一、Java 与匿名内部类Java 中最开始要使用高阶函数,需要先定义一个接口,比如Android里常用的 OnClickListener: publicinterfaceOnClickListener{ voidonClick(View v);}然后使用的时候,需要创建一个实现这个接口的匿名内部类,这就是最原始的类似高阶函数的写法: view.setOnClickListener(newView.OnClickListener() { @Override publicvoidonClick(View v) { System.out.println("Hello World"); }});从 Java 8 开始,符合函数式接口规范(只包含一个抽象方法)的接口,可以用 Lambda 表达式简写成这样: view.setOnClickListener(v - System.out.println("Hello World"));这下代码少了不少,看起来终于“函数式”了一点。然而使用时需要先创建接口,虽然有这个特性,但是用的人也不多,在当时已经算是进阶用法了。 二、Kotlin 与高阶函数后来有了 Kotlin ,Kotlin最大的优点之一就是原生支持 Lambda 和高阶函数了。Kotlin 并不需要提前定义接口,只要在函数参数中写函数类型就行了: funwrapper(block: () -Unit){ block()}调用方式: wrapper { println("Inside Block")}看上去非常函数式,一个类也没写,但实际上…… 查看 Kotlin 字节码(Tools - Kotlin - Show Kotlin Bytecode),可以看到下面的内容: publicfinalstaticwrapper(Lkotlin/jvm/functions/Function0;)V // annotable parameter count: 1 (invisible) @Lorg/jetbrains/annotations/NotNull;() //invisible, parameter0 L0 ALOAD0 LDC"block" INVOKESTATICkotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V L1 LINENUMBER3L1 ALOAD0 INVOKEINTERFACEkotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object; (itf) POP L2 LINENUMBER5L2 RETURN L3 LOCALVARIABLEblockLkotlin/jvm/functions/Function0;L0L30 MAXSTACK=2 MAXLOCALS=1虽然一堆字节码看起来让人头晕,但是其实认得几个关键词就够用了:wrapper、Function0、Ojbect、invoke。 原来,lambda函数wrapper中的函数类型参数block,被编译成了Function0类型的参数,并通过invoke()方法调用它。那Function0是什么呢? Function0是 Kotlin 中预先定义好的函数接口,表示函数接收0个参数,而接收一个参数的则使用Function1,两个参数使用Function2,以此类推。源码在Functions.kt,如下: // Functions.ktpublicinterfaceFunction0out R :FunctionR { publicoperatorfuninvoke(): R}publicinterfaceFunction1in P1, out R :FunctionR { publicoperatorfuninvoke(p1:P1): R}...publicinterfaceFunction22......?小问题:Functions.kt只定义到了Function22,那超过22个参数的函数怎么办? ?所以,实际上,Kotlin跟Java的Lambda基本没区别?都是使用接口和匿名内部类的形式。 但是如果是要创建类的话,那或多或少会有一些性能开销,有些高阶函数可能会被频繁调用。 三、inline 的作用:避免对象和调用开销这个时候,Kotlin 提供了inline关键字。加上inline后,编译器会将函数和它的 lambda 参数的代码,在调用处直接展开,从而避免函数调用和对象创建。 示例: // 加上inline关键字inlinefunwrapper(block: () -Unit){ println("Start") block() println("End")}调用: wrapper { println("Inside Block")}这段代码编译后,大致会变成: println("Start")println("Inside Block")println("End")?可以打开 Kotlin Bytecode 面板,切换“Inline”选项查看区别。 ?代码展开了,性能更好了,也没生成对象,非常的清晰明了。 如果一切就止步于此,我也只需要学一个关键字,但显然没这么简单... 四、inline 带来的非局部 return 行为当 inline 函数中的 lambda 含有return时,会出现一些有点“反直觉”的行为。 比如下面的代码: // 定义inlinefunwrapper(block: () -Unit){ println("Start") block() println("End")}// 调用funtest(){ wrapper { println("Inside") return } println("Outside")}按常规理解会打印三行日志:“End”不输出,“Outside”能输出,但实际输出的结果是: StartInside也就是说调用者“Outside”也没有输出:“难道这个 return 结束了外部的函数?" 没错,这个return实际上直接跳出了test()函数,这也是 inline 带来的特性之一: 「非局部返回:return会跳出外部函数」 由于使用了inline,block的内容被直接展开,return也会被展开,相当于变成这样: funtest(){ println("Start") println("Inside") return println("End") // 不执行 println("Outside")// 不执行}五、crossinline:阻止非局部返回好吧,inline是将函数直接展开,相当于复制粘贴到这里,return就返回调用者了,也算合理。 但是假如「这个函数将在异步异步线程中调用呢」?例如: inlinefunrunAsync(block: () -Unit){ thread { block() }}调用: funmain(){ runAsync { println("Inside thread") return }}假设inline后展开: funmain(){ thread { println("Inside thread") return }}思考一下,这样会有什么问题:main()执行完就返回了,而线程在另一个栈异步执行,按照上文inline中return的逻辑,这个这个return会跳出main()。 但是问题就出现了: main()已经结束了,这个时候再返回main就会引起异常。 所以这种情况是不被允许的,block()在编译器中会直接报下面的错误: // 此处无法内联“block: () - Unit”:它可能包含非局部返回。// 请将 “crossinline” 修饰符添加到参数声明 “block: () - Unit”。Cannotinline'block: () - Unit'here: it might contain non-local returns.Add'crossinline'modifier to parameter declaration'block: () - Unit'.提示我们需要加上crossinline修饰符: inlinefunrunAsync(crossinlineblock: () -Unit){ thread { block() }}之后,编译器会阻止lambda 中的return非局部返回,只能使用return@xxx的局部返回。 六、noinline:不内联在 inline 函数中,所有 lambda 参数默认都会被内联。但有时候可能想保留 lambda 作为对象使用。 例如: inlinefunwrapper(block: () -Unit){ valb = block // ? 报错 b()}编译器会报错,因为试图把一个 inline 的参数当作值存到变量里。 为什么会报错?让我们从最开始思考: ?lambda会创建函数对象而增加性能消耗inline为了优化性能而将函数直接展开,不创建函数对象现在需要把函数当成变量传递,所以又需要函数对象?那到底是不需要对象优化性能,还是需要对象进行传递?编译器也蒙圈了,这个地方构成了语义冲突,所以这个被当成变量使用的函数,就无法inline了。这个时候可以对这个参数加上noinline,告诉编译器这个参数不内联展开: inlinefunwrapper(noinlineblock: () -Unit){ valb = block // ? 编译通过 b()}多个参数的场景(有的函数参数要展开,而有的函数参数要当成变量不能展开): inlinefunrun(block1: () -Unit,noinlineblock2: () -Unit){ valtask = block2 // 可以编译 task() block1()}七、总结inline系列关键字的行为对比如下: 修饰符是否内联是否允许非局部 return是否可以保存/传递 lambdainline???crossinline???noinline???Kotlin 的 lambda 也会生成函数对象,所以会带来一定的性能开销。inline可以将函数在调用处展开,减少对象创建,同时如果写了return,也会一起展开,导致跳出调用者。在线程或协程中,return会造成跳出栈帧的语义错误,所以编译器不允许,使用crossinline可以让编译器阻止非局部return。inline是为了避免生成函数对象,而把 lambda 存到变量里传来传去又需要函数对象,造成了冲突,这种要使用noinline。如果搞不懂,可以不用。思考为什么Koltin要搞非局部返回??没有非局部 return,代码逻辑一样能写,但「表达力会变差,控制流会变复杂」,尤其是在「组合式 API、DSL、协程」这些地方。(参考AI的理解) ?理想写法(有非局部 return)inlinefundoIf(condition:Boolean, block: () -Unit){ if(condition) block()} funlogin(user:String?){ doIf(user ==null) { println("no user") return// ? 非局部 return:直接跳出 login() } println("login user:$user")}这段代码可读性非常高,像是在自然叙述: ?如果用户是 null,就执行块并跳出函数;否则继续执行。 ?如果没有非局部 return,会怎么样?那block就不能直接 return 出login(),它只能跳出 lambda 本身。所以这段代码必须“手动补救”,如下面这种加flag的写法: funlogin(user:String?){ varshouldReturn =false doIf(user ==null) { println("no user") shouldReturn =true } if(shouldReturn)return println("login user:$user")}AI编程资讯AI Coding专区指南: https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2025-01-07_CES 2025:AMD锐龙9000新品亮相,游戏、创作力表现超Intel旗舰 下一篇:2025-02-13_《哪吒2》破百亿!如果票房能到160亿,饺子导演能分多少亿

TAG标签:

21
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价