Before the order

In Kotlin, functions exist as first-class citizens and can be passed as values. Lambda is a small piece of code wrapped as an anonymous function that is passed as parameter values for the function to use.

I met a lambda

Before Java8, when the external needs to set a certain event processing logic in a class, it is often necessary to define an interface (class), and create its anonymous instance as a parameter, and store the specific processing logic in a corresponding method to achieve:

mName.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
});
Copy the code

Processing logic

mName.setOnClickListener { 
}
Copy the code

Lambda is an anonymous function, but what about the argument list and return type? How about this:

val sum = { x:Int, y:Int -> 
    x + y
} 
Copy the code

Lambda expressions are always enclosed in curly braces, with -> separating the argument list from the function body. When lambda does its own type derivation, the return value type of the last line of the expression is the return value type of the lambda. A function’s required argument list, function body, and return type are now found.

Function types

It is said that a function can be passed as a variable value. What is the type of the variable? The types of function variables are collectively called function types. The so-called function types are the list of parameter types and return value types of the function declared.

Let’s start with a simple function type:

() -> Unit
Copy the code

Function types use the same -> delimiter as lambda, but separate the argument type list from the return value type. All function types have a parenthesized argument type list and return value type.

Some relatively simple function types:

// Type of function with no parameter and no return value (Unit return type cannot be omitted) () -> Unit // Type of function with type T and no return value (T) -> Unit // Type of function with type T and type A and no return value (same for multiple arguments) (T,A) -> Unit // The type of the function that takes T arguments and returns R values (T,A) -> RCopy the code

More complex function types:

(T,(A,B) -> C) -> R
Copy the code

Y = (A,B) -> C (T,Y) -> R (T,Y) -> R (T,Y) -> R (T,Y) -> R (T,Y) -> R (T,Y) -> R (T,Y) -> R

When displaying the type of the function that declares lambda, the types of the arguments in the lambda argument list can be omitted, and the return value type of the last line of the expression must be the same as the declared return value type:

Val min (Int,Int) -> Int = {x,y -> //ifExpression returns Intif (x < y){
        x
    }else{
        y
    }
}
Copy the code

Suspend functions are special function types that have suspend modifiers, such as suspend () -> Unit or suspend A.(B) -> C. (Hang up function belongs to coroutine knowledge, can be temporarily let go)

Type the alias

Type aliases provide alternative names for existing types. If the type name is too long, you can introduce a shorter name and replace the original type name with the new name. A type alias does not introduce a new type; it is equivalent to the corresponding underlying type. Use a type alias to give a function type another name:

typealias alias = (String,(Int,Int) -> String) -> String
typealias alias2 = () -> Unit
Copy the code

In addition to function types, we can alias other types:

typealias FileTable<K> = MutableMap<K, MutableList<File>>
Copy the code

Lambda statement simplification

Since Kotlin does type inference from context, we can use a simpler lambda for a cleaner syntax. Take the maxBy function, which takes a parameter of type (T) -> R:

data class Person(val age:Int,val name:String)
val persons = listOf(Person(17,"daqi"),Person(20,"Bob") // Find the oldest Person object // the code snippet in curly braces represents a lambda expression and is passed as an argument to the maxBy() method. persons.maxBy( { person: Person -> person.age } )Copy the code
  • When a lambda expression is the last argument to a function call, it can be placed outside the parentheses:
persons.maxBy() { person: Person -> 
    person.age 
}
Copy the code
persons.joinToString (""){person -> 
    person.name
}
Copy the code
  • When lambda is the only argument to a function, we can also remove the empty parentheses from the function:
persons.maxBy{ person: Person -> 
    person.age 
}
Copy the code
  • As with local variables, the type of a lambda argument can be inferred, and the type of the argument can be specified without being explicit:
persons.maxBy{ person -> 
    person.age 
}
Copy the code

Because the maxBy() function is declared, the argument type is always the same as the element type of the collection. The compiler knows that you are calling maxBy on the Person collection, so it can deduce that the argument type of the lambda expression is also Person.

public inline fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T? {}Copy the code

But if you use functions to store lambda expressions, you cannot deduce the parameter types from the context, so you must specify the parameter types explicitly.

Val getAge = {p:Person -> p.tage} // or explicitly specify the function type of the variable val getAge:(Person) -> Int = {p -> p.tage}Copy the code
  • The default parameter name it is generated when a lambda expression has only one parameter, the specified parameter name is not displayed, and the type of the parameter can be inferred
persons.maxBy{ 
    it.age
}
Copy the code

The default parameter name it, while concise, cannot be abused. When multiple lambdas are nested, it is best to explicitly declare the arguments to each lambda expression, otherwise it can be difficult to figure out exactly what value it is referencing, severely affecting code readability.

var persons:List<Person>? = null // It persons? .let { personList -> personList.maxBy{ person -> person.age } }Copy the code
  • Lambda can be passed as a named parameter
persons.joinToString (separator = "",transform = {person ->
    person.name
})
Copy the code
  • When a function needs two or more lambda arguments and cannot place more than one lambda outside the parentheses, it is best to use the regular pass-argument syntax.

SAM conversion

Looking back at the original setOnClickListener() method, the argument received is an interface instance, not a function type. How can you pass a lambda? Let’s start with a concept: functional interfaces:

A functional interface is one that defines only one abstract method

The SAM conversion converts the lambda display to a functional interface instance, but requires Kotlin’s function type to be the same as that of the SAM (a single abstract method). SAM transitions generally occur automatically.

SAM constructors are functions generated by the compiler to convert a lambda display into a functional interface instance. The SAM constructor takes a single argument — the lambda used as the single abstract method body of the functional interface — and returns an instance of that functional interface.

The SAM constructor has the same name as the Java functional interface.

Show call SAM constructor, simulate conversion:

#daqiInterface.javaPublic interface daqiInterface {String absMethod(); }#daqiJava.java
public class daqiJava {
    public void setDaqiInterface(daqiInterface listener){

    }
}
Copy the code
#daqiKotlin.ktVal interfaceObject = daqiInterface {// Mandatory String Value"daqi"} / / show passed to receive the functional interface instance function val daqiJava = daqiJava () / / here will not error daqiJava. SetDaqiInterface (interfaceObject)Copy the code

InterfaceObject type check:

if (interfaceObject is daqiInterface){
    println("This object is an instance of daqiInterface.")}else{
    println("This object is not an instance of daqiInterface")}Copy the code

When a single method receives multiple instances of a functional interface, either call the SAM constructor explicitly or hand it all over to the compiler to convert:

#daqiJava.java
public class daqiJava {
    public void setDaqiInterface2(daqiInterface listener,Runnable runnable){

    }
}
Copy the code
#daqiKotlin.ktVal daqiJava = daqiJava () / / all by compilers convert daqiJava setDaqiInterface2 ({"daqi"{}}). / / all manual explicit SAM conversion daqiJava setDaqiInterface2 (daqiInterface {"daqi" }, Runnable {  })
Copy the code

Note:

  • SAM transformations apply only to interfaces, not to abstract classes, even though these abstract classes have only one abstract method.
  • SAM conversions only work on methods in Java classes that receive instances of Java functional interfaces. Because Kotlin has full function types, there is no need to automatically convert functions to implementations of the Kotlin interface. Therefore, Kotlin functions that need to accept a lambda as an argument should use function types rather than functional interfaces.

Lambda expression with receiver

The lambdas mentioned so far are ordinary lambdas, but there is another type of lambdas: those with a receiver.

Type definition of lambda with receiver:

A.() -> C 
Copy the code

Represents A function that can be called on A receiver object of type A and returns A value of type C.

The benefit of a lambda with a receiver is that the body of the lambda function can use members (attributes or methods) of the receiver object directly without any additional qualifiers, and this can also be used to access the receiver object.

In the familiar extension function, the this keyword also executes an instance object of the extension class, and can also be omitted. Extension functions are in a sense functions with receivers.

Extension functions are very similar to lambda with receivers in that both sides need a receiver object, and both sides can call its members directly. If you think of ordinary lambda as the anonymous way of ordinary functions, then a lambda with a receiver type can be thought of as the anonymous way of extension functions.

Kotlin’s standard library provides lambda expressions with receivers: with and apply

val stringBuilder = StringBuilder()
val result = with(stringBuilder){
    append("Daqi is trying to learn Android")
    append("Daqi is trying to learn from Kotlin.") // The last expression returns this.toString()} // Prints the string added above println(result)Copy the code

The with function explicitly accepts the receiver and returns the return value of the last expression of the lambda as the with function.

Look at the definition of the with function:

Public inline fun <T, R> with(receiver: T, block: T.() -> R): R {}Copy the code

The function type of its lambda indicates that the parameter type and return value type can be different values, that is, a value can be returned that is inconsistent with the receiver type.

The Apply function is almost identical to the with function, except that apply always returns a receiver object. Refactor the with code:

val stringBuilder = StringBuilder().apply {
    append("Daqi is trying to learn Android")
    append("Daqi is trying to learn from Kotlin.")
}
println(stringBuilder.toString())
Copy the code

View the definition of the apply function:

Public inline fun <T> t.ply (block: T.() -> Unit): T {}Copy the code

Function is declared as an extension function of type T and returns an object of type T. Because of its generics, you can use Apply on any object.

The Apply function is useful when you create an object and need to initialize it. In Java, the Builder object is typically used.

Usage scenarios for lambda expressions

  • Scenario 1: Lambda is used with collections, which is the most classic use of lambda. You can filter, map, and other operations on the collection.
val languages = listOf("Java"."Kotlin"."Python"."JavaScript")
languages.filter {
    it.contains("Java")
}.forEach{
    println(it)
}
Copy the code

  • Scenario 2: Substitute functional interface instances
// Replace the view. OnClickListener interface mname. setOnClickListener {} // replace the Runnable interface mhandler. post {}Copy the code
  • Scenario 3: A function that needs to receive a function type variable
Fundaqi (string:(Int) -> string){}Copy the code

Limited to return

The return value of the last expression in the lambda is used as the return value of the lambda. This return occurs implicitly and requires no additional syntax. But when multiple lambdas are nested and you need to return the outer lambdas, you can use finite returns.

A finite return is a labeled returnCopy the code

The label is typically the name of the function that receives a lambda argument. When you need to explicitly return a lambda result, you can return the result in the form of a finite return. Example:

val array = listOf("Java"."Kotlin")
val buffer = with(StringBuffer()) {
    array.forEach { str ->
        if (str.equals("Kotlin")){// Returns a StringBuffer to which the Kotlin string is addedreturn@with this.append(str)
        }
    }
}
println(buffer.toString())
Copy the code

Naked returns are prohibited inside lambda expressions because an unlabeled return statement is always returned in a function declared with the fun keyword. This means that the return in a lambda expression will return from the function that contains it.

Fun main(args: Array<String>) {StringBuffer().apply {// Print the first daqi println("daqi")
       return} // Print the second daqi println("daqi")}Copy the code

The result: after printing for the first time, the main function exits.

Anonymous functions

Lambda expression syntax lacks the ability to specify the return type of a function, and anonymous functions can be used when you need to specify the return type explicitly. Anonymous functions are declared the same as regular functions, except that their names are omitted.

fun(x: Int, y: Int): Int {
    return x + y
}
Copy the code

Unlike lambda, a return in an anonymous function returns from an anonymous function.

Lambda variable capture

In Java, when an anonymous inner class or lambda is declared within a function, the anonymous inner class can reference the parameters and local variables of the function, but these parameters and local variables must be final. Kotlin’s lambda also has access to function parameters and local variables, and is not limited to final variables, and can even modify non-final local variables! Kotlin’s lambda expressions are closures in the real sense.

fun daqi(func:() -> Unit){
    func()
}

fun sum(x:Int,y:Int){
    var count = x + y
    daqi{
        count++
        println("$x + $y+ 1 =$count")}}Copy the code

Normally, the life of a local variable is limited to the function that declared it, and the local variable is destroyed after the function has been executed. But after a local variable or parameter is captured by lambda, blocks of code that use that variable can be stored and deferred. Why is that?

When a final variable is captured, it is copied and stored with the lambda code that uses the final variable. Non-final variables are encapsulated ina final Ref wrapper class instance, and then stored with the same final variables as lambda using that variable. When you need to modify this non-final reference, change the layout variables stored in the wrapper class by getting an instance of the Ref wrapper class. So lambda can still only capture final variables, but Kotlin hides this wrapper.

View source code:

Public static final void sum(final int x, final int y) {// Create an IntRef wrapper. Final IntRef count = new IntRef(); count.element = x + y; daqi((Function0)(newFunction0() {
     public Object invoke() {
        this.invoke();
        return Unit.INSTANCE;
     }

     public final void invokeInt var10001 = count. Element++; String var1 = x +"+" + y + "+ 1 ="+ count.element; System.out.println(var1); }})); }Copy the code

