关于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 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 : Function {
    /** Invokes the function. */
    public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1 : Function {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R

...

public interface Function22 : Function {
    /** 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}")
        }
        
}

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

final class TestLamda$showResult$1 extends Lambda implements Function0 {
    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() 传递的是TestLamda1的实例

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(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(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  Context.startActivityReified(
    vararg params: Pair) {
    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()
    }
}
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

你可能感兴趣的:(关于lamda、inline和reified)