前言
要说Kotlin哪个特性最受欢迎,我觉得毫无疑问是“Null safety”(空安全/空类型安全),有图为证:
如果只是浅浅地看,无非就是Kotlin区分空类型和非空类型,然后辅以?.
,?:
等操作符,简单明了,没什么好说的。但是,在如此简明的使用层面之下,其实是编程语言的类型系统这个非常深刻的问题。这篇文章就从类型系统的角度重新审视Kotlin的空类型。
1. 十亿美金的错误
或许大家都听说过这种说法——null引用是一个十亿美金的错误。
“我把 Null引用称为自己的十亿美元错误。它的发明是在1965年,那时我用一个面向对象语言(ALGOL W )设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了Null引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40年中造成了十亿美元的损失。... 更新的程序设计语言比如 Spec# 已经引入了非 Null 引用的声明。这正是我在1965年拒绝的解决方案。” —— 《Null References: The Billion Dollar Mistake》托尼·霍尔(Tony Hoare),图灵奖得主
以上就是这种说法的由来。Null引用之所以如此流行,原因很简单,就是因为它实现起来非常容易。“未能抵御住诱惑的”不仅是托尼·霍尔,还有之后诸多的编程语言,当然也包括Java。
Null引用看上去是如此的理所当然,以致于我们想当然地认为这就是唯一的解决方案。难道还有别的方案?
2. null不仅是一种引用,也是一种“类型”
在Kotlin中,如果我们定义了一个类——Customer
,其实它有两种类型——Customer
和Customer?
,两者之间的关系也十分明确,在任何情况下,Customer
都是Customer?
的子类型(subtype)。
这就启示我们可以换个角度来思考null,之前我们一直认为null是一种引用,任何类型都存在着这么一种null引用;其实我们可以这样想,并不是任何类型都存在null引用,只有特定的类型才存在null引用,有些类型就不能取值为null,这就将可空类型和非空类型给区分开来。
如果用公式表达就是:Type + null = Type?
Kotlin整个类型系统可以用下图来描述:
Any?
是所有类型的父类型(supertype);Nothing
是所有类型的子类型(subtype)。
3. 什么是Nothing
Nothing
是一种类型,这种类型很特殊,它没有任何可取的值,也就是这种类型没有实例,用于标记那些永远不能达到的代码位置。什么是永远不能达到的代码位置?其实就是那些永远不会执行的代码,最常见的情况就是异常之后的代码。
以上只是Nothing
类型的常规解释,那么为什么会有这么一种类型,它没有实例,却是所有类型的子类型,其实,Nothing
最主要的用途是类型推导:
fun processPerson(person: Person?) {
val s = person?.name ?: throw IllegalArgumentException("Name required")
val n = person?.name ?: return
}
person?.name
的类型是String?
,而throw
表达式和return
表达式的类型都是Nothing
,再加上Elvis操作符?:
,所以以上表达式的含义是:s
的类型是String
和Nothing
的共同的父类型,String
和Nothing
的共同的父类型当然是String
。所以最后推导出来s
和n
的类型都是String
。
4. 为什么需要Unit
Kotlin类型系统中还有一种常见的类型,那就是Unit
,Unit
很简单,它是一种类型,并且是一种单例类型,像是这样一般:
public object Unit {
override fun toString() = "kotlin.Unit"
}
那么为什么需要Unit
呢?主要是为了把一切函数调用改造成表达式。我们不能说Java中的函数调用是表达式,因为存在特例void
,而Kotlin中的函数会隐式返回Unit
,所以Kotlin中的函数调用皆为表达式,用一个小小的单例类型Unit
,将函数调用全部转换为表达式,这就是Unit
的作用。而我们都知道,表达式在Kotlin中起很重要的作用,诸如Java中的if
,throw
语句在Kotlin中皆被改造成了表达式。