淺談Kotlin語法篇之lambda編譯成字節碼過程徹底解析(七)

簡述: 今天帶來的是Kotlin淺談系列第七彈,上篇博客咱們聊到關於Kotlin中的lambda表達式的一些語法規則和基本使用。然而咱們並無聊到Kotlin的lambda表達式的本質是什麼?咱們都知道使用Kotlin來開發Android,最終都會編譯成字節碼文件.class,而後字節碼文件run到JVM上,最後整個應用跑起來。java

  • 一、爲何須要去對lambda表達式字節碼分析?(why)
  • 二、lambda表達式實質原理是什麼?(what)
  • 三、lambda表達式字節碼查看工具的使用
  • 四、kotlin中@Metadata註解詳解
  • 五、如何去分析lambda表達式字節碼(how)
  • 六、使用lambda表達式時的性能優化
  • 七、使用lambda表達式須要注意哪些問題

1、爲何須要去對lambda表達式字節碼分析?

Kotlin中的lambda表達式給使用者的感知它就是一個很簡潔的語法糖,可是在簡潔語法糖背後內容你有所瞭解嗎?學會分析lambda表達式編譯成字節碼的整個過程,會對高效率地去使用lambda表達式會有很大幫助,不然就會很容出現lambda表達式濫用的狀況,這種狀況很是影響程序性能。因此須要從真正本質上去徹底理解lambda是什麼?以及它是如何編譯成對應class。git

2、lambda表達式實質原理

Kotlin中的lambda表達式實際上最後會編譯爲一個class類,這個類會去繼承Kotlin中Lambda的抽象類(在kotlin.jvm.internal包中)而且實現一個FunctionN(在kotlin.jvm.functions包中)的接口(這個N是根據lambda表達式傳入參數的個數決定的,目前接口N的取值爲 0 <= N <= 22,也就是lambda表達式中函數傳入的參數最多也只能是22個),這個Lambda抽象類是實現了FunctionBase接口,該接口中有兩個方法一個是getArity()獲取lambda參數的元數,toString()實際上就是打印出Lambda表達式類型字符串,獲取Lambda表達式類型字符串是經過Java中Reflection類反射來實現的。FunctionBase接口繼承了Function,Serializable接口。來看一個簡單的lambda例子github

package com.mikyou.kotlin.lambda.simple

typealias Sum = (Int, Int, Int) -> Int

fun main(args: Array<String>) {
    val sum: Sum = { a, b, c ->//定義一個很簡單的三個數求和的lambda表達式
        a + b + c
    }

    println(sum.invoke(1, 2, 3))
}
複製代碼

lambda調用處反編譯後的代碼express

package com.mikyou.kotlin.lambda.simple;

import kotlin.Metadata;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 10},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005*:\u0010\u0006\"\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}
)
public final class SumLambdaKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      Function3 sum = (Function3)null.INSTANCE;//實例化的是FunctionN接口中Function3,由於有三個參數
      int var2 = ((Number)sum.invoke(1, 2, 3)).intValue();
      System.out.println(var2);
   }
}

複製代碼

lambda反編譯後的代碼apache

package com.mikyou.kotlin.lambda.simple;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\n\n\000\n\002\020\b\n\002\b\004\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\0012\006\020\004\032\0020\001H\n¢\006\002\b\005"}, d2 = {"<anonymous>", "", "a", "b", "c", "invoke"})
final class SumLambdaKt$main$sum$1 extends Lambda implements kotlin.jvm.functions.Function3<Integer, Integer, Integer, Integer> {
    public final int invoke(int a, int b, int c) {
        return a + b + c;
    }

    public static final SumLambdaKt$main$sum$1 INSTANCE =new SumLambdaKt$main$sum$1();

    SumLambdaKt$main$sum$1() {
        super(3);//這個super傳入3,也就是前面getArity得到參數的元數和函數參數個數一致
    }
}
複製代碼

3、lambda表達式字節碼查看工具的使用

