那些年,我们看不懂的那些Kotlin标准函数

Kotlin标准库中提供了一套用于常用操作的函数。最近,在我的Kotlin交流群中有人再次问到了关于这些函数的用法。今天,让我们花一点时间,一起看一下这些函数的用法。

Ready go >>>

注:这里所说的标准函数主要来自于标准库中在文件Standard.kt中的所有函数。

run#1

@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
复制代码

contract部分主要用于编译器上下文推断,这里我们忽略掉这部分代码。

观察源码发现,run方法仅仅是执行传入的block表达式并返回执行结果而已(block是一个lambda表达式)。

因此,如果你仅仅需要执行一个代码块,可以使用该函数

看一个例子:

val x = run {
           println("Hello, world")
           return@run 1
        }
println(x)

// 执行结果
Hello,world
1
复制代码

run#2

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
复制代码

这个函数跟上面的函数功能是完全一样的。不同的是,block的receiver是当前调用对象,即在block中可以使用当前对象的上下文。

因此,如果你需要在执行的lambda表达式中使用当前对象的上下文的话,可以使用该函数。除此之外,两者没有任何差别

看一个例子:

class A {
    fun sayHi(name: String) {
        println("Hello, $name")
    }
}

class B {

}

fun main(args: Array<String>) {
    val a = A()
    val b = a.run {
        // 这里你可以使用A的上下文
        a.sayHi("Scott Smith")
        return@run B()
    }
    println(b)
}

// 执行结果
Hello,Scott Smith
b@2314
复制代码

从例子中,我们可以看到,这个函数还可以用于对数据类型进行转换。

with

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
复制代码

这个函数其实和run函数也是做了一样的事情。不同的是,这里可以指定block的接收者。

因此,如果你在执行lambda表达式的时候,希望指定不同的接收者的话,可以使用该方法

class A {
    fun sayHi(name: String) {
        println("Hello, $name")
    }
}


fun main(args: Array<String>) {
    val a = A()
    with(a) {
        // 这里的接收者是对象a,因此可以调用a实例的所有方法
        sayHi("Scott Smith")
    }
}

复制代码

apply

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
复制代码

可以看到,这个方法是针对泛型参数的扩展方法,即所有对象都将拥有该扩展方法。相对于run#2方法,apply不仅执行了block,同时还返回了receiver本身。

这在链式编程中很常用,如果你希望执行lambda表达式的同时而不破坏链式编程,可以使用该方法

看一个例子:

class A {
    fun sayHi(name: String) {
        println("Hello, $name")
    }
    
    fun other() {
        println("Other function...")
    }
}


fun main(args: Array<String>) {
    val a = A()
    a.apply { 
        println("This is a block")
        sayHi("Scott Smith")
    }.other()
}

// 执行结果
This is a block
Hello, Scott Smith
Other function...
复制代码

also

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
复制代码

这个函数跟with又很像,不同的是,block带有一个当前receiver类型的参数。在block中,你可以使用该参数对当前实例进行操作。

这个函数和with完全可以互相通用,with函数可以直接在当前实例上下文中对其进行操作,而also函数要通过block参数获取当前类实例。因为用法完全一致,这里就不举例了

let

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
复制代码

如果你使用过RxJava,可能会感到似曾相识,这其实就是RxJava的map函数。这个函数也是针对泛型参数的扩展函数,所有类都将拥有这个扩展函数。

如果你希望对当前数据类型进行一定的转换,可以使用该方法。该方法的block中同样可以使用当前receiver的上下文

看一个例子:

class Triangle {}

class Rectangle {}

fun main(args: Array<String>) {
    val tr = Triangle()
    val rect = tr.let { it ->
        println("It is $it")
        return@let Rectangle()
    }
    println(rect)
}

// 执行结果
It is Triangle@78308db1
Rectangle@27c170f0
复制代码

从例子中可以看到,我们成功地将三角形转换成了矩形,这就是let函数的作用。

takeIf

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}
复制代码

这个函数也是针对泛型参数的扩展函数,所有类都将拥有这个扩展。这个函数使用了一个预言函数作为参数,主要用于判断当前对象是否符合条件。 这个条件函数由你指定。如果条件符合,将返回当前对象。否则返回空值。

因此,如果你希望筛选集合中某个数据是否符合要求,可以使用这个函数

看一个例子:

fun main(args: Array<String>) {
    val arr = listOf(1, 2, 3)
    arr.forEach {
        println("$it % 2 == 0 => ${it.takeIf { it % 2 == 0 }}")
    }
}

// 执行结果
1 % 2 == 0 => null
2 % 2 == 0 => 2
3 % 2 == 0 => null
复制代码

takeUnless

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}
复制代码

这个函数刚好与takeIf筛选逻辑恰好相反。即:如果符合条件返回null,不符合条件返回对象本身。

看一个例子:

fun main(args: Array<String>) {
    val arr = listOf(1, 2, 3)
    arr.forEach {
        println("$it % 2 == 0 => ${it.takeUnless { it % 2 == 0 }}")
    }
}

// 执行结果
1 % 2 == 0 => 1
2 % 2 == 0 => null
3 % 2 == 0 => 3
复制代码

看到了吗?这里的执行结果和takeIf恰好相反。

repeat

@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }

    for (index in 0 until times) {
        action(index)
    }
}
复制代码

这个函数意思很明显,就是将一个动作重复指定的次数。动作对应一个lambda表达式,表达式中持有一个参数表示当前正在执行的次数索引。

看一个例子:

fun main(args: Array<String>) {
    repeat(3) {
        println("Just repeat, index: $it")
    }
}

Just repeat, index: 0
Just repeat, index: 1
Just repeat, index: 2
复制代码

简单总结

最后,我们用一个表格简单总结一下这些函数的用法:

函数用途特点形式
run#1执行block,并返回执行结果block中无法获取接收者上下文全局函数
run#2执行block,并返回执行结果block中可以获取接收者上下文扩展函数
with指定接收者,通过接收者执行blockblock中可以获取接收者的上下文,可以对接收者数据类型做一定转换全局函数
apply执行block,并返回接收者实例本身block中可以获取接收者的上下文,可用于链式编程扩展
also执行block,并返回接收者实例本身block中有一个参数代表接收者实例,可用于链式编程扩展
let执行block,并返回执行结果block中有一个参数代表接收者实例,可以对接收者数据类型做一定转换扩展
takeIf根据条件predicate判断当前实例是否符合要求如果符合要求,返回当前实例本身;否则返回null扩展函数
takeUnless根据条件predicate判断当前实例是否不符合要求如果不符合要求,返回当前实例本身;否则返回null扩展

搞定Receiver

理解上面这几个函数,最重要的一点是要理解Receiver。遗憾的是,Kotlin官方文档中并没有针对Receiver的详细讲解。关于这部分的讲解,请扫描下方二维码关注欧阳锋工作室,回复搞定Receiver查看文章。

欢迎加入Kotlin交流群

关于Kotlin,如果你有任何问题,欢迎加入我的Kotlin交流群: 329673958。当前群交流活跃,问题解答速度很快,期待你的加入。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值