Introduction: Today we bring the sixth bullet of the Kotlin Talk series, let’s talk about the lambda expression in Kotlin. Lambda expressions should be familiar, but they are an important feature introduced in Java8 that frees developers from cumbersome syntax. Unfortunately, they are only available in Java8. Kotlin fixes this problem by mixing lambda expressions with Java programming in Kotlin, which supports versions below Java8. So let’s look at lambda expressions in Kotlin with the following questions.

  • 1. Why Kotlin’s lambda expression (why)?
  • How to use Kotlin’s lambda expressions (how)?
  • 3. Where are Kotlin’s lambda expressions used?
  • Kotlin’s lambda expression scoped variables and variable capture
  • 5. Member references to Kotlin’s lambda expressions

Why use Kotlin’s lambda expressions?

I think there are three main reasons why Kotlin lambda expressions are used.

  • Kotlin’s lambda expressions are implemented in a more concise syntax, freeing developers from redundant and verbose syntax declarations. You can use the filter, map, transform, and other operators in functional programming to work with collection data, bringing your code closer to the functional programming style.
  • Java8 and later versions do not support Lambda expressions, while Kotlin is compatible with Java8 and later versions and has good interoperability, making it a good fit for mixed development patterns. Resolved the bottleneck of not using lambda expressions in Java versions later than 8.
  • 3. The use of Lambda expressions in Java8 is somewhat limited. It does not support closures in the true sense of the term, whereas in Kotlin Lambda is the true implementation of closures. (Why this is explained below)

Kotlin’s basic syntax for lambda expressions

1. Classification of lambda expressions

Kotlin can actually divide Lambda expressions into two broad categories, regular Lambda expressions and Lambda expressions with receivers (very powerful, and there will be a blog devoted to analysis later). The two lambdas are also very different in their usage and usage scenarios. Take a look at the type declarations for the following two lambda expressions:

Lambda expressions with receivers are also very common in Kotlin’s library functions such as declarations of the with and apply standard functions.

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T. () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T. () -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
Copy the code

Seeing the classification of lambda expressions above, do you think of the previous extension function, do you think of the previous diagram?

Similar to what we said in our previous blog about normal functions and extension functions. Normal Lambda expressions are similar to declarations corresponding to normal functions, while Lambda expressions with receivers are similar to extension functions. Extension functions declare the receiver type and then use the receiver object to call directly like a member function call, but actually internally access its methods and properties directly through the receiver object instance.

Basic syntax for lambda

The basic statement of the standard form of lambda meets three conditions:

Contains actual parameters

Contains a function body (even if the function body is empty, it must be declared)

The above interior must be enclosed within curly braces

The above is the most standard form of lambda expression, and this standard form may be seen less in the future development, and more is a more simplified form. The following is to introduce the lambda expression simplification rules

3, Lambda syntax simplified conversion

In the future development, we will use more simplified versions of lambda expressions, because we can see that the standard form of lambda expressions is still a little wordy, which can be omitted than the type of true arguments. Because Kotlin language supports intelligent derivation of types according to the context, it can be omitted, abandoning the wordy syntax. Here is the lambda simplification rule.

Note: syntax simplification is a double-edged sword. Simplification is great and easy to use, but it should not be abused. The simplest form of Lambda simplification in the figure above is “IT”, which is generally not recommended when multiple lambdas are nested, resulting in code readability and probably not even developers knowing what “it” refers to. For example:

This is the joinToString extension function in the Kotlin library. The last argument is a lambda expression that takes a collection element of type T and returns a CharSequence.

//joinToString internal declaration
public fun <T>可迭代<T>.joinToString(separator: CharSequence = ",", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = - 1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
    return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}


fun main(args: Array<String>) {
    val num = listOf(1.2.3)
    println(num.joinToString(separator = ",", prefix = "<", postfix = ">") {
        return@joinToString "index$it"})}Copy the code

We can see that the joinToString call uses a simplified form of the lambda expression as an argument, lifting it out of parentheses. This does make the call a little confusing, because it doesn’t show where lambda expressions are applied, so it’s hard for developers unfamiliar with the internal implementation to understand. Kotlin actually provides us with a solution to this problem, which is the named parameter mentioned in our previous blog. The code after using named parameters