既然是分析字節碼class文件,因此必須得有個字節碼查看工具,你可使用JD-GUI或者其餘的,我這裏使用的是BytecodeViewer,它核心實現也是基於JD-GUI的。任意選擇一款字節碼查看工具均可以,這裏是BytecodeViewer的下載地址有須要能夠去下載,它的使用也是很是簡單的。性能優化

  • 一、下載完畢後,打開主界面(界面有點粗糙,這個能夠忽略)

  • 二、而後,只須要把相應的.class文件拖入文件選擇區,就能夠查看相應反編譯的Java代碼和字節碼

4、kotlin中@Metadata註解詳解

從剛剛反編譯的代碼,有個@Metadata註解非常引入注意,它究竟是個啥?每一個Kotlin代碼反編譯成Java代碼都會有這個@Metadata註解。爲了更好地去理解這個字節碼生成的過程,我以爲有必要去了解一下,它們每個的含義。app

一、@Metadata註解介紹及生成流程

kotlin中的@Metadata註解是一個很特殊的註解,它記錄了Kotlin代碼中的一些信息,好比 class 的可見性,function 的返回值,參數類型,property 的 lateinit,nullable 的屬性,typealias類型別名聲明等。咱們都知道Kotlin代碼最終都要轉化成Java的字節碼的,而後運行JVM上。可是Kotlin代碼和Java代碼差異仍是很大的,一些Kotlin特殊語言特性是獨有的(好比lateinit, nullable, typealias),因此須要記錄一些信息來標識Kotlin中的一些特殊語法信息。最終這些信息都是有kotlinc編譯器生成,並以註解的形式存在於字節碼文件中。less

二、@Metadata註解的狀態

經過上面轉化圖可得知,@Metadata註解會一直保存在class字節碼中,也就是這個註解是一個運行時的註解,在RunTime的時候會一直保留,那麼能夠經過反射能夠拿到,而且這個@Metadata註解是Kotlin獨有的,也就是Java是不會生成這樣的註解存在於.class文件中,也就是從另外一方面能夠經過反射能夠得知這個類是否是Kotlin的class.jvm

三、@Metadata註解源碼中每一個參數的含義

@Metadata註解源碼ide

package kotlin

/** * This annotation is present on any class file produced by the Kotlin compiler and is read by the compiler and reflection. * Parameters have very short names on purpose: these names appear in the generated class files, and we'd like to reduce their size. */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
internal annotation class Metadata(
        /** * A kind of the metadata this annotation encodes. Kotlin compiler recognizes the following kinds (see KotlinClassHeader.Kind): * * 1 Class * 2 File * 3 Synthetic class * 4 Multi-file class facade * 5 Multi-file class part * * The class file with a kind not listed here is treated as a non-Kotlin file. */
        val k: Int = 1,
        /** * The version of the metadata provided in the arguments of this annotation. */
        val mv: IntArray = intArrayOf(),
        /** * The version of the bytecode interface (naming conventions, signatures) of the class file annotated with this annotation. */
        val bv: IntArray = intArrayOf(),
        /** * Metadata in a custom format. The format may be different (or even absent) for different kinds. */
        val d1: Array<String> = arrayOf(),
        /** * An addition to [d1]: array of strings which occur in metadata, written in plain text so that strings already present * in the constant pool are reused. These strings may be then indexed in the metadata by an integer index in this array. */
        val d2: Array<String> = arrayOf(),
        /** * An extra string. For a multi-file part class, internal name of the facade class. */
        val xs: String = "",
        /** * Fully qualified name of the package this class is located in, from Kotlin's point of view, or empty string if this name * does not differ from the JVM's package FQ name. These names can be different in case the [JvmPackageName] annotation is used. * Note that this information is also stored in the corresponding module's `.kotlin_module` file. */
        val pn: String = "",
        /** * An extra int. Bits of this number represent the following flags: * * 0 - this is a multi-file class facade or part, compiled with `-Xmultifile-parts-inherit`. * 1 - this class file is compiled by a pre-release version of Kotlin and is not visible to release versions. * 2 - this class file is a compiled Kotlin script source file (.kts). */
        @SinceKotlin("1.1")
        val xi: Int = 0
)
複製代碼

注意: @Metadata註解中的k,mv,d1,d2..都是簡寫,爲何要這樣作呢?說白了就是爲了class文件的大小,儘量作到精簡。

