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:
- The code is pretty neat
Disadvantages:
-
Code readability is poor
-
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:
list.maxBy({ number -> number }))
Passed as a normal parameter{ 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
- If the function argument type has only one argument, it can be used directly
it
Instead of.selector: (T) -> R
There is only one parameterT
, soT
You can use it when you use itit
Instead of, so you can use it directly:
list.maxBy { it }
Copy the code
- 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
- 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
- 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:
-
Kotlin comes after 1.4
-
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)