关于lamda、inline和reified

一、关于 lamda

1.1、kotlin中的一个lamda声明,对应Java的一个FunctionN的接口

TestLamda.kt 定义一个顶级函数theFun,参数param是一个lamda表达式

fun theFun(param:()->Unit){
    println("aaaaa")
    param.invoke()
    println("bbbbb")
}

顶级函数 会依据文件名生成一个类TestLamdaKt,顶级函数会被转化成一个静态函数,如theFun。
theFun中的lamda表达式被替换成Funtion0的接口

public final class TestLamdaKt {
    public static final void theFun(Function0<Unit> function0) {
        Intrinsics.checkNotNullParameter(function0, "param");
        System.out.println((Object) "aaaaa");
        function0.invoke();
        System.out.println((Object) "bbbbb");
    }
}

FunctionN是kotlin.jvm.functions 中定义的接口

package kotlin.jvm.functions

/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R

...

public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

  • Function0 代表无参数的lamda表达式
param:()->Unit

  • Function1 代表有一个参数P1,返回值为R的lamda表达式
param: (String) -> Unit

Function2 代表有两个参数P1、P2,返回值为R的lamda表达式

param: (String,String) -> Int

lamda表达式最多可以有22个参数

1.2、Lamda的实现: 函数即对象

kotlin中每一个函数都是一个对象,并且会捕获一个闭包。

在Kotlin中,使用高阶函数(函数/Lambda作为参数传递)时不良使用会造成性能问题。

1.2.1、Lambda不访问外部

使用高阶函数时当函数/Lambda不访问外部的变量/方法(即不捕获外部)时,编译器会将函数对应的对象优化成类的静态成员变量,反复调用时不会有性能问题。此时也不需要使用inline。

TestLamda的ShowResult()方法中,生成一个Lamda表达式实例,传递给theFun()

class TestLamda {

    fun showResult(){
        theFun {
            val result = "hello,this is the name"
            println("this is result:${result}")
        }
        
}

生成的类为TestLamdashowResult1
继承自Lamda,实现了Function0接口

final class TestLamda$showResult$1 extends Lambda implements Function0<Unit> {
    public static final TestLamda$showResult$1 INSTANCE = new TestLamda$showResult$1();

    TestLamda$showResult$1() {
        super(0);
    }

    @Override // kotlin.jvm.functions.Function0
    public final void invoke() {
        System.out.println((Object) Intrinsics.stringPlus("this is result:", "hello,this is the name"));
    }
}

调用处theFun() 传递的是TestLamdashowResult1的实例

public final class TestLamda {
    public final void showResult() {
        TestLamdaKt.theFun(TestLamda$showResult$1.INSTANCE);
    }
}

1.2.2、当函数/Lambda捕获外部时

当函数/Lambda捕获外部时,比如访问闭包内的参数、访问外部方法时,闭包会一new 内部类对象的方式进行传递,此时如果方法被频繁调用(如在循环中被调用)会造成性能问题:对象被持续创建,造成内存抖动,增加gc负担,频繁gc也可能造成卡顿。

class TestLamda {

    var curNum:Int = 300
    fun myPrinter(info:String){
        Log.d("myPrinter",info)
    }
    
    
    fun showResultPrinter(){
        theFun {
            myPrinter("this visit outer class function 111")
        }
    }

    fun showResultOuterVal(){
        theFun {
          var newValue = this.curNum+1
        }
    }
}

反编译成Java后如下所示


public final class TestLamda {
    private int curNum = 300;

    public final void myPrinter(String info) {
        Intrinsics.checkNotNullParameter(info, "info");
        Log.d("myPrinter", info);
    }


    public final void showResultPrinter() {
        TestLamdaKt.theFun(new Function0<Unit>(this) { // from class: com.zuoyebang.iot.watch.testlamda.TestLamda$showResultPrinter$1
            final /* synthetic */ TestLamda this$0;

            {
                this.this$0 = r2;
            }

            @Override // kotlin.jvm.functions.Function0
            public final void invoke() {
                this.this$0.myPrinter("this visit outer class function 111");
            }
        });
    }

    public final void showResultOuterVal() {
        TestLamdaKt.theFun(new Function0<Unit>(this) { // from class: com.zuoyebang.iot.watch.testlamda.TestLamda$showResultOuterVal$1
            final /* synthetic */ TestLamda this$0;

            {
                this.this$0 = r2;
            }

            @Override // kotlin.jvm.functions.Function0
            public final void invoke() {
                int curNum = this.this$0.getCurNum() + 1;
            }
        });
    }
}

1.2.3、使用inline,方法会被平铺到调用处,不存在上面说的性能问题。

class TestLamda {

    var curNum:Int = 300
   
    fun showResultOuterValInline(){
        theFun {
            var newValue = this.curNum+1
        }
    }
}

inline fun theFun(param:()->Unit){
    println("aaaaa")
    param.invoke()
    println("bbbbb")
}

反编译成Java,可以看到inline函数,theFun被平铺到了showResultOuterValInline中,没有性能问题

public final class TestLamda {
    private int curNum = 300;
    public final void showResultOuterValInline() {
        System.out.println((Object) "aaaaa");
        int curNum = getCurNum() + 1;
        System.out.println((Object) "bbbbb");
    }
}

二、inline、noinline与crossline

2.1、inline

