Standard functions such as apply, let, run, with, and also are defined in Kotlin's Standard.kt file.
Extension functions can be used to simplify complex code.
Let's take a look at how each function is implemented in the Standard.kt file and what the differences are.
First let's look at the apply function.
The implementation part of the apply function is as follows.
In the implementation part, the T.apply part is defined as an extension function.
Because Generic is used, the apply function can be used as an extension function in any object.
And the block: T.() part of the implementation part means the receiving object-specified lambda.
The receiving object-specified lambda can bind a specific object to this in the lambda expression.
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
Below is the code using apply.
Change the properties of the Person object created in the sample code and save it in the person variable.
The reason it can be stored in the person variable is because the apply function returns the Person object that has been changed in the lambda expression.
If you look at the implementation part of the apply function, you can see that the return value is the receiving object.
val person = Person().apply {
name = "John"
age = 20
}
In the sample code above, the Person object is the receiving object, so the Person object becomes this in the lambda expression.
The code without omitting this in the sample code is as follows
val person = Person().apply {
this.name = "John"
this.age = 20
}
Let's learn run and with together
The reason the two functions are described together is that the operation method of run and with is the same.
And both functions take the receiving object as an argument and return the result of the lambda expression.
The three functions apply, run, and with all receive a lambda that lambda with receiver as an argument.
For the rest of the standard functions, the general lambda expression is passed as an argument, not the receiving lambda with receiver.
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
The operation methods of run and with both take the receiving object as an argument and return the result of the lambda expression.
The difference is that run is an extension function and with receives a receiver object.
with receives two arguments: a receiver object and a lambda expression.
If you think that with argument takes only one person, remember that the lambda expression passed as the last argument can be moved out of parentheses.
Person("John", 20).run {
println("name => $name")
println("age = > $age")
}
val person = Person("John", 20)
with(person){
println("name => $name")
println("age = > $age")
}
Next, let's look at the return values of run and with.
apply returns the receiving object.
However, run and with return the result of a lambda expression.
In the sample code, the Unit type, which returns no value, is inverse.
In the following code, if you change it as follows, the return value becomes String.
val personName: String = Person("John", 20).run {
println("age = > $age")
name
}
println("name => $personName")
also is an extension function and it is the same as apply function in that it returns a receiving object, but the argument is a general lambda expression, not a lambda with receiver.
T.() stands for receiving lambda with receiver (T) stands for general lambda expression.
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
The sample code using the also function is as follows.
val person = Person("John", 20).also{
println("name => ${it.name}")
println("age = > ${it.age}")
}
let receives a lambda expression as an argument and inverts the result of the lambda expression.
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
val person: = Person().apply {
this.name = "John"
this.age = 20
}
person.let {
println("name => ${it.name}")
println("age = > ${it.age}")
}
When used with the null-safe operator, the lambda expression can be implemented to execute only if the receiving object is not a null value.
person?.let {
println("name => ${it.name}")
println("age = > ${it.age}")
}
takeIf returns the receiving object if the conditional statement of Ramdisak is true, and returns null if it is false.
In the code below, the age of the receiving object is printed.
// age => 20
val person = Person().apply {
this.name = "John"
this.age = 20
}
person.takeIf {
it.age >= 20
}?.let{
println("age = > ${it.age}")
} ?: throw IllegalStateException("")
Contrary to takeIf, if the conditional statement of Ramdisak is false, the receiver object is returned, and if true, null is returned.
The code below throws IllegalStateException.
// java.lang.IllegalStateException:
val person = Person().apply {
this.name = "John"
this.age = 20
}
person.takeUnless {
it.age >= 20
}?.let{
println("age = > ${it.age}")
} ?: throw IllegalStateException("")