美文网首页Kotlin开发指南深入浅出KotlinKotlin编程
浅谈Kotlin语法篇之lambda编译成字节码过程完全解析(七

浅谈Kotlin语法篇之lambda编译成字节码过程完全解析(七

作者: 熊喵先森 | 来源:发表于2018-04-28 21:43 被阅读46次

    简述: 今天带来的是Kotlin浅谈系列第七弹,上篇博客我们聊到关于Kotlin中的lambda表达式的一些语法规则和基本使用。然而我们并没有聊到Kotlin的lambda表达式的本质是什么?我们都知道使用Kotlin来开发Android,最终都会编译成字节码文件.class,然后字节码文件run到JVM上,最后整个应用跑起来。

    • 1、为什么需要去对lambda表达式字节码分析?(why)
    • 2、lambda表达式实质原理是什么?(what)
    • 3、lambda表达式字节码查看工具的使用
    • 4、kotlin中@Metadata注解详解
    • 5、如何去分析lambda表达式字节码(how)
    • 6、使用lambda表达式时的性能优化
    • 7、使用lambda表达式需要注意哪些问题

    一、为什么需要去对lambda表达式字节码分析?

    Kotlin中的lambda表达式给使用者的感知它就是一个很简洁的语法糖,但是在简洁语法糖背后内容你有所了解吗?学会分析lambda表达式编译成字节码的整个过程,会对高效率地去使用lambda表达式会有很大帮助,否则就会很容出现lambda表达式滥用的情况,这种情况非常影响程序性能。所以需要从真正本质上去完全理解lambda是什么?以及它是如何编译成对应class。

    二、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例子

    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调用处反编译后的代码

    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反编译后的代码

    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获得参数的元数和函数参数个数一致
        }
    }
    

    三、lambda表达式字节码查看工具的使用

    既然是分析字节码class文件,所以必须得有个字节码查看工具,你可以使用JD-GUI或者其他的,我这里使用的是BytecodeViewer,它核心实现也是基于JD-GUI的。任意选择一款字节码查看工具都可以,这里是BytecodeViewer的下载地址有需要可以去下载,它的使用也是非常简单的。

    • 1、下载完毕后,打开主界面(界面有点粗糙,这个可以忽略)
    image
    • 2、然后,只需要把相应的.class文件拖入文件选择区,就可以查看相应反编译的Java代码和字节码
    image

    四、kotlin中@Metadata注解详解

    从刚刚反编译的代码,有个@Metadata注解很是引入注意,它到底是个啥?每个Kotlin代码反编译成Java代码都会有这个@Metadata注解。为了更好地去理解这个字节码生成的过程,我觉得有必要去了解一下,它们每一个的含义。

    1、@Metadata注解介绍及生成流程

    kotlin中的@Metadata注解是一个很特殊的注解,它记录了Kotlin代码中的一些信息,比如 class 的可见性,function 的返回值,参数类型,property 的 lateinit,nullable 的属性,typealias类型别名声明等。我们都知道Kotlin代码最终都要转化成Java的字节码的,然后运行JVM上。但是Kotlin代码和Java代码差别还是很大的,一些Kotlin特殊语言特性是独有的(比如lateinit, nullable, typealias),所以需要记录一些信息来标识Kotlin中的一些特殊语法信息。最终这些信息都是有kotlinc编译器生成,并以注解的形式存在于字节码文件中。

    image

    2、@Metadata注解的状态

    通过上面转化图可得知,@Metadata注解会一直保存在class字节码中,也就是这个注解是一个运行时的注解,在RunTime的时候会一直保留,那么可以通过反射可以拿到,并且这个@Metadata注解是Kotlin独有的,也就是Java是不会生成这样的注解存在于.class文件中,也就是从另一方面可以通过反射可以得知这个类是不是Kotlin的class.

    3、@Metadata注解源码中每个参数的含义

    @Metadata注解源码

    
    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类完整的包名

    4、实例分析@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;
          }
       }
    }
    

    5、@Metadata注解需要注意的问题

    我们知道了@Metadata注解是会一直保留至运行时,而且在配置混淆一定需要注意,@Metadata注解保存的信息是会被proguard给干掉的,所以不能让proguard干掉,否则一些重要的信息就会丢失,那么这个.class文件就是无效的,run在JVM上是会抛异常的。

    五、如何去分析lambda表达式字节码

    • 1、从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表达式的调用
    }
    
    • 2、然后再重点介绍几个类和接口。

    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获得参数的元数和函数参数个数一致
        }
    }
    

    为了清晰表达类与类之间关系请看下面这张类图

    image

    调用处反编译的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方法。

    image

    六、使用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表达式字节码:

    image

    函数调用处生成的字节码

    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文件目录,只有一个调用处生成的字节码:

    image

    函数调用处生成的字节码

    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);
       }
    }
    
    

    七、使用lambda表达式需要注意哪些问题

    • 1、在使用proguard的时候需要注意不要将@Metadata注解中信息给混淆了,否则会有异常抛出。
    • 2、在使用高阶函数时,尽量去使用inline函数,降低类生成和类的实例创建的开销。关于内联函数,我们会在接下来博客一一详细介绍。
    qrcode_for_gh_109398d5e616_430.jpg

    欢迎关注Kotlin开发者联盟,这里有最新Kotlin技术文章,每周会不定期翻译一篇Kotlin国外技术文章。如果你也喜欢Kotlin,欢迎加入我们~~~

    相关文章

      网友评论

      • 周公在世:另外请请教一下, kotlin 编译后的 class 文件位于什么目录下?
        熊喵先森:@周公在世 :+1:
        周公在世:@mikyou 找到了,多谢。使用的 Android Studio 3.2 RC 3, 目录为 build/tmp/kotlin-classes/debug。
        熊喵先森:@周公在世 如果你是用Intellij IDEA 建立的普通Kotlin工程的话,在项目目录下out/production/classes目录下,如果是AndroidStudio的android项目就在build/kotlin-classes/debug目录下
      • 周公在世:非常感谢你的分享,对我十分有帮助。
      • IT人故事会:看完楼主的这个帖子之后,学习了谢谢!
        熊喵先森:@IT人故事会 不客气,欢迎关注,一起学习,一起交流。

      本文标题:浅谈Kotlin语法篇之lambda编译成字节码过程完全解析(七

      本文链接:https://www.haomeiwen.com/subject/azunlftx.html