//joinToString internal declaration
public fun <T>可迭代<T>.joinToString(separator: CharSequence = ",", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = - 1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
    return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
fun main(args: Array<String>) {
    val num = listOf(1.2.3)
    println(num.joinToString(separator = ",", prefix = "<", postfix = ">", transform = { "index$it"}}))Copy the code

The return value of the lambda expression

The return value of a lambda expression always returns the value of the last line of expression inside the function body

package com.mikyou.kotlin.lambda

fun main(args: Array<String>) {

    val isOddNumber = { number: Int ->
        println("number is $number")
        number % 2= =1
    }

    println(isOddNumber.invoke(100))}Copy the code

Transpose the two expressions inside the function

package com.mikyou.kotlin.lambda

fun main(args: Array<String>) {

    val isOddNumber = { number: Int ->
        number % 2= =1
        println("number is $number")
    }

    println(isOddNumber.invoke(100))}Copy the code

The lambda expression returns the value of the last line in the function body. Println does not return a value, so it prints Unit by default. The return type of the invoke function is actually the return type of the expression on the last line. We can compare the decomcompiled Java code using the above two methods:

// Decompiled code before switching places
package com.mikyou.kotlin.lambda;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1.1.10}, bv = {1.0.2}, k = 3, d1 = {"\ n \ n \ 000\000\016\002\020\000\013 \ n \ n \ 002\020 \ b \ n \ 000\020\000\032\0020\0012\006\020\002\032\0020\003 h \ n ¢\ 006\002 \ \ 004 b"}, d2 = {"<anonymous>".""."number".""."invoke"})
final class LambdaReturnValueKt$main$isOddNumberThe $1extends Lambda implements kotlin.jvm.functions.Function1<Integer.Boolean> {
    public final boolean invoke(int number) {// The invoke function returns a Boolean of type corresponding to the Boolean in Kotlin
        String str = "number is " + number;
        System.out.println(str);
        return number % 2= =1;
    }

    public static final 1INSTANCE =new 1(a); LambdaReturnValueKt$main$isOddNumber$1() {
        super(1); }}Copy the code
// Decompile code after switching positions
package com.mikyou.kotlin.lambda;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1.1.10}, bv = {1.0.2}, k = 3, d1 = {"\ n \ n \ 000\000\016\002\020\000\002 \ n \ n \ 002\020 \ b \ n \ 000\020\000\032\0020\0012\006\020\002\032\0020\003 h \ n ¢\ 006\002 \ \ 004 b"}, d2 = {"<anonymous>".""."number".""."invoke"})
final class LambdaReturnValueKt$main$isOddNumberThe $1extends Lambda implements kotlin.jvm.functions.Function1<Integer.kotlin.Unit> {
    public final void invoke(int number) {// Invoke returns a value of type void, corresponding to Unit in Kotlin
        if (number % 2! =1) {
        }
        String str = "number is " + number;
        System.out.println(str);
    }

    public static final 1INSTANCE =new 1(a); LambdaReturnValueKt$main$isOddNumber$1() {
        super(1); }}Copy the code

5. Lambda expression types

Kotlin provides a concise syntax for defining the types of functions.

() - >Unit// Represents the type of Lambda expression with no arguments and no return value

(T) -> UnitLambda expression type that accepts a t-type argument with no return value

(T) -> RLambda expression type that takes a t-type argument and returns a value of r-type

(T, P) -> RLambda expression type that accepts a parameter of type T and type P and returns a value of type R

(T, (P,Q) -> S) -> R// represents a Lambda expression type that takes a T parameter and a Lambda expression type that takes P and Q parameters and returns a value of type S, and a Lambda expression type that returns a value of type R
Copy the code

The last one, the last one is actually in the category of higher-order functions. But said the people here to see this type of a method is a bit like a layer of a layer to peel Onions, lining split is from outside to inside, and then do break up, for itself is a Lambda expression type, first temporarily as a whole, so that you can determine the outermost Lambda type, and then using similar methods to internal split.

6. Name Lambda types using the TypeAlias keyword

Imagine a scenario where multiple lambda expressions may be used, but they are of the same type. It’s easy to repeat all the same long list of lambda types or your lambda type declaration is too long to read. Not really, and for Kotlin, a language that opposes all verbose syntax, it gives you a series of solutions that allow you to simplify your code without making it less readable.

fun main(args: Array<String>) {
    val oddNum:  (Int) - >Unit = {
        if (it % 2= =1) {
            println(it)
        } else {
            println("is not a odd num")}}val evenNum:  (Int) - >Unit = {
        if (it % 2= =0) {
            println(it)
        } else {
            println("is not a even num")
        }
    }

    oddNum.invoke(100)
    evenNum.invoke(100)}Copy the code

Declare (Int) -> Unit type using the TypeAlias keyword

package com.mikyou.kotlin.lambda

typealias NumPrint = (Int) - >Unit// Note: the declared location is outside the function, inside the package

