kotlin-let,run,with,apply,also作用域函数详解

kotlin-let,run,with,apply,also作用域函数详解_第1张图片

前言

五个作用域,会比较长。
这篇文章,类似于字典。查查。用用。

主要需要了解的点
① 有什么区别?
② 如何选择?


作用域函数是什么意思呢

通过编译器的手段增加一些操作符,使代码变得更简洁
所以,你不用它也完全可以实现相同的功能

它提供了一个临时作用域,让对象执行代码块的 代码看起来更简洁

感受一下 作用域函数 带来的代码整洁
data class Person(var name: String, var age: Int, var city: String) {
    fun moveTo(newCity: String) { city = newCity }
    fun incrementAge() { age++ }
}

fun main() {
    Person("Alice", 20, "Amsterdam").let {
        println(it)
        it.moveTo("London")
        it.incrementAge()
        println(it)
    }
}

我们将main()函数的代码改成普通的方式

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

使用 let 之后,代码更显层次,更方便的寻找 Person alice的作用域(有效范围)


作用域函数的弊端是什么

显然代码整洁了
但是可读性却下降了。lambda过多就会导致修改起来很累。
要考虑到交接成本。后来者可能改不动你的代码


Kotlin有五个范围函数

  • let
  • run
  • with
  • apply
  • also

5个范围函数的区别

《kotlin - let,run,with,apply,also作用域函数的区别》


具体讲讲这5个范围函数的用法

很多时候这5个范围函数是可以互换的

同一个逻辑可以用不同的范围函数来实现

接下来讲讲 常见的使用方法


let

上下文对象: it
返回结果: lambda结果

普通写法
fun main() {
    val numbers = mutableListOf("one", "two", "three", "four", "five")
    val resultList = numbers.map { it.length }.filter { it > 3 }
    println(resultList)    
}

用法1

重写部分使用了某变量的逻辑,显得更有代码结构层次感

改写普通写法
fun main() {
    val numbers = mutableListOf("one", "two", "three", "four", "five")
    numbers.map { it.length }.filter { it > 3 }.let { 
        println(it)
    } 
}

若代码块 仅包含一个it参数的单一函数

还可以用反射进行改写
fun main() {
    val numbers = mutableListOf("one", "two", "three", "four", "five")
    numbers.map { it.length }.filter { it > 3 }.let(::println)
}

用法2

搭配 ?. 进行非空判断

fun processNonNullString(str: String) {}

fun main() {
    val str: String? = null
    //processNonNullString(str)       // compilation error: str can be null
    val length = str?.let { 
        println("let() called on $it")        
        processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
        it.length
    }
}

这里str为null,则let不会被执行。若str有值,则let代码块可以被执行

用法3

使用 it的名别 来提高代码可阅读性

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    val modifiedFirstItem = numbers.first().let { firstItem ->
            println("The first item of the list is '$firstItem'")
        if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
    }.toUpperCase()
    println("First item after modifications: '$modifiedFirstItem'")
}

这里的firstItem即it的别名。从字面上可以更清楚的制度这里的it是什么


with

上下文对象: this
返回结果: lambda结果
with可以被解读为: 请用这个object做以下的事情

用法1

用numbers去打印log

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

用法2

调用其functions

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    val firstAndLast = with(numbers) {
        "The first element is ${this.first()}," +
            " the last element is ${last()}"
    }
    println(firstAndLast)
}

这里会输出

The first element is one, the last element is three

run

上下文对象: this
返回结果: lambda结果

它可以独立运行

val hexNumberRegex = run {
        val digits = "0-9"
        val hexDigits = "A-Fa-f"
        val sign = "+-"

        Regex("[$sign]?[$digits$hexDigits]+")
}

可以看到没有 context对象
这里范例返回的就是Regex

像let一样用

let用的是it,run用的是this

范例
class Person(var name: String, var height: Int) {
    fun signIn(): String = "$name has sign in"
    fun getHeight(height: String): String = "Height = '$height'"
}

fun main() {
    val alice = Person("Alice", 188)

    val result = alice.run {
        this.name = "$name the Student"
        println(signIn() + " to port " + getHeight("$height"))
        "alice clone1 success"  //输出最后一行的结果
    }

    // the same code written with let() function:
    val result2 = alice.let {
        it.name = "${it.name} the Student"
        println(it.signIn() + " to port " + it.getHeight("${it.height}"))
        "alice clone2 success"  //输出最后一行的结果
    }
    println(result)
    println(result2)
}

输出结果呢

Alice the Student has sign in to port Height = '188'
Alice the Student the Student has sign in to port Height = '188'
alice clone1 success
alice clone2 success

apply

上下文对象: this
返回结果: 上下文对象
使用apply的代码块没有返回值,主要是操作上的接收器对象的成员
主要用来进行对象配置

范例
data class Person(var name: String, var age: Int = 0, var city: String = "")

fun main() {
    val adam = Person("Adam").apply {
        age = 32
        city = "London"        
    }
}

also

上下文对象: it
返回结果: 上下文对象
这可以理解为 我也要做什么什么
定义为附加功能最合适

顺便打个log吧
fun main() {
    val numbers = mutableListOf("one", "two", "three")
    numbers
        .also { println("The list elements before adding new one: $it") }
        .add("four")
}

takeIf与takeUnless

takeIf

针对单个对象的过滤功能

匹配则返回该对象,不匹配返回null

takeUnless

与takeIf相反

注意

takeIf和takeUnless是过滤
返回的结果 是 原先的对象

这可以达到链式调用的效果!!!

takeIf和takeUnless可以和五个作用域函数 搭配使用

范例
fun main() {
    val number = 6

    val evenOrNull = number.takeIf { it % 2 == 0 }
    val oddOrNull = number.takeUnless { it % 2 == 0 }
    println("even: $evenOrNull, odd: $oddOrNull")
    
    
    val number2 = 7

    val evenOrNull2 = number2.takeIf { it % 2 == 0 }
    val oddOrNull2 = number2.takeUnless { it % 2 == 0 }
    println("even: $evenOrNull2, odd: $oddOrNull2")
}

可以 看出来takeIf与takeUnless的结果可能是null
所以这里,你会用到 ?.操作符 进行判空

你可能感兴趣的:([kotlin])