Description: This is the seventh in Kotlin’s series on lambda expressions. In the last blog we talked about the syntax and basic usage of lambda expressions in Kotlin. What we haven’t talked about, however, is the nature of Kotlin’s lambda expressions. We all know that when you develop Android using Kotlin, you end up compiling it into a bytecode file. Class, and then the bytecode file runs into the JVM, and then the entire application runs.

  • 1. Why do WE need to analyze lambda expressions bytecode? (why)
  • 2. What is the essential principle of lambda expressions? (what)
  • 3. Use of lambda expression bytecode viewing tools
  • 4, Kotlin @metadata annotation details
  • 5, How to analyze lambda expression bytecode (how)
  • Performance optimization when using lambda expressions
  • 7. What should be noted when using lambda expressions

Why do WE need to analyze lambda expressions bytecode?

The lambda expression in Kotlin is perceived by the user as a very concise syntax sugar, but what do you know about the concise syntax sugar behind it? Learning to analyze the entire process of compiling lambda expressions into bytecode can be very helpful in using lambda expressions efficiently, otherwise it is very easy to abuse lambda expressions, which can affect program performance. So you need to understand exactly what lambda is in its true nature, right? And how it compiles to the corresponding class.

The essence of lambda expressions

Lambda expressions in Kotlin actually end up compiling into a class, This class inherits the abstract classes of Lambda in Kotlin (in the kotlin.jvm.internal package) and implements an interface to FunctionN(in the kotlin.jvm.functions package) (the N is based on the number of arguments passed to Lambda expressions, for now GetArity () : getArity() : getArity() : getArity() : getArity() : getArity() : getArity() : getArity() : getArity() ToString () essentially prints out a Lambda expression type string, which is obtained through Reflection of the Java Reflection class. The FunctionBase interface inherits the Function and Serializable interfaces. Consider a simple lambda example

package com.mikyou.kotlin.lambda.simple

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

fun main(args: Array<String>) {
    val sum: Sum = { a, b, c ->// Define a simple lambda expression for the sum of three numbers
        a + b + c
    }

    println(sum.invoke(1.2.3))}Copy the code

Decompiled code at the lambda call

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 \ \ u0012 b \u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u00 12 \ 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;// Instantiate Function3 in the FunctionN interface because there are three parameters
      int var2 = ((Number)sum.invoke(1.2.3)).intValue(); System.out.println(var2); }}Copy the code

Lambda decompiled code

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\001 h \ n ¢\ 006\002 \ \ 005 b"}, d2 = {"<anonymous>".""."a"."b"."c"."invoke"})
final class SumLambdaKt$main$sumThe $1extends 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(a); SumLambdaKt$main$sum$1() {
        super(3);// This super passes in 3, which is the same number of arguments getArity got}}Copy the code

Use of lambda expression bytecode viewing tools

You can use JD-GUI or something else. Here I use BytecodeViewer, whose core implementation is also based on JD-GUI. You can choose any BytecodeViewer. Here is the download address for BytecodeViewer. It is very simple to use.

  • 1. After downloading, open the main interface (the interface is a little rough, this can be ignored)

  • 2. Then, just drag the corresponding.class file into the file selection area to see the corresponding decomcompiled Java code and bytecode

Kotlin @metadata annotation in detail

The @metadata annotation is very interesting from the decompilated code. What is it? Every Kotlin code decompiles into Java code with this @metadata annotation. In order to better understand the bytecode generation process, I think it’s important to understand what each of them means.

1, @metadata annotation introduction and generation process

The @metadata annotation in Kotlin is a special annotation that records information about the Kotlin code, such as class visibility, function return values, parameter types, property lateInit, nullable attributes, Typealias typealias declaration. We all know that Kotlin code is eventually converted to Java bytecode and then run on the JVM. However, Kotlin code is quite different from Java code, and some of the Kotlin language-specific features are unique to Kotlin (e.g., LateInit, Nullable, TypeAlias), so you need to log information to identify specific syntax information in Kotlin. The resulting information is generated by the Kotlinc compiler and exists as annotations in bytecode files.

2. State of @metadata annotation

As you can see from the transformation diagram above, the @metadata annotation is always stored in the class bytecode. That is, the annotation is a RunTime annotation and is available at RunTime through reflection. The @metadata annotation is unique to Kotlin. Java does not generate such annotations in.class files, so reflection, on the other hand, can tell whether the class is Kotlin’s class.

3, @metadata annotation source code for the meaning of each parameter

@metadata annotated source code


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
)
Copy the code

