Lambda expressions

What is?

A: In Kotlin, a lambda expression is a type of expression whose scope is delimited with {} and whose body is -> distinguished from that of a function. It is essentially a block of code, which you can also understand as a callable function type object (but it is not, according to decomcompilations, it is implemented in many ways. Generate a function, pass a function reference, etc.)

I’m going to take the time to analyze some of the ways lambda can be implemented

// val funcType: (Int, Int) -> Int = {x, y -> x + y}
val funcType: (Int.Int) - >Int = {x: Int, y: Int -> x + y}


val sum01 = funcType.invoke(1.2)
// It's like this:
val sum02 = funcType(1.2)

// {x: Int, y: Int -> x + y}(1, 2)
{x: Int, y: Int -> x + y}.invoke(1.2)
Copy the code

{x: Int, y: Int -> x + y} is a lambda expression

Functype.invoke (1, 2) and {x: Int, y: Int -> x + y}. Invoke (1, 2) denotes a lambda function object that calls a function

Nothing hard to understand, just think of it as a String funcType, except that it has only one function invoke, right

What are the advantages and disadvantages?

Advantages:

  1. The code is pretty neat

Disadvantages:

  1. Code readability is poor

  2. When lambda is used, notice this in some cases, sometimes not, but sometimes not (this will be explained later), and anonymous object creation of this and lambda’s this may sometimes point to different objects in the same scenario. Lambda, for example, usually points to an external class surrounding the lambda, while the anonymous object this points to the anonymous object itself

How does it work? What are the application scenarios?

The main use is for passing arguments, usually functions. Arguments are of function type, and we pass a lambda expression

Here’s what I wrote in emulation of the maxBy function

fun <T, R : Comparable<R>> List<T>.maxBy(selector: (T) - >R): R? = listIterator().run {
    if(! hasNext())return null
    val maxElement = next()
    var maxVal = selector(maxElement)
    while (hasNext()) {
        val nextElement = next()
        val nextVal = selector(nextElement)
        if (nextVal > maxVal) {
            maxVal = nextVal
        }
    }
    return maxVal
}
Copy the code

And the way to call

    val list: List<Int> = listOf(100.2.3.400.59999.66.700)
// println(list.maxBy({ number -> number }))
// println(list.maxBy { number -> number })
    println(list.maxBy { it })
Copy the code

We need to be aware of these issues when using lambda:

  1. list.maxBy({ number -> number }))Passed as a normal parameter
  2. { number -> number }If you have only one function argument, you can omit the argument. If the function type argument is at the end of the argument, for example:sun(a: Int, b: Int, func: (Int, Int) -> Int)It can be called like this:
sum(1.2) { a, b -> a + b }
Copy the code
  1. If the function argument type has only one argument, it can be used directlyitInstead of.selector: (T) -> RThere is only one parameterT, soTYou can use it when you use ititInstead of, so you can use it directly:
list.maxBy { it }
Copy the code
  1. The body of a lambda function can have multiple lines, with the return value being the last line by default
val sum = {x: Int, y: Int -> 
    println("x = ${x}, y = ${y}")
    x + y
}
Copy the code

Lambda is different in Kotlin and Java

In Java, lambda’s use of external local variables requires final modification, but not in Kotlin, where the value of a variable in this case can be changed. In Kotlin, it is not limited to accessing a final variable; inside Kotlin, it can be modified

val list = listOf(1.2.3.4.5.6.7.8.9)
var odd = 0
var even = 0
list.forEach {
    if (0 == it % 2) {
        even++
    } else {
        odd++
    }
}
println("Singular:$odd, the number:$even")
Copy the code

Note that the code for lambda does not execute immediately, such as button.onclink {i++}, which will only execute i++ when the event is triggered

Kotlin supports the implementation of lambda internal modification variables by:

Wrap all the variables captured by lambda, like in Java you can change the object to an AtomicInteger or something like that, and then store the value inside the AtomicInteger, This way, even if lambda captures the AtomicInteger object, only the reference is captured and final is added to the reference, and we modify the value behind the reference, so we can modify the value inside the lambda as well as outside it

