kotlin入門潛修之進(jìn)階篇—高階方法和lambda表達(dá)式原理

本文收錄于 kotlin入門潛修專題系列,歡迎學(xué)習(xí)交流。

創(chuàng)作不易,如有轉(zhuǎn)載,還請(qǐng)備注。

高階方法及l(fā)ambda表達(dá)式原理

照例,先看下我們要分析的源代碼片段。如下所示:

class Test {
//定義了一個(gè)高階方法m0
    fun m0(checkStr: () -> String) {
        checkStr()
    }
//測(cè)試方法,用于測(cè)試m0
    fun test(){
        m0 {  -> "return a str" }//采用lambda表達(dá)式實(shí)例化方法類型
    }
}

一個(gè)最簡(jiǎn)單的高階方法及l(fā)ambda表達(dá)式的示例,來(lái)看下對(duì)應(yīng)的字節(jié)碼,照例先全部粘貼下:

public final class Test {


  // access flags 0x11
  // signature (Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;)V
  // declaration: void m0(kotlin.jvm.functions.Function0<java.lang.String>)
  public final m0(Lkotlin/jvm/functions/Function0;)V
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "checkStr"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 3 L1
    ALOAD 1
    INVOKEINTERFACE kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object;
    POP
   L2
    LINENUMBER 4 L2
    RETURN
   L3
    LOCALVARIABLE this LTest; L0 L3 0
    LOCALVARIABLE checkStr Lkotlin/jvm/functions/Function0; L0 L3 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x11
  public final test()V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    GETSTATIC Test$test$1.INSTANCE : LTest$test$1;
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKEVIRTUAL Test.m0 (Lkotlin/jvm/functions/Function0;)V
   L1
    LINENUMBER 8 L1
    RETURN
   L2
    LOCALVARIABLE this LTest; L0 L2 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // access flags 0x18
  final static INNERCLASS Test$test$1 null null
  // compiled from: Main.kt
}


// ================Test$test$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
// declaration: Test$test$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<java.lang.String>
final class Test$test$1 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0  {


  // access flags 0x1041
  public synthetic bridge invoke()Ljava/lang/Object;
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKEVIRTUAL Test$test$1.invoke ()Ljava/lang/String;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x11
  public final invoke()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 7 L0
    LDC "return a str"
   L1
    ARETURN
   L2
    LOCALVARIABLE this LTest$test$1; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x0
  <init>()V
    ALOAD 0
    ICONST_0
    INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x19
  public final static LTest$test$1; INSTANCE

  // access flags 0x8
  static <clinit>()V
    NEW Test$test$1
    DUP
    INVOKESPECIAL Test$test$1.<init> ()V
    PUTSTATIC Test$test$1.INSTANCE : LTest$test$1;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0
  OUTERCLASS Test test ()V
  // access flags 0x18
  final static INNERCLASS Test$test$1 null null
  // compiled from: Main.kt
}

字節(jié)碼比較長(zhǎng),這里照例分割成幾個(gè)部分來(lái)進(jìn)行闡述。

  1. 高階方法中的方法類型實(shí)際上會(huì)被編譯成Function類型,在本例中的類型就是Function0,其對(duì)應(yīng)的字節(jié)碼如下所示:
  public final m0(Lkotlin/jvm/functions/Function0;)V

這就是kotlin中對(duì)方法類型的處理,那么是所有的方法類型都會(huì)被編譯成Function0嗎?當(dāng)然不是,kotlin會(huì)根據(jù)方法類型的參數(shù)個(gè)數(shù)來(lái)做相應(yīng)的處理,比如現(xiàn)在方法類型有1個(gè)入?yún)?,則就會(huì)被編譯成Function1類型,有2個(gè)入?yún)⒕蜁?huì)被編譯成Function2類型,一次類推。下面我粘貼一個(gè)有兩個(gè)入?yún)⒌淖止?jié)碼編譯案例:

//源代碼m0,其入?yún)⑹怯袃蓚€(gè)參數(shù)的方法類型
    fun m0(checkStr: (str1:String, str2:String) -> String) {
    }
//編譯后對(duì)應(yīng)的字節(jié)碼,從中可知,
//兩個(gè)參數(shù)的方法類型被編譯成了Function2類型
  public final m0(Lkotlin/jvm/functions/Function2;)V

