Kotlin lambda-Capturing vs Non-Capturing Lambdas

Overview

I believe that many people are confused when they see Capturing/non-capturing lambdas. What is this? It seems that they have not seen this in actual use. In fact, we encounter this thing every day in actual development, but we are not very familiar with this concept. Today I will talk about this thing. After understanding this concept, we

What are Capturing/Non-Capturing Lambdas

So what exactly are Capturing/non-capturing lambdas? Literally you can see that Capturing lambdas and non-capturing lambdas are two relative concepts. Here I translate them literally as capturing type lambda > and non-capturing type lambda. We all know that lambda refers to lambda expressions, but what do capturing and non-capturing here mean? Let me look at the English explanation:

Lambdas are said to be “capturing” if they access a non-static variable or object that was defined outside of the lambda body

As can be seen from the explanation, if a lambda does not reference external non-static variables or objects, the lambda is called non-capturing lambdas. If it does, it is called Capturing lambdas. So according to the meaning here, we can understand capturing as quoting which is easy to understand.

Example

Let me first give an example of non-capturing lambdas

class LambdaTest2(private val viewModel: TestViewModel, private val lifecycleOwner: LifecycleOwner) {<!-- -->
    fun initView() {<!-- -->
        viewModel.liveData.observe(lifecycleOwner) {<!-- -->
            println("receive data")
        }
    }
}

The above is an example of monitoring LiveData data. The lambda used belongs to non-capturing lambdas, because there is no internal reference to variables outside the task. Let’s look at another example of Capturing lambdas

class LambdaTest2(private val viewModel: TestViewModel, private val lifecycleOwner: LifecycleOwner) {<!-- -->
    private val message: String? = null

    fun initView2() {<!-- -->
        viewModel.liveData.observe(lifecycleOwner) {<!-- -->
            println("receive data,toast=${<!-- -->message}")
        }
    }
}

In the above example, the external message variable is referenced, so this is a Capturing lambdas

Note: Capturing/non-capturing lambdas, this concept is not unique to kotlin. It is a language-level concept. As long as a language supports lambda, it generally has this, such as Java, C++ wait.

Substantial difference

So now that we understand the concepts, what are the differences between them? What is the difference between referencing external variables and not referencing external variables? To understand this, we need to see what the final compilation result is. Let's first look at an example:

class LambdaTest2(private val viewModel: TestViewModel, private val lifecycleOwner: LifecycleOwner) {<!-- -->
    private val message: String? = null

    // `Capturing lambdas`, referencing external variables
    fun initView2() {<!-- -->
        viewModel.liveData.observe(lifecycleOwner) {<!-- -->
            println("receive data,toast=${<!-- -->message}")
        }
    }

    // `non-capturing lambdas`, no external variables are referenced
    fun initView() {<!-- -->
        viewModel.liveData.observe(lifecycleOwner) {<!-- -->
            println("receive data")
        }
    }
}

The code is very simple, just observe the data changes of LiveData. One internally refers to the external variable message, and the other does not refer to the external variable. Let me look at its compiled code. The internal code does Simplified:

public final class LambdaTest2 {<!-- -->
    private final Context context;
    private final LifecycleOwner lifecycleOwner;
    private final TestViewModel viewModel;

    public LambdaTest2(TestViewModel viewModel, LifecycleOwner lifecycleOwner) {<!-- -->
        Intrinsics.checkNotNullParameter(viewModel, "viewModel");
        Intrinsics.checkNotNullParameter(lifecycleOwner, "lifecycleOwner");
        this.viewModel = viewModel;
        this.lifecycleOwner = lifecycleOwner;
    }

    public final void initView2() {<!-- -->
        // Note 1
        this.viewModel.getLiveData().observe(this.lifecycleOwner, new LambdaTest2$sam$androidx_lifecycle_Observer(new LambdaTest2$initView2$1(this)));
    }

    public final void initView() {<!-- -->
        // Note 2
        this.viewModel.getLiveData().observe(this.lifecycleOwner, new LambdaTest2$sam$androidx_lifecycle_Observer(LambdaTest2$initView$1.INSTANCE));
    }
}

final class LambdaTest2$initView$1 extends Lambda implements Function1<String, Unit> {<!-- -->
    // Static variable will only be created once
    public static final LambdaTest2$initView$1 INSTANCE = new LambdaTest2$initView$1();
    ...
}

