“This is the sixth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”
This article introduces the scope functions provided in the Kotlin standard library. Their sole purpose is to execute a block of code in the context of an object. When such a function is called on an object and a Lambda expression is provided, a temporary scope is formed. In this scope, you can access an object without using its name. There are five types:
- let
- with
- apply
- run
- also
Here’s an example:
const val MALE = 0
const val FEMALE = 1
class Person {
var name: String? = null
var age: Int = 0
var sex: Int = MALE
override fun toString(a): String {
return "[name=$name, age=$age, sex=${if (sex == MALE) "MALE" else "FEMALE"}]. ""}}fun main(a) {
val p = Person()
p.name = "owen"
p.age = 18
p.sex = MALE
println(p)
val p2 = Person().apply { // Call apply
name = "Tom"
age = 23
sex = FEMALE
}
println(p2)
}
// Run the result
[name=owen, age=18, sex=MALE]
[name=Tom, age=23, sex=FEMALE]
Copy the code
This simple example shows the advantages of using scoped functions:
- omitted
The variable name.
You can save some code when variable names are long and many properties need to be set. - The code is cleaner, more semantic, and easier to see
{}
The code in the.
The official document also states:
Scoped functions don’t introduce any new techniques, but they can make your code cleaner and more readable.
These five scoped functions are very similar in nature, so it is important to understand the differences between them. There are two main differences between each scoped function:
- How to refer to context objects (it or this)
- Return value (context object or Lambda expression result)
Let’s make a list to get a better idea:
Scope function | Reference context object mode | The return value | Extension function |
---|---|---|---|
Run (extended function) | this | Lambda expression result | is |
Run (non-extended function) | – | Lambda expression result | No, the call does not require a context object |
with | this | Lambda expression result | No, take the context object as an argument |
apply | this | Context object | is |
let | it | Lambda expression result | is |
also | it | Context object | is |
Reference context object mode
There are two ways to refer to context objects, it and this. This represents the recipient of the Lambda expression, and it represents the argument to the Lambda expression. The main difference between them is that “this” can be omitted without ambiguity and “it” cannot.
this
Run, with, and apply refer to context objects through the keyword this. Context objects can therefore be accessed in Lambda expressions just as they would in normal class functions. In most scenarios, you can omit this to simplify the code. Therefore, for operations that are primarily performed on context object members (calling functions of the object or assigning values to attributes of the object), it is recommended that the context object be the receiver, that is, run, with, or apply.
fun main(a) {
val p = Person().apply {
name = "owen"
age = 18
city = "Shanghai"
}
println(p)
val nextYearAge = Person().run {
name = "tom"
age = 22
city = "Beijing"
println(this)
age + 1
}
println("nextYearAge = $nextYearAge")
with(Person()) {
name = "sam"
age = 30
city = "Shenzhen"
println(this)}}// Run the result
Person(name=owen, age=18Person(name= Tom, age=22, city= Beijing) nextYearAge =23
Person(name=sam, age=30, city= Shenzhen)Copy the code
it
Let and also take context objects as arguments to Lambda expressions. 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 calling object functions or properties, you cannot omit them as this does. Therefore, it is better to use it as the context object when the context object is primarily used as an argument to a function in a Lambda. It is also better if you use multiple variables in a code block.
const val MALE = 0
const val FEMALE = 1
class Person {
var name: String? = null
var age: Int = 0
var sex: Int = MALE
override fun toString(a): String {
return "[name=$name, age=$age, sex=${if (sex == MALE) "MALE" else "FEMALE"}]. ""
}
fun sayHi(a){
println("$name says Hi")}}fun main(a) {
valp:Person? = Person() p? .let { it.name ="Owen"
it.age = 15
it.sex = MALE
println(it)
}
Person().also {
it.name = "Sam"
println(it)
}.sayHi()
}
// Run the result
[name=Owen, age=15, sex=MALE]
[name=Sam, age=0, sex=MALE]
Sam says Hi
Copy the code
The return value
There are also two return values, either the context object itself, or the result of the Lambda expression (the value of the last line of the Lambda expression).
Context object
Apply and Also return the context object itself, so they can be included in the call chain as auxiliary steps: you can continue to make chained function calls on the same object.
Such as:
fun main(a) {
val list = mutableListOf<Int>()
list.also {
println("Fill in data")
}.apply {
add(4)
add(3)
add(6)
}.also {
println("Order")
}.sort()
println(list)
}
// Run the resultFill data sort [3.4.6]
Copy the code
Lambda expression result
Let, run, and with return the result of a lambda expression. So you can use them when you need to assign a value to a variable using their results, or when you need to chain the results, etc.
fun main(a) {
val nums = mutableListOf("One"."Two"."Three")
val count = nums.run {
add("Four")
add("Five")
count {
it.endsWith("e")
}
}
println("The number of words ending in e is:$count")}Copy the code
Here are some typical uses of these functions:
Typical Usage Scenarios
let
Let is often used instead of repeated? For a nullable variable, we need to use? To call the corresponding function, but each safe call is redundant, also increases the unnecessary non-null judgment, this time can use? .let performs operations in Lambda expressions, in which the variable is definitely not empty.
As in this example, when p2 is null, the code in the Lambda expression will not execute:
fun main(a) {
valp:Person? = Person() p? .let { it.name ="Owen"
it.age = 15
it.sex = MALE
println(it)
}
val p2: Person? = nullp2? .let { it.name ="Tom"
it.age = 22
it.sex = FEMALE
println(it)
}
}
// Run the result
[name=Owen, age=15, sex=MALE]
Copy the code
Let function source:
public inline fun <T, R> T.let(block: (T) - >R): R {
return block(this)}Copy the code
with
With is not an extension function; it takes two arguments, the first a context object and the second a Lambda expression. For this object, do the following.
With function source:
public inline fun <T, R> with(receiver: T, block: T. () - >R): R {
return receiver.block()
}
Copy the code
run
Run can be used in two ways. One is as an extension function, and is commonly used when a Lambda expression contains both object initialization and return value evaluation.
Extended function run function source:
public inline fun <T, R> T.run(block: T. () - >R): R {
return block()
}
Copy the code
The other is similar to with, which is used as a non-extending function to execute a block of code consisting of multiple statements where an expression is needed.
Non-extended function run function source:
public inline fun <R> run(block: () -> R): R {
return block()
}
Copy the code
apply
Use Apply for blocks of code that return no value and run primarily on members of the receiver (this) object. A common case for Apply is object configuration. Such a call can be interpreted as applying the following assignment operations to the object.
Apply function source:
public inline fun <T> T.apply(block: T. () - >Unit): T {
block()
return this
}
Copy the code
also
Also is useful for performing operations that take context objects as arguments. Use also for operations that refer to an object rather than its properties and functions. And use this object to perform the following operations (additional effects).
Also function source:
public inline fun <T> T.also(block: (T) - >Unit): T {
block(this)
return this
}
Copy the code
Other standard functions
The library also provides two functions: takeIf and takeUnless, which embed an object’s state check into the call chain.
takeIf
TakeIf returns an object that satisfies the result of a Lambda expression, otherwise null.
TakeIf function
public inline fun <T> T.takeIf(predicate: (T) - >Boolean): T? {
return if (predicate(this)) this else null
}
Copy the code
takeUnless
TakeUnless is the opposite of takeIf in that it returns an object if the condition is not met and null if the condition is met.
TakeUnless source code:
public inline fun <T> T.takeUnless(predicate: (T) - >Boolean): T? {
return if(! predicate(this)) this else null
}
Copy the code
When calling other functions after takeIf and takeUnless, you need to do a null check or use? Because their return value is nullable.
TakeIf and takeUnless are particularly useful with scoped functions, such as let after takeIf or takeUnless.
Here’s a comparison:
// Do not use takeIf and let
fun displaySubstringPosition(input: String, sub: String) {
val index = input.indexOf(sub)
if (index >= 0) {
println("In"$input【 find 】$sub】")
println("The substring starts at:$index")}}fun main(a) {
displaySubstringPosition("HelloWorld"."ll")
displaySubstringPosition("HelloWorld"."lf")}// Run the resultSelect * from [HelloWorld]; select * from [HelloWorld];2
Copy the code
// Use takeIf and let
fun displaySubstringPosition2(input: String, sub: String) {
input.indexOf(sub).takeIf { it >= 0}? .let { println("In"$input【 find 】$sub】")
println("The substring starts at:$it")}}fun main(a) {
displaySubstringPosition2("HelloWorld"."ld")
displaySubstringPosition2("HelloWorld"."lf")}// Run the resultFind [ld] substring in [HelloWorld] :8
Copy the code
The resources
Scope function
Play with Kotlin’s scope function
The first Line of Android Code (Version 3)
Crazy Kotlin handout