  • inline 修改的函数为内联函数,会将函数体平铺到调用处,可以规避高阶函数的性能问题。
  • inline的使用不当也会有负面作用:由于inline是将函数平铺到调用处,所以要避免内联函数过大。

2.2、noinline

在inline修饰的方法里默认所有形参都是inline的,内联后会被复制到这个函数中使用到的位置.如果形参是函数类型,同样会被复制到这个位置.

如果希望某个 函数形参 不进行内联,就需要使用noinline关键字。

noinline修饰的函数类型参数不会被内联优化.

2.3、crossline 内联函数中禁止非局部返回

局部返回和非局部返回

a、没有inline修饰的函数只能进行局部返回(return@局部函数域名)
  • 可以通过 自定义label@ 为一个lamda 定义标签,需要局部返回时,直接return @自定义label即可
  • 局部返回 仅能退出该lamda,不会影响lamda调用方的逻辑
fun test():Int{
    func label@{
        if(it ==1){
            return@label
            //return 1 //会提示 - return is not allow here 
        }
        println("feifei action:${it} 执行中")
    }
    return 1
}


fun func(action:(Int)->Unit){
    println("feifei func 开始执行")
    for(i in 0..2){
        println("feifei action:${i},开始执行")
        action.invoke(i)
        println("feifei action:${i},执行完毕")
    }
}

执行结果:

2021-12-12 15:22:31.282 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:0,开始执行
2021-12-12 15:22:31.282 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:0 执行中
2021-12-12 15:22:31.283 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:0,执行完毕
2021-12-12 15:22:31.283 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:1,开始执行
2021-12-12 15:22:31.283 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:1,执行完毕
2021-12-12 15:22:31.283 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:2,开始执行
2021-12-12 15:22:31.283 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:2 执行中
2021-12-12 15:22:31.283 27491-27491/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:2,执行完毕
b、inline修饰的函数可以进行非局部返回(也就是可以使用return)

因为inline函数会将fun的内容平铺到调用处,所以可以允许非局部返回,非局部返回 会影响调用方的逻辑处理


fun test():Int{
    func label@{
        if(it ==1){

            return 1 //会影响lamda调用方的逻辑
        }
        println("feifei action:${it} 执行中")
    }
    return 1
}


inline fun func(action:(Int)->Unit){
    println("feifei func 开始执行")
    for(i in 0..2){
        println("feifei action:${i},开始执行")
        action.invoke(i)
        println("feifei action:${i},执行完毕")
    }
}

2021-12-12 15:37:31.351 0-0/? I/init: processing action (sys.init.updatable_crashing=1) from (/system/etc/init/flags_health_check.rc:10)
2021-12-12 15:37:36.897 28051-28051/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:0,开始执行
2021-12-12 15:37:36.897 28051-28051/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:0 执行中
2021-12-12 15:37:36.897 28051-28051/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:0,执行完毕
2021-12-12 15:37:36.897 28051-28051/com.zuoyebang.iot.watch.testlamda I/System.out: feifei action:1,开始执行
c、如果希望inline函数中 某个lamda函数 不允许 "非局部返回",则需要使用crossInline关键字修饰

如下所示,直接return 会提示错误

fun test():Int{
    func label@{
        if(it ==1){
            return 1 //return not allow here 
        }
        println("feifei action:${it} 执行中")
    }
    return 1
}


inline fun func(crossinline  action:(Int)->Unit){
    println("feifei func 开始执行")
    for(i in 0..2){
        println("feifei action:${i},开始执行")
        action.invoke(i)
        println("feifei action:${i},执行完毕")
    }
}

三、泛型实化reified

Kotlin和Java同样存在泛型类型擦除的问题,在运行时是无法获取泛型的真实类型信息的。

但是Kotlin作为一门现代编程语言,他知道Java擦除所带来的问题,所以开了一扇后门。就是通过inline函数保证使得泛型类的类型实参在运行时能够保留,这样的操作Kotlin中把它称为实化,对应需要使用reified关键字。

泛型实化的原理

  • 带泛型参数的函数,在具体调用时,泛型参数的具体类型 已经确定。
  • inline内联函数 会将函数体内容 平铺到调用处

结合以上两点,可以使用reified关键字,在内联函数将代码平铺时,将调用处泛型参数的实际参数类型 平铺到调用处。

这样不同泛型参数的调用被内联时,会被实化成不同的具体泛型实际类型,因为调用处泛型已经被替换成实际的类型参数,所以也就理所当然可以在运行时获取泛型的实际类型。

每次调用带实化类型参数的函数时,编译器都知道此次调用中作为泛型类型实参的具体类型。所以编译器只要在每次调用时生成对应不同类型实参调用的字节码插入到调用点即可。总之一句话很简单,就是带实化参数的函数每次调用都生成不同类型实参的字节码,动态插入到调用点。由于生成的字节码的类型实参引用了具体的类型,而不是类型参数所以不会存在擦除问题。

示例:

public inline fun <reified T: Activity> Context.startActivityReified(
    vararg params: Pair<String, String>) {
    val intent = Intent(this, (T::class as Any).javaClass)
    params.forEach { intent.putExtra(it.first, it.second) }
    startActivity(intent)
}

class MainActivity : AppCompatActivity() {
 
    fun testIntent(){
        startActivityReified<MainActivity>()
    }
}
public final class MainActivity extends AppCompatActivity {
    /* access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public final void testIntent() {
        MainActivity $this$startActivityReified$iv = this;
        Pair[] params$iv = new Pair[0];
        Intent intent$iv = new Intent($this$startActivityReified$iv, Reflection.getOrCreateKotlinClass(MainActivity.class).getClass());
        for (Pair pair : params$iv) {
            intent$iv.putExtra((String) pair.getFirst(), (String) pair.getSecond());
        }
        $this$startActivityReified$iv.startActivity(intent$iv);
    }

四、参考文章

https://www.jianshu.com/p/70b20229827d

https://zhuanlan.zhihu.com/p/78571521

https://www.jianshu.com/p/f613a05bab1e

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容