Note: @Metadata annotation k,mv,d1,d2.. It’s all shorthand. Why do you do that? In other words, keep the class file size as small as possible.

Parameter abbreviation name Parameter name The parameter types Parameter selection Parameter meaning
k kind Int 1: class, indicating that the kotlin file is a class or interface

2: file, indicating that the kotin file is a.kt ending file

Synthetic class, which means that the Kotlin file is a Synthetic class

4(Multi-file class facade)

5(Multi-file class part)
Represents the current metadata annotation encoding type
mv metadata version IntArray The metadata version number
bv bytecode version IntArray Bytecode version number
d1 data1 Array<String> Mainly record Kotlin syntax information
d2 data2 Array<String> Mainly record Kotlin syntax information
xs extra String String The name is reserved for multi-file classes
xi extra Int Int 0 (represents a multi-file class facade or multi-file class part of a multi-file class compiled to -xmultifile-parts-inherit)

1 (indicates that such files are compiled by a pre-release version of Kotlin and are not visible to the release)

2 (indicates that the class file is a compiled Kotlin script file)
pn fully qualified name of package String It records the complete package name of the Kotlin class

4, example analysis @metadata annotation

Kotlin source code, defined is a.kt ending file

package com.mikyou.kotlin.lambda.simple

typealias Sum = (Int.Int.Int) - >Int// the typealias keyword declares a lambda expression typealias

fun main(args: Array<String>) {
    val sum: Sum = { a, b, c ->// Define a simple lambda expression for the sum of three numbers
        a + b + c
    }

    println(sum.invoke(1.2.3))}Copy the code

Decompiled Java code

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 \ \ u0012 b \u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u00 12 \ 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); }}Copy the code
  • K equals 2 means that this is a kotlin file ending in.kt
  • Mv = {1,1,10} indicates that the metadata version is 1.1.10
  • Bv = {1,0,2} indicates that the bytecode version is 1.0.2
  • d1 = {… } the information inside is protobuf encoded binary stream concrete visibleA detailed description
  • d2 = {“main”, “”, “args”, “”, “”, “([Ljava/lang/String;)V”, “Sum”, “Lkotlin/Function3;”, “”, “Production sources for module Lambda_main”} “production sources for module Lambda_main”} This class implements the Function3 interface in FunctionN, since it corresponds to only three parameters

Kotlin defines a class source code

package com.mikyou.kotlin.lambda

/** * Created by mikyou on 2018/3/27. */
data class Person(val name: String, val age: Int)
Copy the code

Decompiled Java code

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 becomes 1, indicating that this is a 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\u 000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u 0010 \ u0004 \ u001a \ u00020 \ u0005 ¢\ u0006 \ u0002 \ u0010 \ u0006J \ t \ u0010 \ u000b \ u001a \ u00020 \ u0003H Æ \ \ u0010 u0003J \ t \ \ u001a \ u00020 f \ u0005H Æ \ u0003J \ u001d \ u0010 \ r \ u001a \ u00020 \ u00002 \ \ b \ u0002 \ b u0010 \ u0002 \ u001a \ u00020 \ u00032 \ \ b \ u0002 \ b u0010 \ u0004 \ u001a \ U00020 \ u0005H Æ \ u0001J \ u0013 \ u0010 \ u000e \ u001a \ u00020 \ u000f2 \ \ b u0010 \ u0010 \ u001a \ u0004 \ u0018 \ u00010 \ \ u001 u0001HO \ u0003J \ t 0 \ u0011 \ u001a \ u00020 \ u0005HO \ u0001J \ \ t u0010 \ u0012 \ u001a \ u00020 \ u0003HO \ u0001R \ u0011 \ u0010 \ u0004 \ u001a \ u00020 \ u0005 ¢\ u000 6 b \ \ n \ u0000 \ u001a \ u0004 \ b \ u0007 \ u0010 \ bR \ u0011 \ u0010 \ u0002 \ u001a \ u00020 \ u0003 ¢\ u0006 \ b \ n \ u0000 \ u001a \ u0004 \ \ t b \ 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(a) {
      return this.name;
   }

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

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

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

   public final int component2(a) {
      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(a) {
      return "Person(name=" + this.name + ", age=" + this.age + ")";
   }

   public int hashCode(a) {
      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; }}}Copy the code