fun main(args: Array<String>) {
    val oddNum: NumPrint = {
        if (it % 2= =1) {
            println(it)
        } else {
            println("is not a odd num")}}val evenNum: NumPrint = {
        if (it % 2= =0) {
            println(it)
        } else {
            println("is not a even num")
        }
    }

    oddNum.invoke(100)
    evenNum.invoke(100)}Copy the code

Kotlin’s lambda expressions are often used in scenarios

  • Scene 1: Lambda expressions are used in conjunction with collections, which is the most common scenario. They can be used with various filtering, mapping, transformation operators and various operations on collection data, which is very flexible. Developers who have used RxJava have already experienced this delight. It gives you an API that supports functional programming.
package com.mikyou.kotlin.lambda

fun main(args: Array<String>) {
    val nameList = listOf("Kotlin"."Java"."Python"."JavaScript"."Scala"."C"."C++"."Go"."Swift")
    nameList.filter {
        it.startsWith("K")
    }.map {
        "$it is a very good language"
    }.forEach {
        println(it)
    }

}
Copy the code

  • Scenario 2: Replace anonymous inner classes, but only classes that contain a single abstract method.
	findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {... }});Copy the code

Kotlin lambda

findViewById(R.id.submit).setOnClickListener{
    ...
}
Copy the code
  • Scenario 3: Defining a Kotlin extension function or a function that needs to be passed an operation or function as a value.
fun Context.showDialog(content: String = "", negativeText: String = "Cancel", positiveText: String = "Sure", isCancelable: Boolean = false, negativeAction: (() -> Unit)? = null, positiveAction: (() -> Unit)? = null) {
	AlertDialog.build(this) .setMessage(content) .setNegativeButton(negativeText) { _, _ -> negativeAction? .invoke() } .setPositiveButton(positiveText) { _, _ -> positiveAction? .invoke() } .setCancelable(isCancelable) .create() .show() }fun Context.toggleSpFalse(key: String, func: () -> Unit) {
	if(! getSpBoolean(key)) { saveSpBoolean(key,true)
		func()
	}
}

fun <T : Any> Observable<T>.subscribeKt(success: ((successData: T) -> Unit)? = null, failure: ((failureError: RespException?) -> Unit)? = null): Subscription? {
	return transformThread()
			.subscribe(object : SBRespHandler<T>() {
				override fun onSuccess(data: T){ success? .invoke(data)}override fun onFailure(e: RespException?).{ failure? .invoke(e) } }) }Copy the code

The scope of Kotlin’s lambda expression access variables and variable capture

1. The difference between Kotlin and Java inner classes or lambda accessing local variables

  • In Java, when an anonymous inner class or lambda is defined inside a function, the local variables accessed by the inner class must be final, which means that the value of the local variable cannot be modified inside the inner class or inside the lambda expression. Take a look at a very simple Android event click example
public class DemoActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo);
        final int count = 0;// Final modifier is required
        findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println(count);Count must be final when accessed inside the anonymous OnClickListener class}}); }}Copy the code
  • In Kotlin, a lambda or inner class is defined inside a function to access both final and non-final modified variables, meaning that the values of local variables of the function can be directly modified inside the lambda. The above example Kotlin implementation

Access final modified variables