Kotlin also uses this approach, but instead of storing the AtomicXXXXX family of classes, it stores a class called Ref

closure

As mentioned in the previous section, closures are things where an inner function can access local variables of an outer function and various inner classes, but an outer function can’t use variables inside the function, as lambda does

The neat thing about lambda, in my view, is that you define lambda and you invoke lambda

On the defining side, any variable that precedes the definition of the lambda can be used unconditionally in the lambda (in other words, lambda captures references to external classes).

Since lambda captures references to external classes, serialization issues need to be addressed

On the caller, lambda arguments run through the caller’s scope. As long as the caller passes arguments in, lambda can use some of the variables put in the call. If the caller passes this, lambda has access to all of the variables available to this on both sides

Members of the reference

This section can be studied as a ++ & reference

A member reference is a process of evaluating, similar to defining a reference that points to the address of the target (static members get the offset address).

The :: reference operator can be used on member properties/member functions/extension functions/extension properties/top-level properties/top-level functions/classes, etc

Person defines which class it is in, :: for reference, and age for target

// This is the offset address of the Person class name attribute
val refName: KMutableProperty1<Person, String> = Person::name
Copy the code

Then you will find that the import kotlin. Reflect. KMutableProperty1 is to reflect, is reflecting the inside of the bag type

So we operate the refName more associated with reflection

Member references can be used interchangeably with lambda

Here is a general description of his usage, take time to devote a chapter to study

Collection and lambda

val list = listOf(Person("haha".22), Person("xixi".21), Person("dd".23))
list.maxBy(Person::age)

println(list.filter { it.age > 22 }.map(Person::name))
Copy the code

Lambda may look simple, but careless use of lambda may make the program more inefficient, as in the above two functions, maxBy traverses the bottom line once, while the program traverses the bottom line twice, and the programmer sees only one line of code

The following code is more efficient for programmers to write by hand

println(list.filter { it.age > 22 }.map(Person::name))

var nameList: MutableList<String> = mutableListOf()
list.forEach {
    if (it.age > 22) {
        nameList.add(it.name)
    }
}
println(nameList)
Copy the code

All Any count find Applies to the collection

  • All evaluates whether all elements in the set meet the criteria. If one element does not meet the criteria, return false, otherwise return true
  • Any checks whether there is at least one in the collection that meets the condition, and returns true if so, false otherwise
  • The count judgment set has several that satisfy the condition judgment
  • Find Finds the first element in the collection that meets the criteria
val list = listOf(1.2.3)
val all = list.all { it > 2 }
println(all)
val any = list.any { it > 2 }
println(any)
val count = list.count { it >= 2 }
println(count)
val find = list.find { it == 2 }
println(find)
Copy the code

GroupBy grouping

val map = list.groupBy { it.name.first() }
for (entry in map.entries) {
    println("key = ${entry.key}, value = ${entry.value}")}Copy the code
key = h, value = [Person{name = heihei, age = 34}, Person{name = haha, age = 22}, Person{name = hoho, age = 23}]
key = z, value = [Person{name = zhazha, age = 23}]
key = d, value = [Person{name = dd, age = 12}]
Copy the code

So if you’ve learned SQL, this is the grouping of that

FlatMap and FlatTen handle elements in nested collections

Flat, smooth

private val list = listOf(
   Book("k language", listOf("zhazha"."haha"."xixi"."heihei"."hoho")),
   Book("v language", listOf("zhazha"."haha"."heihei"."hoho")),
   Book("l language", listOf("zhazha"."haha"."xixi"."heihei")),
   Book("j language", listOf("zhazha"."haha"."xixi"."hoho")))val map = list.flatMap { it.title.toList() }
map.forEach {
   print("$it")}Copy the code

What a Flat Map does is it takes one or more bricks (attributes or set attributes) from a bunch of objects, divides them into piles, flattens them out, and joins them together

The flatten function does much the same thing, but it works with the List > approach

Lazy set operations: sequences

list.asSequence().filter { it.age > 60 }.map { it.name }.toList().forEach {println(it)}
Copy the code

It is lazy in that it avoids temporary objects created in filter, and temporary objects created in map calculations, which use Interator for lazy operations