參數簡寫名稱 參數全稱 參數類型 參數取值 參數含義
k kind Int 1: class,表示這個kotlin文件是一個類或者接口
2: file,表示這個kotin文件是一個.kt結尾的文件
3: Synthetic class,表示這個kotlin文件是一個合成類
4(Multi-file class facade)
5(Multi-file class part)
表示當前metadata註解編碼種類
mv metadata version IntArray - metadata版本號
bv bytecode version IntArray - 字節碼版本號
d1 data1 Array<String> - 主要記錄Kotlin語法信息
d2 data2 Array<String> - 主要記錄Kotlin語法信息
xs extra String String - 主要是爲多文件的類(Multi-file class)預留的名稱
xi extra Int Int 0 (表示一個多文件的類Multi-file class facade或者多文件類的部分Multi-file class part編譯成-Xmultifile-parts-inherit)
1 (表示此類文件由Kotlin的預發行版本編譯,而且對於發行版本不可見)
2 (表示這個類文件是一個編譯的Kotlin腳本源文件)
-
pn fully qualified name of package String - 主要記錄kotlin類完整的包名

四、實例分析@Metadata註解

kotlin源碼,定義的是一個.kt結尾文件

package com.mikyou.kotlin.lambda.simple

typealias Sum = (Int, Int, Int) -> Int//typealias關鍵字聲明lambda表達式類型別名

fun main(args: Array<String>) {
    val sum: Sum = { a, b, c ->//定義一個很簡單的三個數求和的lambda表達式
        a + b + c
    }

    println(sum.invoke(1, 2, 3))
}
複製代碼

反編譯後Java代碼

package com.mikyou.kotlin.lambda.simple;

import kotlin.Metadata;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 10},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005*:\u0010\u0006\"\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}
)
public final class SumLambdaKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      Function3 sum = (Function3)SumLambdaKt$main$sum$1.INSTANCE;
      int var2 = ((Number)sum.invoke(1, 2, 3)).intValue();
      System.out.println(var2);
   }
}
複製代碼
  • k = 2 表示的是這是一個.kt結尾的kotlin文件
  • mv = {1,1,10} 表示metadata版本號是1.1.10
  • bv = {1,0,2} 表示bytecode版本號是1.0.2
  • d1 = {...} 裏面的信息是通過了 protobuf 編碼的二進制流 具體可見詳細描述
  • d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}表示記錄有main函數,已經main函數參數名稱args,Sum就是經過typealias取lambda表達式類型別名,Lkotlin/Function3聲明的是Lambda表達式中生成的這個類是實現了FunctionN中Function3接口,由於它對應只有三個參數

kotlin中定義一個類源碼

package com.mikyou.kotlin.lambda

/** * Created by mikyou on 2018/3/27. */
data class Person(val name: String, val age: Int)
複製代碼

反編譯後Java代碼

package com.mikyou.kotlin.lambda;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 10},
   bv = {1, 0, 2},
   k = 1,//kind就是變成了1,表示這是一個kotlin的class類
   d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\t\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006J\t\u0010\u000b\u001a\u00020\u0003HÆ\u0003J\t\u0010\f\u001a\u00020\u0005HÆ\u0003J\u001d\u0010\r\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u000e\u001a\u00020\u000f2\b\u0010\u0010\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0011\u001a\u00020\u0005HÖ\u0001J\t\u0010\u0012\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\n¨\u0006\u0013"},
   d2 = {"Lcom/mikyou/kotlin/lambda/Person;", "", "name", "", "age", "", "(Ljava/lang/String;I)V", "getAge", "()I", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString", "production sources for module Lambda_main"}
)
public final class Person {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public Person(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }

   @NotNull
   public final Person copy(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      return new Person(name, age);
   }

   // $FF: synthetic method
   @NotNull
   public static Person copy$default(Person var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.age;
      }