Note: For lambda modification local variables, this is triggered only when the lambda expression is executed.

Members of the reference

Lambda can pass blocks of code as arguments to functions, but what happens when the code I need to pass is already defined as a function? Should I write a lambda that calls that function? Kotlin and Java8 allow you to use member references to convert a function to a value and then pass it.

A member reference is used to create a function value that calls a single method or accesses a single property.Copy the code
data class Person(val age:Int,val name:String)

fun daqi(){
    val persons = listOf(Person(17,"daqi"),Person(20,"Bob"))
    persons.maxBy({person -> person.age })
}
Copy the code

In Kotlin, when you declare a property, you also declare the corresponding accessors (get and set). The accessor method for the Age attribute already exists in the Person class, but we also have a layer of lambda nested outside when we call the accessor. Optimized with member references:

data class Person(val age:Int,val name:String)

fun daqi(){
    val persons = listOf(Person(17,"daqi"),Person(20,"Bob"))
    persons.maxBy(Person::age)
}
Copy the code

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

Both top-level functions and extension functions can be represented using member references:

// The top-level function fundaqi(){} // Extend the fun Person function.getPersonAge(){} fun main(args: Array<String>) {// Run (::daqi) // Extend function member reference Person(17,"daqi").run(Person::getPersonAge)
}
Copy the code