But in practice, I don’t see how fast it is

// Load the object
val list = mutableListOf<Person>()
for (i in 1.10000.) {
   list.add(Person(UUID.randomUUID().toString(), Random.nextInt() % 150))}// The normal mode of lambda
var start = Instant.now()
list.filter { it.age > 60 }.map { it.name }
var duration = Duration.between(start, Instant.now())
println()
println(duration.toMillis())

// Lazy mode
start = Instant.now()
list.asSequence().filter { it.age > 60 }.map { it.name }
duration = Duration.between(start, Instant.now())
println()
println(duration.toMillis())

// Write code manually
start = Instant.now()
val mutableList = mutableListOf<String>()
list.forEach {
   if (it.age > 60) {
      mutableList.add(it.name)
   }
}
duration = Duration.between(start, Instant.now())
println()
println(duration.toMillis())
Copy the code
20   17

34   22

3    4
Copy the code

No matter how many times I try, it’s always the case, maybe there aren’t enough people…? Will lead to inertia no??

I don’t think so. The lazy approach may not be much more efficient, but it should be significant in terms of memory savings

But manual coding is the most efficient anyway, right

= = = = = = = = = = = = = = = = = = = 2021.10.05 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

Found a possible cause of sluggishness, in terms of inline and sequence

Sequence functions are not inline when lambda is used, so objects are generated each time the function is called, whereas normal collection functions are inline but operate on the intermediate collection behind each new method, which is also slow

So it depends

The middle and end operations of a sequence

Intermediate operations are always lazy, and terminal operations trigger the latency of all lazy operations, which begin execution directly

The difference between a sequence and a set

  1. A sequence operation is one element at a time, one element performs a series of functions and then stays and switches to another element, whereas a sequence operation is a set of operations, one function completes and leaves an intermediate set, and then passes it on to the next function, doing the operation

For example, in the figure above, it is obvious that the source code of the two is roughly:

listOf(1.2.3.4).map { it * it }.find { it > 3 }
listOf(1.2.3.4).asSequence().map { it * it }.find { it > 3 }
Copy the code

The left side is just like a class of students in a school going to get vaccinated. These students go to get the first injection (MAP), and after the whole class gets the first injection, they go to check who has developed antibodies (find).

On the right, just like ordinary people go to get an injection, they make an appointment to get the number and queue up to get the vaccine (MAP). After finishing the vaccine, they don’t need to wait for others to directly check whether antibodies are generated (FIND).

One finishes waiting for someone else, the other goes straight to the next task

  1. Collections require attention to the order in which functions are called, not columns
listOf(1.2.3.0.4).map{ it * it }.filter{ it is Double }
listOf(1.2.3.0.4).filter { it is Double }.map{ it * it }
Copy the code

This difference, need not I say more to understand the picture (do not understand, go back to primary school study)

The more you filter, the fewer subsequent collections, and the more efficient you are

How lambda is implemented

fun postponeComputation(id: Int, runnable: () -> Unit) {
   println("id = $id")
   runnable()
}

fun handleComputation(id: String) {
   postponeComputation(1000) { println(id) }
}
Copy the code

Lambda is essentially a small piece of code that can be passed to other functions, which we can think of as a reference to an anonymous function + function (function argument list and function body) that can be passed as arguments

Following the coding above, we show the underlying process

fun postponeComputation(id: Int, runnable: Function0<Unit>) {
   println("id = $id")
   runnable()
}
Copy the code

Then the code for the following function would look like this:

fun handleComputation(id: String) {
   postponeComputation(1000.object : Function0<Unit> {
       val id = id
       fun void invoke(a): Unit {
           println(this.id)
       }
   })
}
Copy the code

Of course the actual code may not be written that way, but the main idea is the same

Lambda this and the anonymous object this

fun postponeComputation(id: Int, runnable: () -> Unit) {
   println("id = $id")
   runnable()
}

