Scope function
There are five scope functions for Kotlin: let, run, with, apply, and also.
These functions basically do the same thing: execute a block of code on an object.
Here are typical usages of scope functions:
val adam = Person("Adam").apply { age = 20 city = "London" } println(adam)
If you do not use apply, the name must be repeated every time you assign a value to the newly created object property.
val adam = Person("Adam") = 20 = "London" println(adam)
Scoping functions do not introduce any new techniques, but they can make your code more concise and easy to read.
In fact, the same function may be implemented with multiple scope functions, but we should use appropriate scope functions according to different scenarios and needs in order to achieve a more elegant implementation.
If you want to directly view the differences between scope functions and the usage scenario summary table,Please click here。
Below we will describe in detail the differences and convention usage of these scope functions.
the difference
There are two main differences between scope functions:
- How to reference a context object
- Return value
How to reference a context object: this or it
In the lambda expression of a scoped function, the context object can be accessed instead of using its actual name but with a shorter reference (this or it).
There are two ways that scope functions refer to context objects:
- As the recipient of the lambda expression (this): run, with, apply
- As a parameter (it): let, also
fun main() { val str = "Hello" // this { println("The receiver string length: $length") //println("The receiver string length: ${}") // The effect is the same as the previous sentence } // it { println("The receiver string's length is ${}") } }
As the recipient of the lambda expression
run, with and apply the context object as the lambda expression recipient, referring to the context object through the keyword this. This can be omitted to make the code shorter.
Usage scenario: mainly operate on members of a context object (accessing properties or calling functions).
val adam = Person("Adam").apply { age = 20 // Same as = 20 or = 20 city = "London" } println(adam)
As an argument to the lambda expression
let and also take the context object as lambda expression parameters. If no parameter name is specified, the object can be accessed with the implicit default name it. it is shorter than this, and expressions with it are usually easier to read. However, when an object function or property is called, the object cannot be accessed implicitly like this.
Usage scenario: mainly operate on context objects and use them as parameters.
fun getRandomInt(): Int { return (100).also { writeToLog("getRandomInt() generated value $it") } } val i = getRandomInt()
Additionally, when a context object is passed as a parameter, a custom name within scope can be specified for the context object (for improving code readability).
fun getRandomInt(): Int { return (100).also { value -> writeToLog("getRandomInt() generated value $value") } } val i = getRandomInt()
Return value
According to the return result, scope functions can be divided into the following two categories:
- Returns the context object: apply, also
- Return the result of lambda expression: let, run, with
The appropriate function can be selected based on subsequent operations in the code.
Return the context object
The return values of apply and also are the context object itself. Therefore, they can be included in the call chain as auxiliary steps: you can continue making chained function calls on the same object.
val numberList = mutableListOf<Double>() { println("Populating the list") } .apply { add(2.71) add(3.14) add(1.0) } .also { println("Sorting the list") } .sort()
They can also be used in return statements of functions that return context objects.
fun getRandomInt(): Int { return (100).also { writeToLog("getRandomInt() generated value $it") } } val i = getRandomInt()
Return the result of the lambda expression
let, run and with return the result of the lambda expression. Therefore, they can be used when they need to use their results to assign values to a variable, or when they need to chain operations on their results, etc.
val numbers = mutableListOf("one", "two", "three") val countEndsWithE = { add("four") add("five") count { ("e") } } println("There are $countEndsWithE elements that end with e.")
In addition, you can also ignore the return value and use the scope function to create a temporary scope for the variable.
val numbers = mutableListOf("one", "two", "three") with(numbers) { val firstItem = first() val lastItem = last() println("First item: $firstItem, last item: $lastItem") }
Contracted usage
let
The context object is accessed as a parameter (it) of the lambda expression. The return value is the result of the lambda expression.
let can be used to call one or more functions on the result of a call chain. For example, the following code prints the results of two operations on a collection:
val numbers = mutableListOf("one", "two", "three", "four", "five") val resultList = { }.filter { it > 3 } println(resultList)
Using let, it can be written like this:
val numbers = mutableListOf("one", "two", "three", "four", "five") { }.filter { it > 3 }.let { println(it) // If necessary, you can call more functions}
If the code block only contains a single function with it as an argument, you can use method reference (::) instead of the lambda expression:
val numbers = mutableListOf("one", "two", "three", "four", "five") { }.filter { it > 3 }.let(::println)
let is often used to execute code blocks using only non-null values. To perform operations on non-empty objects, use the safe call operator ?. on it and call let to perform operations in the lambda expression.
val str: String? = "Hello" //processNonNullString(str) // Compilation error: str may be emptyval length = str?.let { println("let() called on $it") processNonNullString(it) // Compile by: 'it' must not be empty in '?.let { }' }
Another case of using let is to introduce scoped local variables to improve the readability of the code. To define a new variable for a context object, provide its name as a lambda expression parameter to replace the default it.
val numbers = listOf("one", "two", "three", "four") val modifiedFirstItem = ().let { firstItem -> println("The first item of the list is '$firstItem'") if ( >= 5) firstItem else "!" + firstItem + "!" }.toUpperCase() println("First item after modifications: '$modifiedFirstItem'")
with
A non-extended function: a context object is passed as a parameter, but inside a lambda expression it can be used as a receiver (this). The return value is the result of the lambda expression.
It is recommended to use with to call functions on context objects instead of using lambda expression results. In the code, with can be understood as "For this object, do the following."
val numbers = mutableListOf("one", "two", "three") with(numbers) { println("'with' is called with argument $this") println("It contains $size elements") }
Another use scenario for with is to introduce a helper object whose properties or functions will be used to calculate a value.
val numbers = mutableListOf("one", "two", "three") val firstAndLast = with(numbers) { "The first element is ${first()}," + " the last element is ${last()}" } println(firstAndLast)
run
The context object is accessed as the receiver (this). The return value is the result of the lambda expression.
run does the same thing as with , but is called the same way as let - as an extension function for context objects.
Run is useful when a lambda expression contains both object initialization and return value calculations.
val service = MultiportService("", 80) val result = { port = 8080 query(prepareRequest() + " to port $port") } // If the same code is written using the let() function:val letResult = { = 8080 (() + " to port ${}") }
In addition to calling run on the receiver object, it can also be used as a non-extended function. A non-extended run allows you to execute a block of multiple statements where you need an expression.
val hexNumberRegex = run { val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } for (match in ("+1234 -FFFF not-a-number")) { println() }
apply
The context object is accessed as the receiver (this). The return value is the context object itself.
Use apply for blocks of code that do not return values and are mainly run on members of the receiver (this) object. A common situation with apply is object configuration. Such a call can be understood as "applying the following assignment operation to an object".
val adam = Person("Adam").apply { age = 32 city = "London" } println(adam)
Taking the receiver as the return value makes it easy to include apply into the call chain for more complex processing.
also
The context object is accessed as a parameter (it) of the lambda expression. The return value is the context object itself.
Also is useful for performing some operations that take context objects as parameters. Use also for operations that need to reference objects instead of their properties and functions, or do not want to block this reference from external scopes.
When you see also in your code, you can understand it as "and do the following with that object".
val numbers = mutableListOf("one", "two", "three") numbers .also { println("The list elements before adding new one: $it") } .add("four")
Summarize
The following table summarizes the main differences and usage scenarios of Kotlin scope functions:
function | Object reference | Return value | Is it an extension function? | Use scenarios |
---|---|---|---|---|
let | it | Lambda expression results | yes | 1. Execute a lambda expression on a non-empty object 2. Introduce expressions as variables into local scope |
run | this | Lambda expression results | yes | Object configuration and calculation results |
run | - | Lambda expression results | No: no context object is required for calls | Run statements where expressions are required |
with | this | Lambda expression results | No: treat context objects as parameters | A set of function calls for an object |
apply | this | Context object | yes | Object configuration |
also | it | Context object | yes | Additional effects |
References
Kotlin Language Chinese website
Okay, this article about the differences and usage scenarios between Kotlin scope functions are introduced here. For more related contents of Kotlin scope functions and usage scenarios, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!