Scoping functions are an important feature of Kotlin and can be divided into the following five types: let, run, with, apply, and Also. These five functions work in very similar ways, but we need to understand the differences between these five functions so as to make better use of them in different scenarios. After reading this article, you will learn:
- What are Kotlin’s scoped functions?
- Roles of the let, run, with, apply and ALSO scope functions;
- The difference of five kinds of scope functions;
- When and where to use these five scopes?
Kotlin’s scoped function
The Kotlin standard library contains several functions whose sole purpose is to execute blocks of code in the context of an object. When such a function is called on an object and a lambda expression is provided, it forms a temporary scope. In this scope, the object can be accessed without its name. These functions are called scoped functions.
In simple terms, scope functions are used to facilitate access and manipulation of an object. You can perform an empty check on it, modify its properties, or return its value directly. The following examples are provided to explain scope functions in detail.
role
2.1 the let
public inline fun <T, R> T.let(block: (T) -> R): R
Copy the code
The let function is an extension function of the parameterized type T. The object can be referred to by it within the let block. Returns the last line of the let block or specifies a return expression.
Let’s take a Book object as an example. The class contains the name and price of Book as follows:
class Book(a){
var name = "Data Structures"
var price = 60
fun displayInfo() = print("Book name : $name and price : $price")}Copy the code
fun main(args: Array<String>) {
val book = Book().let {
it.name = "Computer Networks"
"This book is ${it.name}"} This book is "computer network"Copy the code
In the example above, we use the let scope function on the Book object, add a string code to the last line of the function block, and print the Book object. We can see that the final console output is the string “This Book is computer network”.
According to our programming idea, print an object, the output must be an object, but use the let function, the output is the last string. This is due to the nature of the let function. Because in Kotlin, if the last statement in a let block is a non-assignment statement, by default it is a return statement.
So what happens if we change the last statement in the let block to an assignment statement?
fun main(args: Array<String>) {
val book = Book().let {
it.name = "Computer Networks"} print(book)} Console output: kotlin.unitCopy the code
You can see that we assign the name value of the Book object and print the Book object, but the console output is “kotlin.unit” because the last sentence in the let block is the assignment statement, and print treats it as a function.
This is the first point of let role setting: 1️
- The last statement in a let block is a return statement by default if it is not an assignment statement, or returns a Unit type if it is not
Let’s look at the second point of let: 2️
- Let can be used for empty security checks.
If you want to perform an operation on a non-empty object, you can use the secure call operator on it? . And call let to perform the operation in the lambda expression. Here’s a case:
var name: String? = null
fun main(args: Array<String>){ val nameLength = name? .let { it.length } ? :"Value when name is null"
print(nameLength)
}
Copy the code
We set name to an nullable string, using name? If name is not null, logic can enter the let function block. At this point, we may not see the advantages of letting empty judgment, but when you have a large number of name attributes to write, let is fast and concise.
Third point: 3️
- Let manipulates the results of the call chain.
The official tutorial gives an example of this, which is used directly here:
fun main(args: Array<String>) {
val numbers = mutableListOf("One"."Two"."Three"."Four"."Five")
val resultsList = numbers.map { it.length }.filter { it > 3 }
print(resultsList)
}
Copy the code
Our goal is to get values in the arraylist that are longer than 3. Since we have to print the result, we store the result in a separate variable and then print it. But using the “let” operator, we can change the code to:
fun main(args: Array<String>) {
val numbers = mutableListOf("One"."Two"."Three"."Four"."Five")
numbers.map { it.length }.filter { it > 3 }.let {
print(it)
}
}
Copy the code
With let, you can directly print the arraylist values larger than 3, eliminating the variable assignment step.
In addition, the let function has one more feature.
4️
- Let renames “It” as a readable lambda parameter.
Let is the context that references an object by using the “It” keyword, so this “It” can be renamed as a readable lambda argument, rename It to book as follows:
fun main(args: Array<String>) {
val book = Book().let {book ->
book.name = "Computer Networks"
}
print(book)
}
Copy the code
2.2 the run
The run function takes “this” as the context object and is called in the same way as let.
In addition, the first point: 1️ Run is more suitable when lambda expressions contain both object initialization and return value evaluation.
What does that mean? Let’s use an example:
fun main(args: Array<String>) {
Book().run {
name = "Computer Networks"
price = 30DisplayInfo ()}} Console output: Book name: Computer Network and price:30
Copy the code
What happens if I don’t use the run function? Take a look:
fun main(args: Array<String>) {
val book = Book()
book.name = "Computer Networks"
book.price = 30Book.displayinfo ()} Console output: Book name: Computer Network and price:30
Copy the code
The output is the same, but the degree of brevity that the run function brings to the code is obvious.
Beyond that, let’s look at some of the other benefits of the run function:
By looking at the source code, we know that there are two ways to declare the run function.
1. Like let, run is an extension function of T;
inline fun <T, R> T.run(block: T.() -> R): R
Copy the code
2. The second run is declared differently. It is not an extension function and has no input value in the block, so instead of passing objects and changing the type of properties, it allows you to execute a statement where an expression is needed.
inline fun <R> run(block: () -> R): R
Copy the code
The following uses the run function block to execute the method, rather than as an extension function:
run {
val book = Book()
book.name = "Computer Networks"
book.price = 30
book.displayInfo()
}
Copy the code
2.3 with
inline fun <T, R> with(receiver: T, block: T.() -> R): R
Copy the code
With is a non-extension function that directly enters an object receiver. When entering receiver, the properties of receiver can be changed. It also does the same thing as run.
Or a case study:
fun main(args: Array<String>) {
val book = Book()
with(book) {
name = "Computer Networks"
price = 40
}
print(book)
}
Copy the code
In the above example, if the with (T) type is passed a parameter book, you can access the name and price attributes of book in the with block and make changes.
With uses non-null objects and can be used when a function block does not need to return a value.
2.4 the apply
inline fun <T> T.apply(block: T.() -> Unit): T
Copy the code
Apply is an extension of T, similar to the run function in that it references the context of an object to “this” instead of “it” and provides an empty security check. However, apply does not accept return values from function blocks and returns its own t-type objects.
fun main(args: Array<String>) {
Book().apply {
name = "Computer Networks"
price = 40}} print (book) the console output: com. Fuusy. Kotlintest. Book @ 61 bbe9baCopy the code
The let, with, and run functions you saw earlier all return the value R. However, apply and also, viewed below, return T. For example, in Let, a value that is not returned in a function block ends up being of type Unit, but in Apply, when the object itself (T) is finally returned, it ends up being of type Book.
The apply function is primarily used to initialize or change an object, because it is used to return itself without using the object’s function.
2.5 also
inline fun <T> T.also(block: (T) -> Unit): T
Copy the code
Also is an extension of T that returns the same value as apply, which returns T directly. The use of the also function is similar to that of the let function, referring to the context of the object as “it” instead of “this” and providing an empty security checking aspect.
Because T takes the input to the block function, properties can be accessed using ALSO. Therefore, also is used without using or changing the properties of the object.
fun main(args: Array<String>) {
val book = Book().also {
it.name = "Computer Networks"
it.price = 40}} print (book) the console output: com. Fuusy. Kotlintest. Book @ 61 bbe9baCopy the code
differentiation
3.1 let & run
- Let refers to the context object as IT, while run refers to this;
- Run cannot rename “this” to a readable lambda parameter, whereas let can rename “it” to a readable lambda parameter. You can see the advantage of this feature when let is nested multiple times.
3.2 with & run
Both with and run do the same thing, calling the context object “this,” but they differ. Let’s look at the examples.
Use the with function first:
fun main(args: Array<String>) {
val book: Book? = null
with(book){
this? .name ="Computer Networks"
this? .price =40
}
print(book)
}
Copy the code
We create an nullable object, book, and modify the properties of the book object using the with function. The code is straightforward, so let’s replace with run and change the code to:
fun main(args: Array<String>) {
val book: Book? = nullbook? .run{ name ="Computer Networks"
price = 40
}
print(book)
}
Copy the code
The first call to the run function omits the this reference. The outer layer is null, and only non-null can enter the function block to operate on book.
- The run function is simpler than with, and empty security checks are less frequent than with.
3.3 apply & let
- Apply does not accept return values from function blocks and returns its own T objects, whereas let does.
- The Apply context object is referred to as “this” and the let as “it”.
When should I use apply, with, let, also, and run?
- To initialize an object or change its properties, use the
apply
- Can be used if you validate an object before assigning data to a property of the receiving object
also
- If you empty check an object and access or modify its properties, you can use
let
- Used if the object is not null and when no return value is required in the function block
with
- Use if you want to evaluate a value or limit the range of multiple local variables
run
conclusion
This is the role of Kotlin scope function and usage scenarios, in actual development of the Android, five function use frequency is very high, in use process, found that when the code logic less scope function can bring us the simplicity and readability of the code, but when the logic is complex, use different function, multiple stacking will reduce the readability. It is up to us to distinguish their respective characteristics so that they can be used in appropriate and complex scenarios.
I hope this article helped you. Thanks for reading.
The resources
Scoped function
Let, with, run, apply, also
Recommended reading
【Kotlin 】 Multidirectional processing coroutine anomalies
Let’s talk about Kotlin’s extension functions