“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:

  1. omittedThe variable name.You can save some code when variable names are long and many properties need to be set.
  2. 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