5, @metadata annotation needs to pay attention to the issues

The @metadata annotation will be retained until runtime, and it is important to note that the @metadata annotation will be deleted by ProGuard if configuration confusion occurs. The.class file is invalid, and run throws exceptions on the JVM.

How to analyze lambda expression bytecode

  • 1. Define a three-number summation lambda expression from Kotlin’s source code. Finally, invoke() is passed three parameters to complete the invocation of the lambda expression.
package com.mikyou.kotlin.lambda.simple

typealias Sum = (Int.Int.Int) - >Int // Give the type an individual name

fun main(args: Array<String>) {
    val sum: Sum = { a, b, c ->// Define a simple lambda expression for the sum of three numbers
        a + b + c
    }

    println(sum.invoke(1.2.3))// The invoke method implements calls to lambda expressions
}
Copy the code
  • 2. I’ll then focus on a few classes and interfaces.

Lambda abstract class:

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(a) = arity// Implement the abstract method getArity() in the FunctionBase interface and pass the metadata to it through the constructor

    override fun toString(a) = Reflection.renderLambdaToString(this)//toString() method overridden
}
Copy the code

The FunctionBase interface, which inherits the Function<R> interface and the Serializable interface (which is a Java interface):

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(a);// Abstract method getArity
}
Copy the code

The FunctionN family of interfaces (0 <= N <= 22) also inherits the Function<R> interface, which currently supports lambda expressions that accept no more than 22 arguments. And each interface has an Invoke abstract method for externally calling lambda expressions.

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(a): 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
}

Copy the code

The Function<R> interface, as we all know in Kotlin, can treat a Function as a value, so the Function value is also typed, that is, the Function type, this Function is lambda, anonymous Function, ordinary named Function reference type.

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>
Copy the code

