On the complete analysis of Lambda expressions in Kotlin grammar (6)

Brief description: The sixth bullet of the Kotlin talk series brought today, let's talk about lambda expressions in Kotlin. Lambda expressions should not be unfamiliar. A very important feature introduced in Java 8 frees developers from the original cumbersome grammar, but unfortunately only the Java 8 version can be used. Kotlin makes up for this problem. The mixed programming of lambda expressions and Java in Kotlin can support versions below Java 8. Let's take the following questions to look at lambda expressions in Kotlin.

  • 1. Why use Kotlin's lambda expression (why)?
  • 2. How to use Kotlin's lambda expressions (how)?
  • 3.Where are Kotlin's lambda expressions generally used?
  • 4. Scope variables and variable capture of Kotlin's lambda expressions
  • 5. Member references of Kotlin's lambda expressions

1. Why use Kotlin's lambda expressions?

For the above question of why lambda expressions in Kotlin are used, I think there are three main reasons.

  • 1. Kotlin's lambda expression implements functions with a more concise and understandable syntax, freeing developers from the original redundant and long-winded syntax declarations. You can use the filtering, mapping, conversion and other operators in functional programming to process collection data, so that your code is closer to the style of functional programming.
  • 2. Versions below Java 8 do not support Lambda expressions, while Kotlin is compatible with versions below Java 8 and has good interoperability, which is very suitable for the mixed development model of versions below Java 8 and Kotlin. Solved the bottleneck that lambda expressions cannot be used in versions below Java 8.
  • 3. The use of Lambda expressions in the Java 8 version is somewhat limited. It does not support closures in the true sense, but lambda in Kotlin is the true support for closures. (About this issue, why will be explained below)

2. The basic syntax of Kotlin's lambda expression

1. Classification of lambda expressions

In Kotlin, Lambda expressions can actually be divided into two categories, one is ordinary lambda expression, and the other is lambda expression with receiver (the function is very powerful, and there will be a special analysis blog later). The use and usage scenarios of these two lambdas are also very different. Let's first look at the type declaration of the following two lambda expressions:

 

Lambda expressions with receivers are also very common in standard library functions in Kotlin, such as the declaration of 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
}

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

 

Is it similar to what our previous blog said about ordinary functions and extended functions? Ordinary Lambda expressions are similar to the declarations corresponding to ordinary functions, and lambda expressions with receivers are similar to corresponding extension functions. The extension function is to declare the receiver type, and then use the receiver object to call directly similar to the member function call. In fact, the method and properties of it are directly accessed through the receiver object instance.

2. Basic syntax of lambda

The standard form of lambda basically states that three conditions are met:

Contains actual parameters

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

The above must be enclosed in curly braces

 

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

3. Simplified conversion of lambda syntax

In the future, we will use the simplified version of lambda expressions more often, because we see that the standard lambda expression form is still a bit verbose, such as the actual parameter type can be omitted, because the language of Kotlin supports intelligent inference of the type based on the context , So you can omit and discard the verbose grammar. Here are the lambda simplification rules.

 

 

Note: grammatical simplification is a double-edged sword. Although simplification is good, it is simple and convenient to use, but it cannot be abused, and the readability of the code needs to be considered. The simplest form of Lambda reduction in the above figure is it, which is generally When multiple Lambdas are nested, it is not recommended to use it, which seriously results in code readability. In the end, it is estimated that even the developer does not know what it refers to. For example, the following code:

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

//joinToString内部声明
public fun <T> Iterable<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"
    })
}

We can see that the call of joinToString is a simplified form of using lambda expression as a parameter, which is brought out from parentheses. This does bring a little confusion to the call, because it does not show where the lambda expression is applied, so it is difficult for developers who are not familiar with the internal implementation to understand. For this kind of problem, Kotlin actually provides us with a solution, which is the named parameter mentioned in our previous blog. Code after using named parameters

//joinToString内部声明
public fun <T> Iterable<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" }))
}

4. The return value of the lambda expression

The return value of a lambda expression always returns the value of the last line 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))
}

After swapping the positions of the two expressions in the function body

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

From the above example, we can see that the lambda expression returns the value of the last line expression in the function body. Since the println function does not return a value, the unit type is printed by default. What is its internal principle? In fact, the return value type of the last line of expression is used as the return value type of the invoke function. We can compare the above two ways of decompilation into java code:

//互换位置之前的反编译代码
package com.mikyou.kotlin.lambda;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\016\n\000\n\002\020\013\n\000\n\002\020\b\n\000\020\000\032\0020\0012\006\020\002\032\0020\003H\n¢\006\002\b\004"}, d2 = {"<anonymous>", "", "number", "", "invoke"})
final class LambdaReturnValueKt$main$isOddNumber$1 extends Lambda implements kotlin.jvm.functions.Function1<Integer, Boolean> {
    public final boolean invoke(int number) {//此时invoke函数返回值的类型是boolean,对应了Kotlin中的Boolean
        String str = "number is " + number;
        System.out.println(str);
        return number % 2 == 1;
    }

    public static final 1INSTANCE =new 1();

    LambdaReturnValueKt$main$isOddNumber$1() {
        super(1);
    }
}
//互换位置之后的反编译代码
package com.mikyou.kotlin.lambda;

import kotlin.jvm.internal.Lambda;

@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\016\n\000\n\002\020\002\n\000\n\002\020\b\n\000\020\000\032\0020\0012\006\020\002\032\0020\003H\n¢\006\002\b\004"}, d2 = {"<anonymous>", "", "number", "", "invoke"})
final class LambdaReturnValueKt$main$isOddNumber$1 extends Lambda implements kotlin.jvm.functions.Function1<Integer, kotlin.Unit> {
    public final void invoke(int number) {//此时invoke函数返回值的类型是void,对应了Kotlin中的Unit
        if (number % 2 != 1) {
        }
        String str = "number is " + number;
        System.out.println(str);
    }

    public static final 1INSTANCE =new 1();

    LambdaReturnValueKt$main$isOddNumber$1() {
        super(1);
    }
}

5. Types of lambda expressions

Kotlin provides a concise syntax to define function types.

() -> Unit//表示无参数无返回值的Lambda表达式类型

(T) -> Unit//表示接收一个T类型参数,无返回值的Lambda表达式类型

(T) -> R//表示接收一个T类型参数,返回一个R类型值的Lambda表达式类型

(T, P) -> R//表示接收一个T类型和P类型的参数,返回一个R类型值的Lambda表达式类型

(T, (P,Q) -> S) -> R//表示接收一个T类型参数和一个接收P、Q类型两个参数并返回一个S类型的值的Lambda表达式类型参数,返回一个R类型值的Lambda表达式类型

The first ones of the above types should be easy to understand. It is estimated that it is a bit difficult to be the last one. The last one actually belongs to the category of higher-order functions. However, one way to personally see this type is a bit like peeling an onion layer by layer and splitting it into the inner layer, that is, look from the outside to the inside, and then do the split. For the Lambda expression type itself, temporarily As a whole, you can determine the outermost Lambda type, and then use a similar method to split it internally.

 

6. Use the typealias keyword to name the Lambda type

Let's imagine a scenario where multiple lambda expressions may be used, but the types of these lambda expressions are many of the same. It is easy for us to repeat all the same large series of Lambda types or your lambda type declaration is too long to be easy to read . Actually no need. For Kotlin, a language that opposes all verbose grammars, it provides you with a series of solutions, allowing you to simplify the code without reducing the readability of the code.

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

Use typealias keyword declaration (Int) -> Unit type

package com.mikyou.kotlin.lambda

typealias NumPrint = (Int) -> Unit//注意:声明的位置在函数外部,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)
}

Three, Kotlin's lambda expressions often used scenes

  • Scenario 1: Lambda expressions are used together with collections. It is the most common scenario. It can perform various filtering, mapping, transformation operators and various operations on collection data. It is very flexible. I believe developers who have used RxJava have already experienced it. This kind of pleasure, it is true that Kotlin provides you with APIs that support functional programming without adding additional libraries at the language level.
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)
    }

}

 

 

 

  • Scenario 2: Replace the original anonymous inner class, but one thing to note is that it can only replace the class with a single abstract method.
findViewById(R.id.submit).setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View v) {
		...
	}
});

Implemented with kotlin lambda

findViewById(R.id.submit).setOnClickListener{
    ...
}
  • Scenario 3: When you define a Kotlin extension function, or when you need to pass a certain operation or function as a value.
fun Context.showDialog(content: String = "", negativeText: String = "取消", positiveText: String = "确定", 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)
				}
			})
}

Four, Kotlin's lambda expression in the scope of access variables and variable capture

1. The difference between Kotlin and Java internal classes or lambdas to access local variables

  • Define an anonymous inner class or lambda inside a function in Java. The function local variables accessed by the inner class must be finalized, which means that the value of the function local variable cannot be modified in the inner class or the lambda expression. You can see 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修饰
        findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println(count);//在匿名OnClickListener类内部访问count必须要是final修饰
            }
        });
    }
}
  • Defining lambdas or internal classes inside functions in Kotlin can access both final and non-final modified variables, which means that the value of function local variables can be directly modified inside 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
		btn_click.setOnClickListener {
			println(count)//访问final修饰的变量这个是和Java是保持一致的。
		}
	}
}