事實(shí)上,我們不從字節(jié)碼的角度也能發(fā)現(xiàn)這個(gè)規(guī)律,kotlin所有的方法類型都會(huì)被處理成Function家族類型,只不過(guò)參數(shù)個(gè)數(shù)不同,對(duì)應(yīng)的具體的Function類型不同而已,這個(gè)可以通過(guò)kotlin.jvm.functions包下的接口定義得到確認(rèn),如下所示:

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
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2): R
}
//中間省略n個(gè)Function類型
.........................................................
.........................................................
//第21個(gè)參數(shù)對(duì)應(yīng)的Function類型
/** A function that takes 21 arguments. */
public interface Function21<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, 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): R
}
/** A function that takes 22 arguments. */
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
}

由上面的代碼可知,kotlin最多支持定義有22個(gè)入?yún)⒌姆椒愋?。這些方法都有一個(gè)共同的Function<R>,F(xiàn)unction<R>代表的正是方法類型。除此之外,每個(gè)方法類型都提供了一個(gè)包含有對(duì)應(yīng)參數(shù)個(gè)數(shù)的invoke操作符。

  1. 在m0方法中調(diào)用傳入的方法類型實(shí)例的時(shí)候,實(shí)際上就是通過(guò)方法的invoke來(lái)完成調(diào)用的,對(duì)應(yīng)的字節(jié)碼摘錄如下:
   L1
    LINENUMBER 3 L1
    ALOAD 1
    INVOKEINTERFACE 
kotlin/jvm/functions/Function0.invoke ()Ljava/lang/Object;
    POP
   L2

有上述代碼可知,kotlin最終是通過(guò)Function0.invoke ()來(lái)完成了checkStr方法的調(diào)用。

  1. kotlin會(huì)為lambda表達(dá)式生成一個(gè)新類,類名為自己所處的類名(Test)+ 所處的方法名(test)+ 數(shù)字(從1開(kāi)始,有多個(gè)則依次遞增)。該類繼承了Lambda類并實(shí)現(xiàn)了對(duì)應(yīng)的Function接口。字節(jié)碼如下所示:
//生成的新類繼承了Lambda類,并實(shí)現(xiàn)了Function0接口
final class Test$test$2 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0  {

Lambda類實(shí)際上沒(méi)有什么東西,就是提供了lambda參數(shù)數(shù)量的入口:getArity。而Function0正是我們上面提到的有invoke入口的接口。

  1. 當(dāng)調(diào)用方法m0的時(shí)候,傳入的lambda實(shí)例,實(shí)際上就是通過(guò)上述生成的新類實(shí)例完成的。而且實(shí)際調(diào)用的時(shí)候也正是通過(guò)上述生成的新類的invoke方法完成調(diào)用的,對(duì)應(yīng)的字節(jié)碼如下所示:
//test方法對(duì)應(yīng)的字節(jié)碼
// access flags 0x11
  public final test()V
   L0
    LINENUMBER 7 L0
    ALOAD 0
//這里實(shí)際上是調(diào)用了生成類LTest$test$1的實(shí)例
    GETSTATIC Test$test$1.INSTANCE : LTest$test$1;
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKEVIRTUAL Test.m0 (Lkotlin/jvm/functions/Function0;)V//將LTest$test$1實(shí)例傳入方法m0
   L1
  1. 步驟4中提到的INSTANCE,實(shí)際上是kotlin為lambda表達(dá)式生成的新類、提供了一個(gè)單例對(duì)象。該單例對(duì)象是在該類的類構(gòu)造方法中完成初始化的,如下所示:
//本例中Test$test$1的成員屬性INSTANCE
  public final static LTest$test$1; INSTANCE
//Test$test$1的類構(gòu)造方法
  static <clinit>()V
    NEW Test$test$1
    DUP
//這里調(diào)用了類的實(shí)例構(gòu)造方法,即生成了一個(gè)Test$test$1對(duì)象
    INVOKESPECIAL Test$test$1.<init> ()V
//這里完成了對(duì)INSTANCE的賦值,其值正式上面生成的新對(duì)象
    PUTSTATIC Test$test$1.INSTANCE : LTest$test$1;
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0
  1. 在kotlin為我們生成的Test$test$1類中的invoke方法中,正式完成了方法實(shí)例對(duì)應(yīng)的實(shí)現(xiàn),如下所示:
//這里就是invoke的實(shí)現(xiàn),lambda表達(dá)式實(shí)例對(duì)應(yīng)的實(shí)現(xiàn)
//就是在這里。
  public final invoke()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 7 L0
    LDC "return a str"
   L1
    ARETURN
   L2
    LOCALVARIABLE this LTest$test$1; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

至此,高階方法與lambda背后的原理產(chǎn)生完畢。下面看下匿名方法的實(shí)現(xiàn)原理。

匿名方法背后的原理

我們將上述代碼的調(diào)用改成匿名方法的實(shí)現(xiàn),如下所示:

//將m0方法的調(diào)用改成傳入匿名方法實(shí)例方式
    fun test() {
        m0(fun(): String { return "return a str" })
    }

通過(guò)查看對(duì)應(yīng)的字節(jié)碼發(fā)現(xiàn),匿名方法和lambda表達(dá)式對(duì)應(yīng)的字節(jié)碼完全一樣,重要的字節(jié)碼摘錄如下所示:

//同樣生成了一個(gè)新類,同lambda表達(dá)式一致
final class Test$test$1 extends kotlin/jvm/internal/Lambda  implements kotlin/jvm/functions/Function0  {
// 也提供了一個(gè)單例實(shí)現(xiàn)
public final static LTest$test$1; INSTANCE
//匿名方法的實(shí)現(xiàn)也在invoke完成
 public final invoke()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 7 L0
    LDC "return a str"
    ARETURN
   L1
    LOCALVARIABLE this LTest$test$1; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
//等等...

上面省略了一些分析,最終總結(jié)一句話,匿名方法和lambda表達(dá)式實(shí)現(xiàn)一致。

閉包的訪問(wèn)原理

在上篇高階方法及l(fā)ambda表達(dá)式中,我們提到了閉包的概念,那么為什么lambda表達(dá)式和匿名方法能夠自由訪問(wèn)外部作用域中的成員呢?本小節(jié)來(lái)分析下原因。
首先附上我們要分析的源代碼:

class Test {
//為了更能說(shuō)服問(wèn)題,這里提供了一個(gè)private的私有屬性
    private val str:String = "test"
    fun m0(checkStr: () -> String) {
    }
//測(cè)試方法,這里完成了對(duì)外部作用域的訪問(wèn),即$str
    fun test() {
        m0(fun(): String { return "return a $str" })
    }
}

上述代碼生成的字節(jié)碼這里不再全部粘貼,直接撿重點(diǎn)的來(lái)看下:

