Kotlin Standard.kt 内置函数使用

在 Kotlin 源码的 Standard.kt 文件中提供了一些很好用的内置高阶函数,可以帮助我们写出更优雅的 Kotlin 代码,提高生产力。为了能学习这些高阶函数,有必要先对高阶函数、Lambda表达式有所了解。

接下来我们逐个学习,其中 let、also、with、run、apply 这几个函数的功能很相似,需要我们重点注意,按需使用。

一、 let

let 函数的声明如下:

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

可以看出 let 是一个作用域函数,需要通过一个对象来调用,参数是函数类型,同时 let 函数的返回值类型也是该函数的返回值类型。由于我们一般会用 Lambda 表达式作为函数类型参数的值,那么 let 函数的返回值就是 Lambda 表达式的返回值,以下内容都会采用类似的说法,这一点需要注意。

一个典型的使用场景就是创建一个目标 Activity、Fragment 并接收参数时,可以考虑使用 let 函数,例如在 Fragment 中接收参数时:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

arguments不为空时,Lambda 表达式内it就代替arguments对象来访问其方法。

二、also

also 函数的声明如下:

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

also 函数从 Kotlin1.1开始支持,和 let 函数的声明比较一下,其实是很类似的,唯一的区别就是返回值不同,前边我已经知道 let 函数的返回值可以是 Lambda 表达式 的返回值,而 also 函数的返回值是调用 also 函数的对象:

fun main(args: Array) {
    val let = "kotlin".let {
        it.toUpperCase()
    }

    val also = "kotlin".also {
        it.toUpperCase()
    }

    println("let的返回值:$let")
    println("also的返回值:$also")
}
// 输出
let的返回值:KOTLIN
also的返回值:kotlin

所以除了返回值的差别外,also 函数适合 let 函数的任何使用场景,另外 also 函数更适合链式操作一个对象的属性、方法,并返回该对象的场景:

data class User(var name: String = "", var age: Int = 0, var sex: String = "") {
    override fun toString(): String {
        return "name:$name,age:$age,sex:$sex"
    }
}

fun main(args: Array) {
    val user2 = User().also {
        it.name = "Tom"
        it.age = 18
        it.sex = "male"
    }
    println(user2.toString())
}
// 输出
name:Tom,age:18,sex:male

三、with

with 函数的声明如下:

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

with 函数需要两个参数,需要操作的对象和一个函数类型的参数(一般是 Lambda 表达式),在 Lambda 表达式中可以用this指代要操作的对象或者省略 this 也行,返回值就是 Lambda 表达式的返回值,虽然这个返回值一般没啥用。

当我们需要调用一个对象的多个方法时,为了简化写法可以省略掉多个对象名称,这是可以考虑使用 with 函数,例如 Recycleriew 中绑定 ViewHolder 的操作,一般情况是这样的:

override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) {
        ImageLoader.load(mContext, data.envelopePic, viewHolder.getView(R.id.projectIv))
        viewHolder.setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString())
        viewHolder.setText(R.id.projectDescTv, data.desc)
        viewHolder.setText(R.id.projectAuthorTv, data.author)
        viewHolder.setText(R.id.projectTimeTv, data.niceDate)
    }

如果使用了 with 函数会是这样的:

override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) {
        with(viewHolder){
            ImageLoader.load(mContext, data.envelopePic, getView(R.id.projectIv))
            setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString())
            setText(R.id.projectDescTv, data.desc)
            setText(R.id.projectAuthorTv, data.author)
            setText(R.id.projectTimeTv, data.niceDate)
        }
    }

四、run

run 函数的声明如下:

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

两种声明,第一个没啥大用,直接看第二个,是不是有点像 let、 with 函数的结合体呢!需要通过一个对象调用,可已接收一个 Lambda 表达式作为参数,那么返回值自然是 Lambda 表达式的返回值。

和 with 函数类似在 Lambda 表达式中可以用this指代要操作的对象或者省略 this 也行,而无需使用it,同时也具备了 let 函数可以进行对象判空的优点,例如 Android 中 Toolbar 的初始化操作:

toolbar.run {
            title = "设置"
            setSupportActionBar(this)
            setNavigationOnClickListener {
                finish()
            }
            supportActionBar?.setDisplayHomeAsUpEnabled(true)
        }

将相关的操作集中在一个代码块里,代码逻辑会更加的清晰。

五、apply

apply 函数的声明如下:

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

apply 函数和 run 函数很像,唯一的区别就是 apply 函数返回调用它的对象本身。一般情况下,如果需要创建一个对象,并在相关初始化操作后赋值给一个变量可以考虑使用 apply 函数。例如 Fragment 的 newInstance()方法传递参数时:

companion object {
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
                TestFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_PARAM1, param1)
                        putString(ARG_PARAM2, param2)
                    }
                }
    }

其实,这五个函数中,根据是否需要对象的返回值来划分需求,只使用 run、apply 函数就可以替代其它函数的使用场景。当然合适的才是最好的,按需选择即可!它们的主要语法差别的如下:

函数 Lambda 表达式中如何指代当前对象 返回值
let it Lambda 表达式的值
also it 当前对象
with this(可省略) Lambda 表达式的值
run this(可省略) Lambda 表达式的值
aplly this(可省略) 当前对象

六、takeIf

takeIf 函数的声明如下:

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

我们可以通过一个对象来调用它,如果predicate函数返回值为true,则返回调用对象,否则返回null,注意predicate函数的参数就是当前调用对象。

这其实就是一个加强版的if表达式,更加灵活,我们可以让对象使用安全调用操作符?.,由于 takeIf 函数可以返回对象本身,那么自然可以进行链式调用。写个例子简单比较下:

fun filterUser1(user: User?) {
    if (user != null && user.age > 18 && user.sex == "male") {
        println(user.toString())
    }
}

fun filterUser2(user: User?) {
    user?.takeIf {
        it.age > 18 && it.sex == "male"
    }.apply {
        println(toString())
    }
}

七、takeUnless

takeUnless 函数的声明如下:

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

嗯?如果predicate函数返回值为false,则返回调用对象,否则返回null,功能和 takeIf 函数相反!

八、repeat

repeat 函数声明如下:

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

    for (index in 0 until times) {
        action(index)
    }
}

就是将action函数执行times次,函数的参数就是当前的次数:

fun main(args: Array) {
    repeat(6) {
        println("Kotlin$it")
    }
}
// 输出
Kotlin0
Kotlin1
Kotlin2
Kotlin3
Kotlin4
Kotlin5

九、TODO

TODO 函数的声明如下:

@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()

@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")

和 Java 中的TODO类似,可以用来标注某个方法需要重写,或者没有完成的事项等等,但是 Kotlin 的 TODO 会抛出异常,并可以指定异常原因!

你可能感兴趣的:(Kotlin Standard.kt 内置函数使用)