      return var0.copy(var1, var2);
   }

   public String toString() {
      return "Person(name=" + this.name + ", age=" + this.age + ")";
   }

   public int hashCode() {
      return (this.name != null ? this.name.hashCode() : 0) * 31 + this.age;
   }

   public boolean equals(Object var1) {
      if (this != var1) {
         if (var1 instanceof Person) {
            Person var2 = (Person)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}
複製代碼

五、@Metadata註解須要注意的問題

咱們知道了@Metadata註解是會一直保留至運行時,並且在配置混淆必定須要注意,@Metadata註解保存的信息是會被proguard給幹掉的,因此不能讓proguard幹掉,不然一些重要的信息就會丟失,那麼這個.class文件就是無效的,run在JVM上是會拋異常的。

5、如何去分析lambda表達式字節碼

  • 一、從Kotlin源碼出發定義了一個三個數求和lambda表達式最後經過invoke()方法傳入三個參數完成lambda表達式的調用。
package com.mikyou.kotlin.lambda.simple

typealias Sum = (Int, Int, Int) -> Int //給類型取個別名

fun main(args: Array<String>) {
    val sum: Sum = { a, b, c ->//定義一個很簡單的三個數求和的lambda表達式
        a + b + c
    }

    println(sum.invoke(1, 2, 3))//invoke方法實現lambda表達式的調用
}
複製代碼
  • 二、而後再重點介紹幾個類和接口。

Lambda抽象類:

/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */

package kotlin.jvm.internal

public abstract class Lambda(private val arity: Int) : FunctionBase {
    override fun getArity() = arity//實現FunctionBase接口中的抽象方法getArity(),並經過構造器把元數傳給它

    override fun toString() = Reflection.renderLambdaToString(this)//toString()方法重寫
}
複製代碼

FunctionBase接口,繼承了Function<R>接口和Serializable序列化接口(這是一個Java接口):

/* * Copyright 2010-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */

package kotlin.jvm.internal;

import kotlin.Function;

import java.io.Serializable;

public interface FunctionBase extends Function, Serializable {
    int getArity();//抽象方法getArity
}
複製代碼

FunctionN系列的接口( 0 <= N <= 22)也繼承了Function<R>接口,也就是目前支持的lambda表達式類型接收參數的個數不能超過22個,是否是忽然感受kotlin竟然還有這種操做。而且每一個接口中都有一個invoke抽象方法,用於外部調用lambda表達式。

/* * Copyright 2010-2018 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */

// Auto-generated file. DO NOT EDIT!

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
}
/** A function that takes 3 arguments. */
public interface Function3<in P1, in P2, in P3, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3): R
}
/** A function that takes 4 arguments. */
public interface Function4<in P1, in P2, in P3, in P4, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4): R
}
/** A function that takes 5 arguments. */
public interface Function5<in P1, in P2, in P3, in P4, in P5, 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): R
}
/** A function that takes 6 arguments. */
public interface Function6<in P1, in P2, in P3, in P4, in P5, in P6, 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): R
}
/** A function that takes 7 arguments. */
public interface Function7<in P1, in P2, in P3, in P4, in P5, in P6, in P7, 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): R
}
/** A function that takes 8 arguments. */
public interface Function8<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, 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): R
}
/** A function that takes 9 arguments. */
public interface Function9<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, 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): R
}
/** A function that takes 10 arguments. */
public interface Function10<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, 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): R
}
/** A function that takes 11 arguments. */
public interface Function11<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, 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): R
}
/** A function that takes 12 arguments. */
public interface Function12<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, 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): R
}
/** A function that takes 13 arguments. */
public interface Function13<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, 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): R
}
/** A function that takes 14 arguments. */
public interface Function14<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, 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): R
}
/** A function that takes 15 arguments. */
public interface Function15<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, 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): R
}
/** A function that takes 16 arguments. */
public interface Function16<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, 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): R
}
/** A function that takes 17 arguments. */
public interface Function17<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, 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): R
}
/** A function that takes 18 arguments. */
public interface Function18<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, 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): R
}
/** A function that takes 19 arguments. */
public interface Function19<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, 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): R
}
/** A function that takes 20 arguments. */
public interface Function20<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, 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): R
}
/** 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
}

複製代碼

Function<R>接口,咱們都知道在kotlin中是能夠把函數當作值來看待的,那麼這個函數值也是有類型的,也就是函數類型,這個Function就是lambda、匿名函數、普通命名函數的函數引用類型。

/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */

package kotlin

/** * Represents a value of a functional type, such as a lambda, an anonymous function or a function reference. * * @param R return type of the function. */
public interface Function<out R>
複製代碼

lambda編譯後生成的class,經過生成字節碼中的類能夠看出, 生成惟一的類名是這個lambda表達式聲明處於哪一個頂層文件(它生成規則以前博客有提到),哪一個方法中,以及最終lambda表達式的名字組成, SumLambdaKt$main$sum$1, SumLambdaKt頂層文件名,在main函數聲明的,lambda表達式名爲sum,因爲Kotlin中的lambda只有三個參數,那麼這個Lambda類的arity元數也就是3,能夠看到生成的類中構造器super(3)。而後就去實現了對應FunctionN接口也就是N=3,實現了Function3接口,重寫了invoke方法。

package com.mikyou.kotlin.lambda.simple;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\n\n\000\n\002\020\b\n\002\b\004\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\0012\006\020\004\032\0020\001H\n¢\006\002\b\005"}, d2 = {"<anonymous>", "", "a", "b", "c", "invoke"})
final class SumLambdaKt$main$sum$1 extends Lambda implements kotlin.jvm.functions.Function3<Integer, Integer, Integer, Integer> {
    public final int invoke(int a, int b, int c) {
        return a + b + c;
    }

    public static final SumLambdaKt$main$sum$1 INSTANCE =new SumLambdaKt$main$sum$1();//靜態SumLambdaKt$main$sum$1的實例INSTANCE供外部調用

    SumLambdaKt$main$sum$1() {
        super(3);//這個super傳入3,也就是前面getArity得到參數的元數和函數參數個數一致
    }
}
複製代碼

爲了清晰表達類與類之間關係請看下面這張類圖

調用處反編譯的Java代碼

package com.mikyou.kotlin.lambda.simple;

import kotlin.Metadata;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 10},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005*:\u0010\u0006\"\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}
)
public final class SumLambdaKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      Function3 sum = (Function3)SumLambdaKt$main$sum$1.INSTANCE;//調用靜態SumLambdaKt$main$sum$1的實例INSTANCE,強轉成Function3類型。
      int var2 = ((Number)sum.invoke(1, 2, 3)).intValue();//而後用這個sum對象實例去調用Function3中三個參數的invoke方法
      System.out.println(var2);
   }
}

複製代碼

最後咱們再梳理一下整個編譯的流程:

首先,定義好的Kotlin Lambda表達式,經過Lambda表達式的類型,能夠獲得參數的個數以及參數的類型,也就是向Lambda抽象類的構造器傳遞arity元數,Lambda抽象類又把arity傳遞給FunctionBase,在編譯時期會根據這個arity元數動態肯定須要實現FunctionN接口,而後經過實現了相應的FunctionN接口中的invoke方法,最後lambda表達式函數體內代碼邏輯將會在invoke方法體內。整個編譯的流程完畢,也就在本地目錄會生成一個.class字節碼文件。從調用處反編譯的代碼就會直接調用.class字節碼中已經生成的類中的INSTANCE靜態實例對象,最後經過這個實例去調用invoke方法。

6、使用lambda表達式時的性能優化

咱們都知道lambda表達式能夠做爲一個參數傳入到另外一個函數中,這個也稱爲高階函數。在使用高階函數的時候咱們須要注意儘可能把咱們的高階函數聲明成inline內聯函數,由於經過上面的字節碼分析的編譯過程知道lambda最終會被編譯成一個FunctionN類,而後調用的地方是使用這個FunctionN的實例去調用相應invoke方法。若是聲明成內聯函數的話,那麼將不會去生成這個類而且在函數調用處是不須要實例化這個FunctionN的實例,而是在調用的時把調用的方法給替換上去,能夠下降很大的性能開銷。一塊兒來看個例子

沒有設置inline函數的case:

package com.mikyou.kotlin.lambda.high

typealias SumAlias = (Int, Int) -> Int

fun printSum(a: Int, b: Int, block: SumAlias) {//沒有設置inline關鍵字
    println(block.invoke(a, b))
}