fun handleComputation(a) {
   postponeComputation(1000) {
      // This is an error
      println(this) // error
   }
   postponeComputation(1999.object : Function0<Unit> {
      override fun invoke(a) {
         println(this)}}}Copy the code

This code will report an error

The handleComputation function is static, so there is no this at all, but postponeComputation(1999, object: The object that this of Function0

can use and point to is the anonymous object itself

But if I write the code like this,

class LambdaRealizationDemo01  {
   fun handleComputation(a) {
      postponeComputation(1000) {
         // No error was reported
         println(this)
      }
      postponeComputation(1999.object : Function0<Unit> {
         override fun invoke(a) {
            println(this)}})}}Copy the code
id = 1000
lambda09.LambdaRealizationDemo01@6108b2d7
id = 1999
lambda09.LambdaRealizationDemo01$handleComputation$2@13969fbe
Copy the code

It can be seen directly that the two “this” points to different objects. In some lambda usage scenarios, we should pay special attention to who “this” points to.

The following code is Java source code:

public final class LambdaRealizationDemo01 {
    public final void handleComputation(a) {
        LambdaRealizationDemo01Kt.postponeComputation(1000, (Function0<Unit>)((Function0)new Function0<Unit>(this) {final /* synthetic */ LambdaRealizationDemo01 this$0;
            {
                this.this$0 = $receiver;
                super(0);
            }

            public final void invoke(a) {
                LambdaRealizationDemo01 lambdaRealizationDemo01 = this.this$0;
                boolean bl = false; System.out.println(lambdaRealizationDemo01); }})); LambdaRealizationDemo01Kt.postponeComputation(1999, (Function0<Unit>)((Function0)new Function0<Unit>(){

            public void invoke(a) {
                boolean bl = false;
                System.out.println(this); }})); }}Copy the code

Look closely at the difference:

LambdaRealizationDemo01Kt.postponeComputation(1000, (Function0<Unit>)((Function0)new Function0<Unit>(this)

LambdaRealizationDemo01Kt.postponeComputation(1999, (Function0<Unit>)((Function0)new Function0<Unit>()

Copy the code

Conclusion: Lambda has a function called capture, which captures variables in the outer scope. In the above example, the lambda captures this in the outer function scope, which is the LambdaRealizationDemo01 object

SAM conversion

In some cases, you may have to create an anonymous object using a new interface, as shown in the following figure

It requires a JavaInterface interface object, not an object of type () -> Unit

So you have to use the Object: interface

interface JavaInterface {
   fun doSomething(person: Person)
}

fun delegateWork(j: JavaInterface) {
   val person = Person("zha"."zha")
   j.doSomething(person)
}

fun main(a) {
   delegateWork(object : JavaInterface {
      override fun doSomething(person: Person) {
         println("name = ${person.firstName + person.lastName}")}}}Copy the code

But Kotlin also offers a more efficient way, which is called SAM constructors, a way to turn lambdas into constructors

But there is a prerequisite for using this method:

  1. Kotlin comes after 1.4

  2. Interfaces need to be special, and there are two kinds of special interfaces

  • The interface needs to be declared as the SAM interface, as in Kotlin: Fun Interface JavaInterface {}, which adds fun in front of the interface

  • If the interface is a Java interface, you can directly use it

Only these two interfaces can implement SAM transformation

The first version of Kotlin on my machine is:

Version 1.5

Next, let’s try changing the JavaInterface to a Java interface

delegateWork(JavaInterface { "zhazha" })
// We can also hide the JavaInterface name
delegateWork { "zhazha" }
Copy the code

Now change the JavaInterface interface to kotlin’s interface

fun interface KotlinInterface {
   fun doSomething(str: String?).: String?
}

fun kotlinDelegateWork(k: KotlinInterface) {
   k.doSomething("hello kotlin")}Copy the code

Note that interface needs to add fun to be kotlin’s SAM functional interface

kotlinDelegateWork(KotlinInterface { "zhazha" })
Before Kotlin 1.4, this was not possible, but now it is
kotlinDelegateWork { "zhazha" }
Copy the code

Kotlin’s lambda is inefficient, so we can modify functions with inline modifiers wherever lambda is used, such as inline fun doSomething(f: (Int) -> Int): Int

Note that the inline modifier is appropriate only for function type parameters and should not be used for SAM functional interface parameters, such as inline fun doSomething(j: JavaInterface)