思维导图|kotlin 作用域函数

大家好,我是青空。今天给大家带来的是kotlin 作用域函数。

图片

文末可领取大厂面试题

作用域函数是什么意思呢

通过编译器的手段增加一些操作符,使代码变得更简洁

所以,你不用它也完全可以实现相同的功能。

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

感受一下  作用域函数  带来的代码整洁

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个范围函数的区别

两个主要区别

  • 上下文对象用this 还是 it

  • 返回值

上下文对象用this 还是 it

  • this和it是作用域函数获取对象引用的  短名称

  • 两者都提供相同的功能

  • 使用this的情况

  • 范围函数:run,with,apply 使用this

  • 在大多数情况下,this访问接收器对象的成员时可以省略,从而使代码更短

比如这个例子就是省略了this

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

fun main() {
    val adam = Person("Adam").apply { 
        age = 20                       // same as this.age = 20 or adam.age = 20
        city = "London"
    }
}

但是不加this,就不容易区分age、city是来自内部接收器Person    还是   外部成员或功能

大家看下不容易区分的例子

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

var otherName = "wangxueming"

fun main() {
    val adam = Person("Adam").apply { 
        age = 20                       // same as this.age = 20 or adam.age = 20
        city = "London"
        otherName = "wxm"
    }
    println("age = ${adam.age}; city = ${adam.city}; otherName = ${otherName}")
}

这里的otherName你就不容易看出来到底是Person的变量还是外部变量

使用it的情况

  • 范围函数:let,also使用it

  • it相较this更为简洁

  • 但在范围域内this就不可用了

看范例

import kotlin.random.Random

fun writeToLog(message: String) {
    println("INFO: $message")
}

fun main() {
    fun getRandomInt(): Int {
        return Random.nextInt(100).also {
            writeToLog("getRandomInt() generated value $it")
            //下面这行的this会报编译错误:'this' is not defined in this context
            //writeToLog("getRandomInt() generated value $this")
        }
    }

    val i = getRandomInt()
}

it在lambda中是支持自定义名称的

将上个范例中的getRandomInt(),从it改成value

fun getRandomInt(): Int {
    return Random.nextInt(100).also { value -> {
        writeToLog("getRandomInt() generated value $value")
        writeToLog("getRandomInt() generated 222 value $value")
    }
    }
}

返回值
  • apply、also返回上下文对象

  • let、run、with返回lambda结果

  • 返回 上下文对象

  • 准确的是是对fun的链式调用

  • 返回又是上下文对象

  • apply、also会返回 上下文对象

  • 由于apply、also发起者是  上下文对象

  • 这就是个链式调用嘛

  • 链式调用就可以这么写了

fun main() {
    val numberList = mutableListOf<Double>()
    numberList.also { println("Populating the list") }
        .apply {
            add(2.71)
            add(3.14)
            add(1.0)
        }
        .also { println("Sorting the list") }
        .sort()
    println(numberList)
}

  • 进一步理解一下,in是上下文对象,out依旧是上下文对象

  • 这说明 also和apply不对原先的操作造成任何影响

  • 原来的是fun,添加了also或apply之后,返回的依旧是fun

  • also、apply可以理解为对原先 fun的补充逻辑处理

我们再看一遍RandomInt的例子

import kotlin.random.Random

fun writeToLog(message: String) {
    println("INFO: $message")
}

fun main() {
    fun getRandomInt(): Int {
        return Random.nextInt(100).also {
            writeToLog("getRandomInt() generated value $it")
        }
    }

    val i = getRandomInt()
}

  • 添加also之后,Random.nextInt(100)不受影响,只是额外打印了一行log

  • 这就可以动态的 给fun添加一个原先fun处理完成后的逻辑

  • 返回lambda表达式结果

  • let、run、with会返回   lambda表达式结果

  • 这就提供了 将fun计算的结果传给下一个函数继续计算

  • 这是对结果的链接调用

  • 看个统计末尾 字母为e个数的范例

fun main() {
    val numbers = mutableListOf("one", "two", "three")
    val countEndsWithE = numbers.run { 
        add("four")
        val Res1 = count { it.startsWith("f") }
        println("Res1 = ${Res1}")
        add("five")
        val Res2 = count { it.startsWith("f") }
        println("Res2 = ${Res2}")
        println("${numbers}")
        count { it.endsWith("e")&&it.startsWith("t") }
    }
    println("There are $countEndsWithE elements that end with e.")
}

输出结果为

Res1 = 1
Res2 = 2
[one, two, three, four, five]
There are 1 elements that end with e.

  • 正如文章开头说的,作用于表达式 只是书写的简化

  • 相当于是 多行 逻辑处理的整合

  • 每一行代码都能依次执行,并及时的得到相应的结果

具体讲讲这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")
}

函数选择

功能对象参考返回值是扩展功能适用情况
letitLambda结果仅支持在非null对象上使用;将 表达式 像变量一样引入到 作用域中
runthisLambda结果对象配置和计算结果
runLambda结果否:在没有上下文对象的情况下调用需要表达式的运行语句
withthisLambda结果否:将上下文对象作为参数。对对象进行分组功能调用
applythis上下文对象对象配置
alsoit上下文对象附加功能

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

最近又赶上跳槽的高峰期,好多粉丝,都问我有没有最新大厂面试题,我连日加班好多天,终于整理好了,1000+ 道,20多份大厂面试题大全,我会持续更新中!公众号回复【面试题】即可获取。

图片

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值