Access the non-final modified variable and modify its value

class Demo2Activity : AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_demo2)
		var count = 0//声明非final类型
		btn_click.setOnClickListener {
			println(count++)//直接访问和修改非final类型的变量
		}
	}
}

Through the above comparison, it can be found that the use of lambda in Kotlin is more flexible than the use of lambda in Java, and the access is less restricted. This is to answer the first sentence of this blog. The lambda expression in Kotlin is a real sense of support. Package, while lambda in Java is not. How do lambda expressions in Kotlin do this? Please continue

2. Variable capture and principle of lambda expression in Kotlin

  • What is variable capture?

Through the above example, we know that in Kotlin we can access both final variables and non-final variables. What is the principle? Before that, first throw out a tall concept called variable capture of lambdab expressions . In fact, lambda expressions can access external variables in the body of the function. We call these external variables captured by lambda expressions. With this concept we can make the above conclusions taller:

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

Second, lambda expressions in Kotlin can not only capture final modified variables but also access and modify non-final variables

  • The principle of variable capture

We all know that the life cycle of the local variable of the function belongs to this function. When the function is executed, the local variable is destroyed, but if the local variable is captured by lambda, then the code using this local variable will be stored and waited. Execute again later, that is, the captured local variables can delay the life cycle. The principle of capturing final modified local variables for lambda expressions is that the value of the local variable and the lambda code using this value will be stored together; The principle of capturing non-final modified local variables is that non-final local variables will be wrapped by a special wrapper class, so that this non-final variable can be modified through the wrapper class instance, then the wrapper class instance reference is final. Store with lambda code

The second conclusion above is correct at the grammatical level of Kotlin, but it is wrong from the point of view of the real principle. It is just that Kotlin shields this at the grammatical level. The actual principle of lambda expressions can only be captured. Final modifies variables, but why Kotlin can modify the value of non-final variables? In fact, Kotlin has made a bridging package at the grammatical level. It wraps the so-called non-final variables with a Ref packaging class, and then retains them externally The reference to the Ref wrapper is final, and then the lambda will be stored with the reference of this final wrapper, and then the value of the variable inside the lambda is modified by the final wrapper reference.

Finally, by viewing the decompiled Java code that Kotlin modified non-final local variables, it is clear at a glance

class Demo2Activity : AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_demo2)
		var count = 0//声明非final类型
		btn_click.setOnClickListener {
			println(count++)//直接访问和修改非final类型的变量
		}
	}
}
@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\u001a\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特殊的包装器类的类型,final修饰的IntRef的count引用
      count.element = 0;//包装器内部的非final变量element
      ((Button)this._$_findCachedViewById(id.btn_click)).setOnClickListener((OnClickListener)(new OnClickListener() {
         public final void onClick(View it) {
            int var2 = count.element++;//直接是通过IntRef的引用直接修改内部的非final变量的值,来达到语法层面的lambda直接修改非final局部变量的值
            System.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();
      }

   }
}

3. Precautions for capturing lambda expression variables in Kotlin

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

Five, Kotlin's lambda expression member reference

1. Why use member references

We know that in Lambda expressions, you can directly pass a code block as a parameter to a function, but have you ever encountered such a scenario where I want to pass the code block in the past, which already exists as a named function, at this time you Do I need to write a code block repeatedly and pass it over? Definitely not, Kotlin rejects repetitive code. So only need member reference substitution.

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

Can be replaced with

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))//成员引用的类型和maxBy传入的lambda表达式类型一致
}

2. The basic syntax of member references

Member reference consists of three parts: class, double colon, and member

3. Usage scenarios for member references

  • The most common way to use member references is 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))//成员引用的类型和maxBy传入的lambda表达式类型一致
}
  • Omit the class name to directly reference the top-level function (the previous blog has a special analysis)
package com.mikyou.kotlin.lambda

fun salute() = print("salute")

fun main(args: Array<String>) {
    run(::salute)
}
  • Member references are used to extend functions
fun Person.isChild() = age < 18

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

At this point, the basic knowledge about Kotlin lambda is basically finished. The next article will analyze the essence of Lambda and bytecode, and optimize the performance of Lambda expressions.

Guess you like

Origin blog.csdn.net/az44yao/article/details/112921282