class Demo2Activity : AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?). {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_demo2)
		val count = 0/ / final declaration
		btn_click.setOnClickListener {
			println(count)// Access final modified variables that are Java compliant.}}}Copy the code

Access a variable that is not final modified and modify its value

class Demo2Activity : AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?). {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_demo2)
		var count = 0// Declare non-final types
		btn_click.setOnClickListener {
			println(count++)// Directly access and modify variables of non-final type}}}Copy the code

Using lambdas in Kotlin is more flexible and less restricted than using lambdas in Java, which answers the first statement of this blog that lambdas in Kotlin are truly closures supported, whereas lambdas in Java are not. How do lambda expressions in Kotlin do this? Then, please see

2. Variable capture and principle of lambda expressions in Kotlin

  • What is variable capture?

From the examples above, we know that in Kotlin you can access and modify both final and non-final variables. How does that work? Before we do that, we throw up a fancy concept called lambdab expressions for variable capture. Essentially, a lambda expression has access to external variables in its function body, which we say are captured by the lambda expression. With this in mind we can make the above statement a little bit more grandiose:

First, lambda expressions in Java can only capture final modified variables

Second, in Kotlin lambda expressions can capture final modified variables as well as access and modify non-final variables

  • Principle of variable capture implementation

We all know the function of the local variable life cycle belongs to this function, when the function is performed, a local variable which is destroyed, but if the local variable is lambda captured, so the use of the local variable code will be stored for later again, namely captured local variables can be delayed life cycle, The local variable principle for capturing final modifications on lambda expressions is that the value of the local variable and the lambda code that uses that value are stored together; The local variable principle for capturing non-final modifiers is that the non-final local variable is wrapped ina special wrapper class so that the non-final variable can be modified through the wrapper class instance. The wrapper class instance reference is final and stored with the lambda code

Kotlin’s second conclusion is syntactically correct, but it is wrong in principle. Kotlin blocks it syntactically. The essence of the principle is that lambda expressions can only capture final modifier variables. Why kotlin can change the values of non-final variables? In fact, Kotlin makes a bridge wrapper syntactically, wrapping so-called non-final variables ina Ref wrapper class, and leaving the Ref wrapper references as final. The lambda is then stored with a reference to the final wrapper, and the value of the variable that is subsequently modified inside the lambda is actually modified through the final wrapper reference.

Finally, look at the decomcompiled Java code that Kotlin uses to modify non-final local variables

class Demo2Activity : AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?). {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_demo2)
		var count = 0// Declare non-final types
		btn_click.setOnClickListener {
			println(count++)// Directly access and modify variables of non-final type}}}Copy the code
@Metadata(
   mv = {1.1.9},
   bv = {1.0.2},
   k = 1,
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\ U0000 \ u0018 \ u00002 \ u00020 \ u0001B \ u0005 ¢\ u0006 \ u0002 \ u0010 \ u0002J \ u0012 \ u0010 \ u0003 \ u001a \ u00020 \ u00042 \ \ b u0010 \ u0005 \ u00 1 a \ u0004 \ u0018 \ u00010 \ u0006H \ u0014 ¨ \ u0006 \ u0007"},
   d2 = {"Lcom/shanbay/prettyui/prettyui/Demo2Activity;"."Landroid/support/v7/app/AppCompatActivity;"."()V"."onCreate".""."savedInstanceState"."Landroid/os/Bundle;"."production sources for module app"})public final class Demo2Activity extends AppCompatActivity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131361820);
      final IntRef count = new IntRef();//IntRef special wrapper class type, final modified IntRef count reference
      count.element = 0;// The non-final element inside the wrapper
      ((Button)this._$_findCachedViewById(id.btn_click)).setOnClickListener((OnClickListener)(new OnClickListener() {
         public final void onClick(View it) {
            int var2 = count.element++;// A reference to IntRef directly modifies the values of internal non-final variables, whereas a syntactically level lambda modifies the values of non-final local variables directlySystem.out.println(var2); }})); }public View _$_findCachedViewById(int var1) {
      if(this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
      if(var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(Integer.valueOf(var1), var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if(this._$_findViewCache ! =null) {
         this._$_findViewCache.clear(); }}}Copy the code

3. Lambda expression variable capture considerations in Kotlin

Note: Modifying the value of a local variable inside a Lambda expression is triggered only when the Lambda expression is executed.

Member references to Kotlin’s lambda expressions

1. Why use member references

We know that Lambda expressions can pass a block of code directly to a function as an argument, but have you ever been in a situation where the block I’m passing already exists as a named function and you need to write another block to pass it? Of course not. Kotlin doesn’t like repetitive code. So all you need is a member reference substitute.

fun main(args: Array<String>) {
    val persons = listOf(Person(name = "Alice", age = 18), Person(name = "Mikyou", age = 20), Person(name = "Bob", age = 16))
    println(persons.maxBy({ p: Person -> p.age }))
}
Copy the code

Can be replaced by

fun main(args: Array<String>) {
    val persons = listOf(Person(name = "Alice", age = 18), Person(name = "Mikyou", age = 20), Person(name = "Bob", age = 16))
    println(persons.maxBy(Person::age))// The type of the member reference is the same as the type of the lambda expression passed by maxBy
}
Copy the code

2. Basic syntax for member references

A member reference consists of a class, a double colon, and a member

3. Usage scenarios for member references

  • The most common use of member references is the class name + double colon + member (attribute or function)
fun main(args: Array<String>) {
    val persons = listOf(Person(name = "Alice", age = 18), Person(name = "Mikyou", age = 20), Person(name = "Bob", age = 16))
    println(persons.maxBy(Person::age))// The type of the member reference is the same as the type of the lambda expression passed by maxBy
}
Copy the code
  • Omit the class name and refer directly to the top-level function.
package com.mikyou.kotlin.lambda

fun salute(a) = print("salute")

fun main(args: Array<String>) {
    run(::salute)
}
Copy the code
  • Member references are used to extend functions

fun Person.isChild(a) = age < 18

fun main(args: Array<String>){
    val isChild = Person::isChild
    println(isChild)
}

Copy the code

Here is the basic knowledge of Kotlin lambda, the next chapter will be from the essence of the lambda principle and bytecode analysis, as well as the use of lambda expression performance optimization.

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 ~~~