In the above code:

  • Note 1 position, that is, the initView2 method, here Capturing lambdas, refers to external variables. After compilation, you can see that the compiler itself constructed an Observer variable LambdaTest2$sam$androidx_lifecycle_Observer and passed in a parameter new LambdaTest2$initView2$1( this), this object is the logic in our lambda. Because the variables of the external class are referenced, the object of the external class this is passed here. So here, you can know that every time the observe method is called, the lambda expression will be used to construct a corresponding new object.

  • Note 2 position, that is, the initView method, here is non-capturing lambdas, which does not reference external variables. After compilation, you can see that the compiler also built an Observer variable LambdaTest2$sam$androidx_lifecycle_Observer, but the difference here is that the parameters passed are A static object LambdaTest2$initView$1.INSTANCE, this object is also the content of the corresponding lambda,

From the above comparison, we can know that: Capturing lambdas (capturing lambda) will create a corresponding object each time the content of the lambda is called, while non-capturing lambdas (uncaptured lambda), because no external variables are referenced, the object will only be created once, and then the object will be passed in as a static variable.

So what's the difference? If it is a single call and creation, there may be no difference, but if it is called multiple times, there will be a difference. For example, if lambda is used in a loop body, it will be different, because if it is Capturing lambdas(capturing lambda) will create an object every time, and non-capturing lambdas(uncapturing lambda) will only create one, so it is obvious that non-capturing lambdas(not Capturing lambda) has better performance and effectively prevents memory jitter.

Therefore, the author’s inspiration for our actual development is: Try to use non-capturing lambdas (uncapturing lambda), especially in some cases of nested loops, which can reduce the creation of many intermediate classes.

Special circumstances

After my own verification, I found that this situation does not occur when calling the Java-defined (ASM) interface in kotlin, for example:

 fun testForJava() {<!-- -->
        LinearLayout(context).apply {<!-- -->
            //References variables of external class
            setOnClickListener {<!-- -->
                println("receive data,toast=${<!-- -->message}")
            }
            // No external class variables are applied
            setOnClickListener {<!-- -->
                println("receive data")
            }
        }
    }

If you follow the above classification, after compilation, the lambda in the first setOnClickListener will new an object, and the second setOnClickListener will be a static variable. But in fact this is not the case, we can look at the compiled code

 public final void testForJava() {<!-- -->
        LinearLayout $this$testForJava_u24lambda_u242 = new LinearLayout(this.context);
        // Automatically created an anonymous inner class of OnClickListener
        $this$testForJava_u24lambda_u242.setOnClickListener(new View.OnClickListener() {<!-- --> // from class: com.example.effectkotlin.LambdaTest2$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {<!-- -->
                // Call a static method
                LambdaTest2.testForJava$lambda$2$lambda$0(LambdaTest2.this, view);
            }
        });
        // Automatically created an anonymous inner class of OnClickListener
        $this$testForJava_u24lambda_u242.setOnClickListener(new View.OnClickListener() {<!-- --> // from class: com.example.effectkotlin.LambdaTest2$$ExternalSyntheticLambda1
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {<!-- -->
                // Call a static method
                LambdaTest2.testForJava$lambda$2$lambda$1(view);
            }
        });
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void testForJava$lambda$2$lambda$0(LambdaTest2 this$0, View it) {<!-- -->
        Intrinsics.checkNotNullParameter(this$0, "this$0");
        System.out.println((Object) ("receive data,toast=" + this$0.message));
    }

    /* JADX INFO: Access modifiers changed from: private */
    public static final void testForJava$lambda$2$lambda$1(View it) {<!-- -->
        System.out.println((Object) "receive data");
    }


As can be seen from the above, kotlin's ASM interface for Java does not have the concept of Capturing/non-capturing lambdas. It is encapsulated as a static method. If there is an external reference, it is treated as a method parameter. transfer. I don't quite understand why this is done? But just remember that there are differences and be aware of these differences when using them.

Finally

If you want to become an architect or want to break through the 20-30K salary range, then don't be limited to coding and business, you must be able to select and expand, and improve your programming thinking. In addition, good career planning is also very important, and learning habits are important, but the most important thing is to be able to persevere. Any plan that cannot be implemented consistently is empty talk.

If you have no direction, here is a set of "Advanced Notes on the Eight Modules of Android" written by a senior architect at Alibaba to help you systematically organize messy, scattered, and fragmented knowledge, so that you can systematically and efficiently Master various knowledge points of Android development.
img
Compared with the fragmented content we usually read, the knowledge points in this note are more systematic, easier to understand and remember, and are arranged strictly according to the knowledge system.

Everyone is welcome to support with one click and three links. If you need the information in the article, just scan the CSDN official certification WeChat card at the end of the article to get it for free ↓↓↓ (There is also a small bonus of the ChatGPT robot at the end of the article, don’t miss it)

PS: There is also a ChatGPT robot in the group, which can answer everyone’s work or technical questions

image

syntaxbug.com © 2021 All Rights Reserved.