fun main(args: Array<String>) {
    printSum(4, 5) { a, b ->
        a + b
    }
}
複製代碼

編譯生成.clas文件目錄,一個是調用處生成的字節碼,另外一處則是聲明Lambda表達式字節碼:

函數調用處生成的字節碼

package com.mikyou.kotlin.lambda.high;

import kotlin.Metadata;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 10},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000(\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a4\u0010\u0006\u001a\u00020\u00012\u0006\u0010\u0007\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\b2\u001c\u0010\n\u001a\u0018\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000bj\u0002`\f*.\u0010\r\"\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b2\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b¨\u0006\u000e"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "printSum", "a", "", "b", "block", "Lkotlin/Function2;", "Lcom/mikyou/kotlin/lambda/high/SumAlias;", "SumAlias", "production sources for module Lambda_main"}
)
public final class SumHighFuntionKt {
   public static final void printSum(int a, int b, @NotNull Function2 block) {
      Intrinsics.checkParameterIsNotNull(block, "block");
      int var3 = ((Number)block.invoke(a, b)).intValue();
      System.out.println(var3);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      printSum(4, 5, (Function2)null.INSTANCE);//傳入了Function2類型INSTANCE實例到printSum函數中
   }
}
複製代碼

lambda聲明處反編譯的代碼

package com.mikyou.kotlin.lambda.high;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\n\n\000\n\002\020\b\n\002\b\003\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\001H\n¢\006\002\b\004"}, d2 = {"<anonymous>", "", "a", "b", "invoke"})
final class SumHighFuntionKt$main$1 extends Lambda implements kotlin.jvm.functions.Function2<Integer, Integer, Integer> {
    public static final 1INSTANCE =new 1();

    public final int invoke(int a, int b) {
        return a + b;
    }

    SumHighFuntionKt$main$1() {
        super(2);
    }
}

複製代碼

設置inline函數的case:

package com.mikyou.kotlin.lambda.high

typealias SumAlias = (Int, Int) -> Int

inline fun printSum(a: Int, b: Int, block: SumAlias) {//設置inline,printSum爲內聯函數
    println(block.invoke(a, b))
}

fun main(args: Array<String>) {
    printSum(4, 5) { a, b ->
        a + b
    }
}
複製代碼

編譯生成.clas文件目錄,只有一個調用處生成的字節碼:

函數調用處生成的字節碼

package com.mikyou.kotlin.lambda.high;

import kotlin.Metadata;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 10},
   bv = {1, 0, 2},
   k = 2,
   d1 = {"\u0000(\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a7\u0010\u0006\u001a\u00020\u00012\u0006\u0010\u0007\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\b2\u001c\u0010\n\u001a\u0018\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000bj\u0002`\fH\u0086\b*.\u0010\r\"\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b2\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b¨\u0006\u000e"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "printSum", "a", "", "b", "block", "Lkotlin/Function2;", "Lcom/mikyou/kotlin/lambda/high/SumAlias;", "SumAlias", "production sources for module Lambda_main"}
)
public final class SumHighFuntionKt {
   public static final void printSum(int a, int b, @NotNull Function2 block) {
      Intrinsics.checkParameterIsNotNull(block, "block");
      int var4 = ((Number)block.invoke(a, b)).intValue();
      System.out.println(var4);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      //下面調用地方直接是把傳入block替換到該調用處執行,根本就不須要了Function2實例對象,這樣會下降類建立和生成的開銷。
      byte a$iv = 4;
      int b$iv = 5;
      int var4 = a$iv + b$iv;
      System.out.println(var4);
   }
}

複製代碼

7、使用lambda表達式須要注意哪些問題

  • 一、在使用proguard的時候須要注意不要將@Metadata註解中信息給混淆了,不然會有異常拋出。
  • 二、在使用高階函數時,儘可能去使用inline函數,下降類生成和類的實例建立的開銷。關於內聯函數,咱們會在接下來博客一一詳細介紹。

歡迎關注Kotlin開發者聯盟,這裏有最新Kotlin技術文章,每週會不按期翻譯一篇Kotlin國外技術文章。若是你也喜歡Kotlin,歡迎加入咱們~~~

相關文章
相關標籤/搜索