  1. kotlin編譯器會(huì)為私有是成員str生成一個(gè)公有的訪問(wèn)方法,對(duì)應(yīng)字節(jié)碼如下所示:
//位于Test類中,由編譯器為我們自動(dòng)生成的訪問(wèn)str的共有方法
  public final static synthetic access$getStr$p(LTest;)Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 1 L0
    ALOAD 0
    GETFIELD Test.str : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE $this LTest; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  1. 在生成的新類中,會(huì)持有Test類的引用,并在實(shí)例構(gòu)造方法中將傳入的Test類對(duì)象賦值給了Test$test$1.this$0,對(duì)應(yīng)字節(jié)碼如下所示:
//位于生成的Test$test$1類中
  <init>(LTest;)V
    ALOAD 0
    ALOAD 1
//這里實(shí)際上將傳入的Test對(duì)象賦值給了Test$test$1.this$0
    PUTFIELD Test$test$1.this$0 : LTest;
    ALOAD 0
    ICONST_0
    INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 2
  1. 步驟2中的Test對(duì)象,實(shí)際上是在test方法調(diào)用的時(shí)候生成的,對(duì)應(yīng)的字節(jié)碼如下所示:
//test方法對(duì)應(yīng)的字節(jié)碼
  public final test()V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    NEW Test$test$1//生成一個(gè)新的Test$test$1對(duì)象
    DUP
    ALOAD 0
//這句字節(jié)碼表示傳入了Test對(duì)象this
    INVOKESPECIAL Test$test$1.<init> (LTest;)V
    CHECKCAST kotlin/jvm/functions/Function0
    INVOKEVIRTUAL Test.m0 (Lkotlin/jvm/functions/Function0;)V
  1. kotlin正式通過(guò)上述三個(gè)步驟完成了對(duì)閉包環(huán)境下的訪問(wèn),因?yàn)榉椒w實(shí)現(xiàn)都在新生成類中的invoke中,所以其對(duì)應(yīng)的字節(jié)碼如下所示:
  public final invoke()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 7 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "return a "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD Test$test$1.this$0 : LTest;
//這里完成了對(duì)str的訪問(wèn)
    INVOKESTATIC Test.access$getStr$p (LTest;)Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN

上面代碼最主要有兩句,這里再次摘錄如下:

//獲取Test$test$1.this$0實(shí)例
GETFIELD Test$test$1.this$0 : LTest;
//這里通過(guò)生成的共有方法access$getStr$p 完成了對(duì)str的訪問(wèn)
    INVOKESTATIC Test.access$getStr$p (LTest;)Ljava/lang/String;

至此,高階方法相關(guān)的原理分析完畢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容