Kotlin intermediate: Lambda expression

1, Lambda introduction

As mentioned above, it has been widely used in Java, but this Lambda expression was only supported in Java 8. In other programming languages (for example, Scala). This expression is one of the grammatical sugars. Fortunately, Kotlin has supported this syntax as soon as open source matures.

The essence of Lambda expression is actually anonymous function, because it is implemented through anonymous function in its underlying implementation. But when we use it, we don't have to care about the bottom layer implementation. However, the emergence of Lambda does not only reduce the amount of code, but also make the code more concise and clear.
As the basis of functional programming, Lambda's syntax is also quite simple. Here, a simple code demonstration does not let you understand the simplicity of Lambda expression.

Example:

// Here is an example of the most common button click event in Android
mBtn.setOnClickListener(object : View.OnClickListener{
        override fun onClick(v: View?) {
            Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show()
        }
    })
Copy code

Equivalent to

// call
mBtn.setOnClickListener { Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() }
Copy code

2, Lambda use

As for the use of Lambda, I will explain from which aspect. First, I will introduce the characteristics of Lambda expressions, but from the syntax of Lambda.

2.1 characteristics of Lambda expression

The ancients said: if you want to take it, go with it first.

To learn Lambda expression syntax, you must first understand its characteristics. Here I first summarize some characteristics of Lambda expressions. We will understand when we explain Lambda grammar and practice below. Namely:

  • Lambda expressions are always enclosed in braces
  • Its parameters (if any) are declared before - > and parameter types can be omitted
  • The body of the function (if present) is after - >.

2.2 Lambda syntax

In order to make you fully understand Lambda grammar, I will explain it in three ways. And give an example to illustrate

The syntax is as follows:

    1. Without parameters:
    val/var Variable name = { Operation code }

    2. With parameters
    val/var Variable name : (Parameter type, parameter type,...) -> return type = {Parameter 1, parameter 2,... -> Code of the operating parameter }

    Can be equivalent to
    // This way of writing: that is, the return value type of the expression will be derived from the operation code.
    val/var Variable name = { Parameter 1: type, parameter 2 : type, ... -> Code of the operating parameter }

    3. lambda When an expression is used as a parameter in a function, here is an example:
    fun test(a : Int, Parameter name : (Parameter 1: type, parameter 2 : type, ... ) -> Expression return type){
        ...
    }
Copy code

Example explanation:

  • Case without parameters

        // source code
       fun test(){ println("No parameters") }
    
        // lambda code
        val test = { println("No parameters") }
    
        // call
        test()  => The result is: no parameters
     Copy code
  • If there are parameters, here is an example of two parameters for the purpose of demonstration

        // source code
        fun test(a : Int , b : Int) : Int{
            return a + b
        }
    
        // lambda
        val test : (Int , Int) -> Int = {a , b -> a + b}
        // perhaps
        val test = {a : Int , b : Int -> a + b}
    
        // call
        test(3,5) => The result is: 8
     Copy code
  • When a lambda expression is used as an argument in a function

        // source code
        fun test(a : Int , b : Int) : Int{
            return a + b
        }
    
        fun sum(num1 : Int , num2 : Int) : Int{
            return num1 + num2
        }
    
        // call
        test(10,sum(3,5)) // The result is: 18
    
        // lambda
        fun test(a : Int , b : (num1 : Int , num2 : Int) -> Int) : Int{
            return a + b.invoke(3,5)
        }
    
        // call
        test(10,{ num1: Int, num2: Int ->  num1 + num2 })  // The result is: 18
     Copy code

The implementation of the last one may be difficult for you to understand, but please don't be confused. You will continue to look at it and introduce it to you in the following practice and high-order functions.

