Kotlin 的 Any

今天,我们来看看 Kotlin 里的 Any,它相当于 Java 里的 Object,所有其他的类都默认继承了它。Kotlin 比 Java 的面向对象更彻底, Java 还有基础类型,它们没有继承 Object, Java 的方法也不是对象,而 Kotlin 的方法是对象。

Any 的构造函数就不多说,就是能构造出它的对象,

Any 的函数

  1. equals
  2. hashCode, 唯一标示这个对象
  3. toString

这三个函数跟 Object 里的相同,这里不再详述

Any 的扩展属性

javaClass 代表一个对象的运行时 java class(当然只在 JVM 上有这个属性)

val <T : Any> T.javaClass: Class<T>

这个写法还是需要解释一下的,首先,扩展函数的定义形式是这样的,例如:

val String.wordsCount: Int
  get() {
    ...
  }

而上面 javaClass 的定义,用到了类型参数,我想说,这真的是太牛逼了,类型参数,指定每个所有继承 Any 的 T 都有 javaClass 这个属性,并且 javaClass 得到的是 T 这个类的 java 运行时 class

扩展函数

also

fun <T> T.also(block: (T) -> Unit): T

also 会接受一个代码块,这个代码块以 this 为参数,并且返回 this。

扩展函数跟扩展属性同样使用了类型参数,不过,这样算是 Any 的扩展函数吗? 当然,一切解释权归官方所有,官方认为是就是了。

我们知道了扩展函数的好处,扩展函数可以使用类型参数作为 Receiver,从而在子类上调用该扩展函数时,我们拿到的 Receiver 是子类。

also 和 apply

fun <T> T.apply(block: T.() -> Unit): T

also 和 apply 的作用可以说是完全一样,它们都可以执行一个代码块,并且返回 Receiver。但是它们有区别的,传入 also 的代码块,将 Receiver 作为参数,而 apply 将 apply 的 Receiver 作为代码块的 Receiver。文字还是太苍白,让我们举个栗子:

val john = Person().apply {
    wearHat()
    wearCoat()
}

val lili = Person().also {
    it.wearHat()
    it.wearCoat()
}

这样就清晰明了了,also 将自己的 Receiver 作为代码块的参数,而当代码块但参数时,可以不写出来,默认这个参数的名字叫 it

对于我来说,它们在语意上,apply 的代码块内更像过程编程,描述一些有顺序的行为,而 also 的代码块内更像面向对象,给这个 it 发信息,设置一些状态。

let 和 run

fun <T, R> T.let(block: (T) -> R): R

fun <T, R> T.run(block: T.() -> R): R

let/run 的差别 跟 also/apply 的差别相同,还是再说一遍,let 将自己的 Receiver 作为 代码块的参数 而 run 将自己的 Receiver 作为代码块的 Receiver。let/run 与 also/apply 的区别在于返回值, 之前提到,also/apply 的返回值是它们的 Receiver,而 let/run 的返回值是代码块表达式的最后返回值。

val result1 = Calculator().run {
   addOne(23)
   addAnother(43)
   compute()
}

val result2 = Calculator().let {
   it.addOne(23)
   it.addAnother(53)
   it.compute()
}

我发现在语言表达上,它们两个也还是有区别的,run 同 apply 运行一些指令,有顺序的运行。而 let 让它做一些动作,最后,获取一个结果

takeIf 和 takeUnless

fun <T> T.takeIf(predicate: (T) -> Boolean): T?

fun <T> T.takeUnless(predicate: (T) -> Boolean): T?

这两个扩展函数都是 kotlin 1.1 版本添。它们都会接受一个断言,在断言返回 true 时 takeIf 返回自己,否则 返回 null;takeUnless 与之相反,在断言返回 false 时返回自己,在断言返回 true 时返回 null。上代码:

val index = "Kotlin".indexOf('K').takeIf { it > 0 } ?: 0

val personName = person?.Name ?: "no name"

这个例子里将 takeIf 与 ?. 做了对比,它们都提供了一个默认值的形式。只不过 takeIf/taskUnless 更普遍,可以自己定义条件来决定是否使用默认值。

to

infix fun <A, B> A.to(that: B): Pair<A, B>

to 比较有趣,它是一个中缀运算符,也就是类似这个样子:

val person1 = Person().run {
    wearHat()
    wearCoat()
    name to this
}

val person2 = Person().let {
    it.wearHat()
    it.wearCoat()
    it.name to it
}

val person = mapOf(person1, person2)

我们可以看到,我们在 run/let 分别使用 to 构造了两个 Pair,而 Pair 正好可以作为 map 的键值对,于是我们构造了一个包含两个人的 map。

toString

fun Any?.toString(): String

这个 toString 是一个扩展函数,它并没有比之前那个 toString() 函数高级多少,只是 Receiver 是 Any? 也就是在 Receiver 为 null 的时候也会调用 toString()。

val name = person.toString()

我们不管 person 到底是不是 null 了。还是为了解决 Java 中的 NullPointerException 的问题。


还有几个 JavaScript 相关的扩展函数,我们这里没有列出来,如果需要请自行查看。类似了。