Constructors can also be represented by member references:

val createPerson = ::Person
val person = createPerson(17,"daqi")
Copy the code

After Kotlin1.1, the member reference syntax supports capturing method references on specific instance objects:

val personAge = Person(17,"name")::age
Copy the code

Performance optimization for Lambda

Since Kotlin1.0, every lambda expression is compiled into an anonymous class, incurs additional overhead. You can use inline functions to optimize the extra cost of lambda.

Inline functions are functions that use inline modifications. Instead of generating the code for the function call where the function is used, the compiler replaces each function call with the actual code for the function implementation. Most library functions in Kotlin are marked inline.

References:

  • Kotlin in Action
  • Kotlin website

Android Kotlin series:

Kotlin’s Knowledge generalization (I) — Basic Grammar

Kotlin knowledge generalization (2) – make functions easier to call

Kotlin’s knowledge generalization (iii) — Top-level members and extensions

Kotlin knowledge generalization (4) – interfaces and classes

Kotlin’s knowledge induction (v) — Lambda

Kotlin’s knowledge generalization (vi) — Type system

Kotlin’s knowledge induction (7) — set

Kotlin’s knowledge induction (viii) — sequence

Kotlin knowledge induction (ix) — Convention

Kotlin’s knowledge induction (10) — Delegation

Kotlin’s knowledge generalization (xi) — Higher order functions

Kotlin’s generalization of knowledge (xii) – generics

Kotlin’s Generalization of Knowledge (XIII) — Notes

Kotlin’s Knowledge induction (xiv) — Reflection