The class generated after a lambda is compiled, as can be seen from the class generated in the bytecode. The only class name generated is the top-level file in which the lambda expression declaration resides (as mentioned in the previous blog), the method in which it is generated, and the name of the final lambda expression. SumLambdaKt$main$sum$1 = SumLambdaKt$main$sum$1 = SumLambdaKt$main$sum$1 = SumLambdaKt$main$sum$1 = SumLambdaKt$main$sum$1 = SumLambdaKt$main$sum$1 = SumLambdaKt$main$sum$1 You can see the constructor super(3) in the generated class. We then implement the corresponding FunctionN interface (N=3), implement the Function3 interface, and override the invoke method.

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\001 h \ n ¢\ 006\002 \ \ 005 b"}, d2 = {"<anonymous>".""."a"."b"."c"."invoke"})
final class SumLambdaKt$main$sumThe $1extends 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(a);// Static SumLambdaKt$main$sum$1 INSTANCE for external calls

    SumLambdaKt$main$sum$1() {
        super(3);// This super passes in 3, which is the same number of arguments getArity got}}Copy the code

To clarify the relationship between classes, see the class diagram below

Java code decompiled at the invocation

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 \ \ u0012 b \u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u00 12 \ 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;// Call the static INSTANCE of SumLambdaKt$main$sum$1, forcefully converted to Function3.
      int var2 = ((Number)sum.invoke(1.2.3)).intValue();// The sum object instance is then used to invoke the invoke method of the three arguments in Function3System.out.println(var2); }}Copy the code

Finally, let’s review the entire compilation process:

The type of the Kotlin Lambda expression can be used to obtain the number of arguments and the types of the arguments. The constructor of the Lambda abstract class is passed the arity tuple. The Lambda abstract class in turn passes arity to FunctionBase. At compile time, the arity element is used to dynamically determine the need to implement the FunctionN interface. Then, by implementing the invoke method in the corresponding FunctionN interface, Finally, the code logic inside the lambda expression function will be inside the Invoke method. At the end of the compilation process, a.class bytecode file is generated in the local directory. The decomcompiled code from the invocation directly calls the INSTANCE static INSTANCE object of the class already generated in the.class bytecode, and finally invokes the invoke method through this INSTANCE.

Performance optimization when using lambda expressions

We all know that lambda expressions can be passed as arguments to another function, also known as higher-order functions. When using higher-order functions, we need to be careful to declare our higher-order functions as inline as possible, since we know from the compilation process of the bytecode analysis above that the lambda will eventually compile into a FunctionN class, The place to call is then to invoke the corresponding invoke method using an instance of the FunctionN. If declared as an inline function, the class is not generated and the FunctionN instance does not need to be instantiated at the function call. Instead, the called method is replaced at the time of the call, reducing the significant performance overhead. Let’s look at an example

Case with no inline function set:

package com.mikyou.kotlin.lambda.high

typealias SumAlias = (Int.Int) - >Int

fun printSum(a: Int, b: Int, block: SumAlias) {// The inline keyword is not set
    println(block.invoke(a, b))
}

fun main(args: Array<String>) {
    printSum(4.5) { a, b ->
        a + b
    }
}
Copy the code

Compile to generate a directory of.clas files, one containing bytecode generated at the call and the other containing bytecode to declare Lambda expressions:

Bytecode generated at a function call

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\u 0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010 \ \ u0012 u0002 \ u001a \ b \ u0004 \ u0012 \ u00020 \ u00040 \ u0003 ¢\ u0006 \ u0002 \ u0010 \ u0005 \ u001a4 \ u0010 \ u0006 \ u001a \ u00020 \ u00012 \ u00 06\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\u000 4\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b2\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0 004 \ 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);// Pass in an INSTANCE of Function2 type to printSum}}Copy the code

Decompiled code at lambda declaration

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\000 \ n \ n \ \ n \ n \ 002\002\020 \ b \ b \ 003\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\001 h \ n ¢\ 006\0 02\b\004"}, d2 = {"<anonymous>".""."a"."b"."invoke"})
final class SumHighFuntionKt$mainThe $1extends Lambda implements kotlin.jvm.functions.Function2<Integer.Integer.Integer> {
    public static final 1INSTANCE =new 1(a);public final int invoke(int a, int b) {
        return a + b;
    }

    SumHighFuntionKt$main$1() {
        super(2); }}Copy the code

Set the case of the inline function:

package com.mikyou.kotlin.lambda.high

typealias SumAlias = (Int.Int) - >Int

inline fun printSum(a: Int, b: Int, block: SumAlias) {// Set printSum to inline
    println(block.invoke(a, b))
}

fun main(args: Array<String>) {
    printSum(4.5) { a, b ->
        a + b
    }
}
Copy the code

Compile to generate. Clas file directory with only one call to generate bytecode:

Bytecode generated at a function call

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\u 0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010 \ \ u0012 u0002 \ u001a \ b \ u0004 \ u0012 \ u00020 \ u00040 \ u0003 ¢\ u0006 \ u0002 \ u0010 \ u0005 \ u001a7 \ u0010 \ u0006 \ u001a \ u00020 \ u00012 \ u00 06\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\u 0012\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");
      // The following call simply replaces the incoming block with the call, eliminating the need for Function2 instance objects, which reduces the overhead of class creation and generation.
      byte a$iv = 4;
      int b$iv = 5;
      intvar4 = a$iv + b$iv; System.out.println(var4); }}Copy the code

What should be paid attention to when using lambda expressions

  • When using ProGuard, be careful not to confuse @metadata annotations; otherwise, exceptions may be thrown.
  • When using higher-order functions, try to use inline functions to reduce the overhead of class generation and class instance creation. We’ll cover the inline functions in more detail in the next blog post.

Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~