After the above example explanation and grammar introduction, we make a summary:

  1. lambda expressions are always enclosed in braces.
  2. Define a complete Lambda expression, such as syntax 2 in the above example. It has its complete parameter type annotation and the return value of the expression. When we omit some type annotations, such as another type of syntax 2 in the above example. When the inferred return value type is not 'Unit', its return value is the type of the last (or only one) expression of the code after the - > symbol.
  3. In the above example, syntax 3 is expressed as a high-order function. When a Lambda expression is used as a parameter, only the parameter type and return type are provided for the expression. Therefore, when we call this high-order function, we need to write its specific implementation for the Lambda expression.
  1. invoke() function: it means to call itself through the function variable, because the variable b in the above example is an anonymous function.

3. Lambda practice

After learning the syntax explained above, I believe you can roughly write and use lambda expressions, but only the above simple syntax is not enough to be applied to complex situations in actual projects. The following explains the key points of lambda practice from several knowledge points.

3.1,it

  • it is not a keyword (reserved word) in Kotlin.
  • it is used when there is only one parameter of Lambda expression in a higher-order function. it can be expressed as an implicit name of a single parameter, which is a Kotlin language convention.

Example 1:

val it : Int = 0  // That is, it is not a keyword in 'Kotlin'. Available variable names
 Copy code

Example 2: implicit name of a single parameter

// Here is an example of a language's own high-order function filter, which is used to filter out values that do not meet conditions.
val arr = arrayOf(1,3,5,7,9)
// Filter out the elements with elements less than 2 in the array and take the first one for printing. it here represents each element.
println(arr.filter { it < 5 }.component1())   
Copy code

Example 2 this column is just for you to use it, filter higher-order function, which will be described later Kotlin - advanced part (IV): Fundamentals of sets (Array, List, Set, Map) The chapter will explain in detail for you, and there will be no more introduction here. Let's write a higher-order function for ourselves to explain it. For the definition and use of higher-order functions, see Kotlin -- advanced part of the series from nothing to something (2): detailed explanation of higher-order functions This article.

Example 3:

 fun test(num1 : Int, bool : (Int) -> Boolean) : Int{
   return if (bool(num1)){ num1 } else 0
}

println(test(10,{it > 5}))
println(test(4,{it > 5}))
Copy code

The output result is:

10
0
 Copy code

Code explanation: the above code means that in the high-order function test, the return value is Int, and the Lambda expression is conditional on num1 bits. If the value of Lambda expression is false, it returns 0, otherwise it returns num1. Therefore, when the condition is num1 > 5, when the test function is called, the return value of num1 = 10 is 10, and the return value of num1 = 4 is 0.

3.2 underline ()

When using a Lambda expression, you can use an underscore () to indicate an unused parameter, indicating that the parameter is not processed.

At the same time, this is very useful when traversing a Map collection.

give an example:

val map = mapOf("key1" to "value1","key2" to "value2","key3" to "value3")

map.forEach{
     key , value -> println("$key \t $value")
}

// When you don't need a key
map.forEach{
    _ , value -> println("$value")
}
Copy code

Output results:

key1     value1
key2     value2
key3     value3
value1
value2
value3
 Copy code

3.3 anonymous functions

  • The characteristic of anonymous function is that its return value type can be specified explicitly.
  • It is almost similar to the definition of a regular function. The difference between them is that anonymous functions do not have function names.

Example:

                 fun test(x : Int , y : Int) : Int{                  fun(x : Int , y : Int) : Int{
  General functions:      return x + y                         Anonymous function:      return x + y
                       }                                                   }
Copy code

Therefore, it can be abbreviated as follows.

General functions: fun test(x : Int , y : Int) : Int = x + y
 Anonymous function: fun(x : Int , y : Int) : Int = x + y
 Copy code

As can be seen from the above two examples, the difference between anonymous functions and regular functions is that one has a function name and the other does not.

Example drill:

val test1 = fun(x : Int , y : Int) = x + y  // When the return value can be inferred automatically, it can be omitted, just like the function
val test2 = fun(x : Int , y : Int) : Int = x + y
val test3 = fun(x : Int , y : Int) : Int{
    return x + y
}

println(test1(3,5))
println(test2(4,6))
println(test3(5,7))
Copy code

