preface

By reading about lambda expressions and Kotlin higher-order functions, you should know that the syntax for passing a lambda as a function argument in Kotlin is very similar to ordinary expressions. This article takes you through how lambda works and the inline functions used to eliminate runtime overhead associated with lambda.

Analyze lambda expressions through bytecode

We declare a higher-order function lambdaFunction, and use lambda as an argument to align it for lower use:

object Lombda {
    @JvmStatic
    fun main(arg: Array<String>) {
        val lambdaFunction = lambdaFunction({ x, y -> x + y })
        println(lambdaFunction)
    }

    private fun lambdaFunction(function: (Int, Int) -> Int): Int {
        return function(2, 3)}}Copy the code

Decompiles its bytecode file into a Java file in Intellij IDEA as follows:

public final class Lombda {
   public static final Lombda INSTANCE;

   @JvmStatic
   public static final void main(@NotNull String[] arg) {
      Intrinsics.checkParameterIsNotNull(arg, "arg");
      int lambdaFunction = INSTANCE.lambdaFunction((Function2)null.INSTANCE);
      System.out.println(lambdaFunction);
   }

   private final int lambdaFunction(Function2 function) {
      return ((Number)function.invoke(Integer.valueOf(2), Integer.valueOf(3))).intValue();
   }

   private Lombda() { INSTANCE = (Lombda)this; } static { new Lombda(); }}Copy the code

Click Function2 to enter functions.kt file, some code screenshots are as follows:

In the functions.kt file under the kotlin.jvm.functions package, a series of interfaces are defined that correspond to functions with different numbers of arguments (in is the function’s input parameter and out is the function’s return value). Each interface defines an invoke method that executes the function if it is not called. A variable of function type is an instance of the implementation class that implements the corresponding FunctionN interface, and the invoke method of the implementation class contains the body of a lambda function. A lambda expression is an implementation of the FunctionN interface. An anonymous class is compiled for each lambda expression (unless it is an inline function). Once implemented, the compiler can avoid generating a class file for every expression. If a lambda expression captures variables, each captured variable will have a corresponding field in the anonymous class. And each call to a lambda expression creates a new instance of the anonymous class.

Inline functions: Eliminate runtime overhead associated with lambda

If a function is marked with an inline modifier, the compiler does not generate the code for the function call when the function is in use, but replaces each function call with the actual code implemented by the function.

In other words, the function will be replaced directly where the function was called, instead of being called normally

Inline function operation method

First we define an inline function and call it

Inline fun lambdaFunction(action (Int,Int)->Unit){action(2,3)} funmain() {
        println("Before call")
        lambdaFunction { x, y -> print(x+y) }
        println("After call")}Copy the code

The following code does the same thing as main() and is compiled to the same bytecode

fun _main_(){
        println("Before call")
        print(2+3)
        println("Before call")}Copy the code

Where lambda expressions are inlined. The code generated by lambda expressions becomes part of the function caller’s definition, rather than being contained in an anonymous class that implements the function interface. (My personal understanding is: extract a code block, compile time to fill the code block into the specified location, that is, call the inline function). The function will be replaced directly where the function was called. If you use the same inline function in two different locations and use different lambdas, the inline function is inlined separately at each called location. The code for the inline function is copied to the location where it is used and replaced with a different lambda.

Limitations on inline functions

Not all lambda functions can be inlined. If the lambda is stored somewhere, the code for the lambda expression will not be inline. Such as:

val la = {x:Int, y:Int -> print(x+y)}
lambdaFunction (la)
Copy the code

Call an inline function with a variable of type function as an argument, in which case the lambda is not inlined. Note that using inline functions can only improve the performance of functions that take lambda arguments. The JVM already provides powerful inline support for ordinary functions, where the implementation of each function appears only once in the bytecode and does not need to be copied every time a function is called, as inline functions do. Also, if the function is called directly, the call stack is much cleaner and easier to read. Using inline functions has obvious benefits: avoiding runtime overhead (saving on function calls, lambda creating anonymous classes and instance objects)

conclusion

At this point, kotlin’s lambdas and inline functions are basically analyzed. The Kotlin standard library provides many inline functions, as well as functions that operate on collections. These functions can greatly reduce the amount of code and productivity. Stay tuned to my blog to learn more about the Kotlin library functions.