Kotlin Type Hierarchy

Kotlin类型层级一览(翻译原文)

原文链接

Kotlin有很多关于语言的文档和教程指南。但是没有看关于类型层级的相关描述。
了解之后,我发现非常的简洁。

Kotlin类型层级要学习的规则非常少,这些规则保持一致性和可预见性。有了这些规则,Kotlin可以提供有用的,用户可扩展的语言特性,例如:null安全,多态性和无法执行代码检测;不需要在IDE和编译阶段做特殊处理和额外的操作。

从顶层开始

所有Kotlin对象的类型都组织在“子类型/父类型”的关系层级中。
最顶层一级是抽象类”Any”,例如:String和Int都是Any的子类型

kotlinTypeHierarchy-16ae82b4.png
Any等价于Java的Object类。和Java不同Kotlin没有区分“原始类型”和用户定义类型。他们都是类型层级的一部分。 如果定义了一个类,但是没有继承任何其他父类,那么这个类就会自动的继承至Any
class Fruit(val ripeness: Double)
kotlin Type Hierarchy-3ead3803.png
如果你定义了一个基类,那么这个基类就是新类的父类,但是最终的父类还是Any
abstract class Fruit(val ripeness: Double)
class Banana(ripeness: Double, val bendiness: Double):
Fruit(ripeness)
class Peach(ripeness: Double, val fuzziness: Double):
Fruit(ripeness)
kotlin Type Hierarchy-fa210f96.png
如果你的类实现了一个或多个接口,那么这个类会有多个父类型,最终的祖先类型是Any
interface ICanGoInASalad
interface ICanBeSunDried

class Tomato(ripeness: Double):
Fruit(ripeness),
ICanGoInASalad,
ICanBeSunDried
kotlin Type Hierarchy-e161388a.png

Kotlin类型检查强制子类型/父类型之间的关系
例如:可以使用父类型变量保存一个子类型

var f: Fruit = Banana(bendiness=0.5)
f = Peach(fuzziness=0.8)

但是不能够使用子类型变量保存父类型

val b = Banana(bendiness=0.5)
val f: Fruit = b
val b2: Banana = f
// Error: Type mismatch: 推导类型是Fruit但是希望的是Banana

Nullable可以为空的类型
和Java不同Kotlin区分“非空”和“空”类型。上面提到的都是非空类型。Kotlin不允许把null赋值给“非空类型”。这样可以保证你在解引用一个“非空类型”的时候,永远也不会抛出NullPointerException异常
类型检查会拒绝把null或者“空类型”赋值给“非空类型”
例如:

var s : String = null
// Error: Null can not be a value of a non-null type String

如果想要一个“空类型”,可以使用后缀“?“。例如:String?就是一个可以为空的字符串类型。

var s : String? = null
s = "foo"
s = null
s = bar

类型检查会保证你在使用”空类型“之前一定会检查是否为空。参见
Null Safety section of the Kotlin language reference
”非空类型“和”空类型“是可以继承的。例如:String是Any的子类型,String?是Any?的子类型,Banana是Fruit子类,Banana?是Fruit?子类。

kotlin Type Hierarchy-bca1adc9.png

就像Any是所有非空类型的父类一样,Any?也是所有空类型的父类。
同时Any?也是Any的父类,Any?是kotlin类型层级中最顶端的。

一个非空类型是它对应的空类型的子类。例如:String是Any的子类,同时也是String?的子类。

kotlin Type Hierarchy-9f1cf554.png
这就是为什么可以把非空类型赋值给空类型变量,反之则不行。 Kotlin的安全null是符合“子类型/父类型”规则的产物,而不是通过特殊规则来强制保证。 同样的也适用于用户自定义的类型
kotlin Type Hierarchy-8ca74cde.png
## Unit Kotlin是一个面向表达式的语言。所有的控制流语句(除了非常规的变量赋值)都是表达式。Kotlin没有void返回值的函数,所有的函数都有一个返回值。如果不显示指定,默认返回Unit,只有一个值的类型。

大多数情况下不需要显示指定返回值,编译器会认为返回Unit,否则编译器会推导返回类型。

fun example() {
println("block body and no explicit return type, so returns Unit")
}

val u: Unit = example()

Unit并不特殊,它和其他类型一样也是Any的子类型,也可以是空类型。

kotlin Type Hierarchy-8679a9c3.png
类型Unit?是一个奇怪的情况,仅仅是保持Kotlin类型系统一致性的产物。尚未找到明确需要使用它的地方。事实上在类型系统中不需要针对“void”做特殊处理,可以很好的做通用型编程来处理各种函数。

Nothing

在cotlin类型层级中最底层的是Nothing。

kotlin Type Hierarchy-eb177d5c.png

就像他的名字一样,Nothing是一个没有实例的类型。Nothing表达式不会返回任何值。
注意Unit和Nothing的区别。对一个Unit表达式求值的时候返回一个单例模式的值Unit。对Nothing表达式求值永远都不会返回。

也就是说任何在Nothing类型的表达式后面的代码是执行不到的。编译器会警告这类无法执行的代码。

什么样的表达式会得出Nothing呢?那就是执行流程控制的时候。
例如:
使用throw关键字中断一个表达式计算,从函数中抛出异常。抛异常就是一个Nothing类型的表达式。

通过把Nothing作为所有其他类型的子类型,类型系统允许任何一个表达式可以在计算的时候失败(注:原文如下,感觉不理解这段话,翻译的可能有问题。the type system allows any expression in the program to actually fail to calculate a value.)。这是对真实世界任何可能性的建模。例如JVM在计算表达式的时候内存溢出,或者有人拔掉了计算机的电源线。
这也意味着我们可以在任何表达式中抛出异常。

fun formatCell(value: Double): String =
if (value.isNaN())
throw IllegalArgumentException("$value is not a number")
else
value.toString()

一个无限循环的函数或者终止当前进程的返回类型都是Nothing。例如Kotlin标准库声明的exitProcess函数如下:

fun exitProcess(status: Int): Nothing

如果你自己写的函数返回了Nothing,编译器会检查调用你函数之后是否有无法执行的代码,就像内置的流程控制语句一样。

inline fun forever(action: ()->Unit): Nothing {
while(true) action()
}

fun example() {
forever {
println("doing...")
}
println("done") // Warning: Unreachable code
}

就像安全null一样,无法执行代码的检查也不是像Java一样通过额外操作或者在IDE和编译器中做特殊处理做到的,而是类型系统的一个功能。

Nullable Nothing?

Nothing和其他类型一样,可以为空,通过Nothing?表示。Nothing?只有一个值null,事实上Nothing?就是null类型.
Nothing?是所有空类型的最终的子类型,这使得null可以赋值给任何空类型。

kotlin Type Hierarchy-07543dec.png
## 总结 当考虑所有情况的时候,Kotlin的整个类型层级似乎非常复杂。
kotlin Type Hierarchy-35fe50d5.png
但是不要畏惧

我希望通过这篇文章演示Kotlin拥有一个简单并且保持一致性的类型系统。
只有很少的规则需要学习:
1 一个最顶端的Any?和最底端的Nothing相关的子类/父类层级关系
2 空类型和非空类型之间的子类关系
就这些,没有特殊情况。
那些有用的语言特性例如安全null,面向对象的多态,无法执行代码的检测,都是这些简单,可预见的规则的结果。