kotlin—匿名函数及其实现原理

2022-05-21  本文已影响0人  jxiang112

1、匿名函数语法

匿名函数就是没有名称的函数,它除了没有名称之外,其他与具名函数是一样的。
你们函数的语法如下:

fun([args]) [:returnType]{
    [body]
} 

args:就是函数参数列表,与具名函数的参数是一样的规则
returnType:函数的返回类型,与具名函数的参数是一样的规则

匿名函数属于函数类型

2、匿名原理

你们函数的实现原理与kotlin—lambda及其原理差不多一样的原理,它们都生成一个匿名类,通过匿名类的来实现匿名函数的对应方法体。
匿名函数也是根据函数参数个数实现Function系列的接口,我们通过例子来分析其原理:
举例:

class TestClosure {
    val b = 20
    val sum : () -> Int = fun() : Int {
        return b + 10
    }
}

TestClosure 中声明了一个匿名函数sum变量,它使用外部TestClosure 类的成员变量b。
编译TestClosure 之后,除了生成TestClosure.class之外,还生成了TestClosuresum1.class,它是匿名函数的对应的匿名类,我们通过javac -c -v -p TestClosuresum1.class反编译看看其伪代码:

final class com.wyx.tcanvas.test.delegate.TestClosure$sum$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<java.lang.Integer>
  //省略.....
{
//声明TestClosure对象,即匿名类持有外部类的引用
final com.wyx.tcanvas.test.delegate.TestClosure this$0;
    descriptor: Lcom/wyx/tcanvas/test/delegate/TestClosure;
    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC

//匿名类构造函数,参数是外部类的引用 com.wyx.tcanvas.test.delegate.TestClosure$sum$1(com.wyx.tcanvas.test.delegate.TestClosure);
    descriptor: (Lcom/wyx/tcanvas/test/delegate/TestClosure;)V
    flags: (0x0000)
    Code:
      stack=2, locals=2, args_size=2
         //0-3:将外部类引用赋值给this$0
         0: aload_0
         1: aload_1
         2: putfield      #13                 // Field this$0:Lcom/wyx/tcanvas/test/delegate/TestClosure;
         5: aload_0
         6: iconst_0
         7: invokespecial #16                 // Method kotlin/jvm/internal/Lambda."<init>":(I)V
        10: return
      //省略......
  
  //匿名函数的真实方法体
  public final java.lang.Integer invoke();
    descriptor: ()Ljava/lang/Integer;
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=1, args_size=1
         //0-13:调用this$0.getgetB() + 10,并返回,其实就是匿名函数的b + 10
         0: aload_0
         1: getfield      #13                 // Field this$0:Lcom/wyx/tcanvas/test/delegate/TestClosure;
         4: invokevirtual #28                 // Method com/wyx/tcanvas/test/delegate/TestClosure.getB:()I
         7: bipush        10
         9: iadd
        10: invokestatic  #34                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: areturn
      //省略......
  
  //实现的Function接口invoke方法
  public java.lang.Object invoke();
    descriptor: ()Ljava/lang/Object;
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
          //调用匿名函数的实体invoke方法并返回
         0: aload_0
         1: invokevirtual #37                 // Method invoke:()Ljava/lang/Integer;
         4: areturn
     //省略.....
}
//省略.....

通过反编译之后匿名类的伪代码,我们发现其实匿名函数与kotlin—lambda及其原理的原理是一样的。
匿名函数会生成一个内部匿名类,匿名类继承lambda类同时实现Function系列接口,Function接口的invoke方法调用匿名函数的真实实现函数invoke。
如果匿名函数没有引用外部类的成员则使用匿名类单例对象的;如果匿名函数使用了外部类的成员则内部会持有外部类的引用,在匿名函数的实现函数invoke中通过持有外部类的引用来调用外部类的成员。

3、总结

匿名函数会生成一个内部匿名类,匿名类继承lambda类同时实现Function系列接口,Function接口的invoke方法调用匿名函数的真实实现函数invoke。
如果匿名函数没有引用外部类的成员则使用匿名类单例对象的方式;如果匿名函数使用了外部类的成员则内部会持有外部类的引用,在匿名函数的实现函数invoke中通过持有外部类的引用来调用外部类的成员。
匿名函数与lambda一样要注意不要滥用,因为它会生成额外的匿名类class文件,在使用时会创建匿名类实例对象,增加额外的空间和执行时间损耗,如果只是用匿名函数代表一个函数并执行此函数其实并非是明智的选择。
匿名函数与lambda还需要注意的是,如果引用了外部类的成员,那么它们内部会持有外部类的引用,如果使用不当会引起内存泄漏,比较典型的就是Android中的Activity使用匿名类导致的内存泄漏——解决方案是使用弱引用等

上一篇下一篇

猜你喜欢

热点阅读