Kotlin 基础05 - 标准函数、静态方法

标准函数

Kotlin 标准库里有一些支持 lambda 的通用工具类标准函数。标准函数本质上都是扩展函数。

函数 是否传 receiver 值参给 lambda 是否有相关作用域 返回
T.let lambda 结果
T.apply 接收者对象
T.run lambda 结果
run lambda 结果
with(T) lambda 结果
T.also 接收者对象
T.takeIf 可空类型接收者对象
T.takeUnless 可空类型接收者对象

let

let 函数能使某个变量作用于其 lambda 表达式里,让 it 关键字能引用它。返回 lambda 的最后一行(lambda 结果值)。

1
2
3
val num = listOf(1, 2, 3).first().let {
it * it
}

apply

apply 函数可看作一个配置函数,接收一个 Lambda 参数,并且会在 Lambda 表达式中提供调用对象的上下文。区别在于 apply 函数无法指定返回值,而是会自动返回调用对象本身。

1
2
3
4
5
6
7
8
9
10
val result = obj.apply {
// 这里是 obj 的上下文
}
// result == obj

val file = File("file.txt").apply {
setWritable(true)
setReadable(true)
setExecutable(false)
}

run

T.run 函数不能直接调用,一定要调用某个对象的 run 函数才可以。只接收一个 Lambda 参数, 并且会在 Lambda 表达式中提供调用对象的上下文,返回的是 lambda 结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val result = obj.run {
// 这里是 obj 的上下文
"value" // run 函数的返回值
}

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().run {
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.\n")
toString()
}
println(result)

run 的另一版本无需接收者,不传递接收者值参,没有作用域限制,返回 lambda 结果值。

1
2
3
val rst = run {
2 * 3
}

with

with 函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个 Lambda 表达式。

with 函数会在 Lambda 表达式中提供第一个参数对象的上下文,并使用 Lambda 表达式中的最后一行代码最为返回值返回。

1
2
3
4
5
6
7
8
val result = with(obj) {
// 这里是 obj 的上下文
"value" // with 函数的返回值
}

val nameTooLong = with("Polarcubis, Supreme Master of NyetHack") {
length >= 20
}

also

also 函数和 let 函数功能相似。also 也是把接收者作为值参传给 lambda,但是返回的是接收者对象。因此可以基于原始接收者对象执行额外的链式调用。

1
2
3
4
5
6
File("file.txt")
.also {
print(it.name)
}.also {
println(it.path)
}

takeIf

takeIf 函数判断 predicate 给出 true 或 false 结果。如果判断结果是 true,从 takeIf 函数返回接收者对象;如果是 false,则返回 null。

如果需要判断某个条件是否满足,再决定是否可以赋值变量或执行某项任务,takeIf 就非常有用。

概念上讲,takeIf 函数类似于 if 语句,但它的优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦。

1
2
// 当且仅当文件可读且可写时,才读取文件内容
val fileContents = File("file.txt").takeIf { it.canRead() && it.canWrite() }?.readText()

takeUnless

takeUnless 和 takeIf 唯一的区别是:只有判断给定的条件结果是 false 时, takeUnless 才会返回原始接收者对象。

1
2
// 文件不是隐藏文件才读取文件
val fileContents = File("file.txt").takeUnless { it.isHidden }?.readText()

takeUnless 描述的逻辑不太自然,最好多使用 takeIf。

先决条件函数

Kotlin 标准库提供了一些先决条件函数。使用这些内置函数,可以抛出带自定义信息的异常。

函数 描述
checkNotNull 如果参数值为 null,则抛出 IllegalStateException 异常,否则返回非 null 值
require 如果参数值为 false,则抛出 IllegalArgumentException 异常
requireNotNull 如果参数值为 null,则抛出 IllegalStateException 异常,否则返回非 null 值
error 如果参数值为 null,则抛出 IllegalStateException 异常并输出错误信息,否则返回非 null 值
assert 如果参数值为 false,则抛出 AssertionError 异常,并打上断言编译器标记
1
2
3
4
5
fun test(input: Int?) {
val content = checkNotNull(input) { "input is null" }
require(input > 5) { "too small" }
println(content)
}

定义静态代码

Kotlin 极度弱化了静态方法这个概念,因为提供了更好的语法特性,就是单例类。

使用单例类 object 的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,如果只希望类中某一个方法变成静态方法的调用方式,就可以使用 companion object 了。

1
2
3
4
5
6
7
8
9
10
11
class Util {
fun doAction1(){
println("doAction1()")
}

companion object {
fun doAction2(){
println("doAction2()")
}
}
}

doAction2() 其实也不是静态方法,companion object 关键字会在 Util 类的内部创建一个伴生类,doAction2() 就是定义在伴生类里面的实例方法。Kotlin 会保证 Util 类内部始终是有一个伴生类对象。

Kotlin 确实没有直接定义静态方法的关键字,如果确实需要定义真正的静态方法,Kotlin 仍让提供了两种实现方式:注解和顶层方法。

使用 const 定义常量,只有在单例类、companion object 或顶层方法中才可以使用 const 关键字。

注解

companion object 只是在语法的形式上模仿了静态方法的调用方式,并不是真正的静态方法。如果在 Java 代码中以静态方法的形式去调用的话,会发现方法并不存在。但是如果为companion object 中的方法加上 @JvmStatic 注解,那么 Kotlin 编译器就会将这些方法编译成真正的静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
class Util {
fun doAction1(){
println("doAction1()")
}

companion object {
@JvmStatic
fun doAction2(){
println("doAction2()")
}
}
}

@JvmStatic 注解只能加载单例类或 companion object 中的方法上,如果加载普通方法上,会提示语法错误。

加上注解后,在 Java 中也可以使用 Util.doAction2() 的方式来调用了。

顶层方法

顶层方法指的是那些没有定义在任何类中的方法。Kotlin 编译器会将所有的顶层方法全部编译成静态方法,因此只要定义了一个顶层方法,那么它就一定是静态方法。

在 Kotlin 代码中,所有的顶层方法都可以在任何位置被直接调用,不用管包名路径,也不用创建实例。

Helper.kt

1
2
3
fun doSomething() {
println("doSomething()...")
}

假设创建的 Kotlin 文件名叫做 Helper.kt,Kotlin 编译器会自动创建一个叫做 HelperKt 的 Java 类,在 Java 中使用 HelperKt.doSomething() 的写法来调用即可。

1
2
3
4
5
public class StaticTest {
public static void main(String[] args) {
HelperKt.doSomething();
}
}