The output result is:

8
10
12
 Copy code

From the above code, we can summarize the differences between anonymous functions and Lambda expressions:

  1. Parameters of anonymous functions are always passed inside parentheses. Lambda expressions can be abbreviated by omitting parentheses.
  2. In an unmarked return statement, the return value of an anonymous function is the value of its own function, while the return value of a Lambda expression is the value returned in the function containing it.

3.4 function literal with receiver

In kotlin, the function of calling Lambda expression by the specified recipient object is provided. In the function body of the function literal, a method on the recipient object can be called without any additional qualifiers. It is similar to an extension function, which allows you to access the members of the receiver object in the function body.

  • Anonymous function as recipient type

Anonymous function syntax allows you to directly specify the receiver type of function numeric value. It will be very useful if you need to declare a variable using the function type with receiver and use it later.

Example:

val iop = fun Int.( other : Int) : Int = this + other
println(2.iop(3))
Copy code

The output result is:

5
 Copy code
  • Lambda expression as receiver type

The premise of using Lambda expression as receiver type is that the receiving type can be inferred from the context.

Example: an official example is used here to illustrate

class HTML {
    fun body() { ...... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // Create recipient object
    html.init()        // Pass the receiver object to the lambda
    return html
}

html {       // lambda with receiver starts here
    body()   // Call a method of the recipient object
}
Copy code

3.5 closure

  • The so-called closure refers to the function contained in the function, where we can include (Lambda expression, anonymous function, local function, object expression). As we all know, functional programming is a good programming trend now and in the future. Therefore, Kotlin also has this feature.
  • As we all know, Java does not support closures. Java is an object-oriented programming language. In Java, objects are his first-class citizens. Functions and variables are second-class citizens.
  • Kotlin supports closures. Functions and variables are its first-class citizens, while objects are its second-class citizens.

Example: look at a piece of Java code

public class TestJava{

    private void test(){
        private void test(){        // Error because function inclusion is not supported in Java

        }
    }

    private void test1(){}          // Correctly, functions in Java can only be contained in objects+
}
Copy code

Example: look at a section of Kotlin code

fun test1(){
    fun test2(){   // Correct, because functions can be nested in Kotlin

    }
}
Copy code

Next, we will explain the expressions of several closures in Kotlin.

3.5.1 carrying status

Example: let the function return a function and carry the status value

fun test(b : Int): () -> Int{
    var a = 3
    return fun() : Int{
        a++
        return a + b
    }
}

val t = test(3)
println(t())
println(t())
println(t())
Copy code

Output results:

7
8
9
 Copy code

3.5.2 reference external variables and change the value of external variables

Example:

var sum : Int = 0
val arr = arrayOf(1,3,5,7,9)
arr.filter { it < 7  }.forEach { sum += it }

println(sum)
Copy code

Output results:

9
 Copy code

3.6 in Android development, write an Item click event for the RecyclerView adapter

class TestAdapter(val context : Context , val data: MutableList<String>)
    : RecyclerView.Adapter<TestAdapter.TestViewHolder>(){

    private var mListener : ((Int , String) -> Unit)? = null

    override fun onBindViewHolder(holder: TestViewHolder?, position: Int) {
        ...
        holder?.itemView?.setOnClickListener {
            mListener?.invoke(position, data[position])
        }

    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): TestViewHolder {
        return TestViewHolder(View.inflate(context,layoutId,parent))
    }

    override fun getItemCount(): Int {
        return data.size
    }

    fun setOnItemClickListener(mListener : (position : Int, item : String) -> Unit){
        this.mListener = mListener
    }

    inner class TestViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView)
}

// call
TestAdapter(this,dataList).setOnItemClickListener { position, item ->
        Toast.makeText(this,"$position \t $item",Toast.LENGTH_SHORT).show()
    }

Keywords: Java Android kotlin

Added by tbone05420 on Tue, 07 Dec 2021 12:38:41 +0200