【译】Kotlin 类型层级结构一览

作者 原文
Nat Pryce A Whirlwind Tour of the Kotlin Type Hierarchy

Kotlin 有大量优秀的语言文档教程。但是我没有找到任何描述 Kotlin 类型层级结构(type hierarchy)是如何组成的文章。那真是太可惜了,因为我觉得这个类型系统很棒1

关于 Kotlin 的类型层级结构只有很少的几条规则需要了解,这些规则一致和可预测地结合在一起。得益于这些规则,Kotlin 可以提供一些有用的语言特性——空安全性(null safety)、多态性(polymorphism)、不可达代码分析(unreachable code analysis),而不需要在编译器和 IDE 中使用一些手段进行特殊处理。

顶层类型

Kotlin 中所有类型被组织成父类型/子类型(supertype/subtype)关系的层级结构。

这个层级结构的顶层Any 类型。例如 StringInt 都是 Any 的子类型。

Any 相当于 Java 的 Object 类型。与 Java 不同的是 Kotlin 不区分原始类型(primitive type)和其它的类型。它们都是同一类型层级结构的一部分。

如果定义了一个没有指定父类的类型,则该类型将是 Any 的直接子类型。

class Fruit(val ripeness: Double)

如果你为定义的类型指定了父类,则该父类将是新类型的直接父类型,并且新类型的最终祖先为 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)

如果你的类型实现了多个接口,那么它将具有多个直接的父类型,而 Any 同样是最终的祖先。

interface ICanGoInASalad
interface ICanBeSunDried

class Tomato(ripeness: Double): 
    Fruit(ripeness), 
    ICanGoInASalad, 
    ICanBeSunDried 

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: inferred type is Fruit but Banana was expected 

可空类型(Nullable Types)

与 Java 不同,Kotlin 区分非空(non-null)和可空(nullable)类型。到目前为止,我们看到的类型都是非空类型,Kotlin 不允许 null 作为这些类型的值。访问非空类型的变量将永远不会抛出空指针异常。

类型检查器拒绝尝试在非空类型上使用 null 或可空类型的代码。

例如:

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

如果一个变量存储的值可能为空,则需要使用与值对应的可空类型。例如 String? 类型是与 String 对应的可空类型,String? 类型的变量可以为任意的 String 值或者 null

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

类型检查器能确保你在使用一个可空类型的变量前不会忘记检查是否非空。Kotlin 提供了一些操作符用以便捷的使用可空类型。有关例子请参阅 Kotlin 语言文档的 Null Safety 部分

可空类型具有与对应的非空类型相同的层级结构。例如 StringAny 的子类型,则 String?Any? 的子类型;BananaFruit 的子类型,则 Banana?Fruit? 的子类型。

Any 是非空类型层级结构的顶层,Any? 则是可空类型层级结构的顶层。因为 Any?Any 的父类型,所以 Any? 是 Kotlin 类型层级结构的最顶端。

非空类型是其对应可空类型的子类型。例如 String 作为 Any 的子类型,同时也是 String? 的子类型。

这就是为什么可以将非空的 String 值存储到可空的 String? 变量中,但是不能将可空的 String? 值存储到非空的 String 变量中。Kotlin 的空安全性不是由特殊规则实施的,而是可空类型与非空类型之间父类型/子类型关系的结果。

这一规则也适用于自定义的类型。

Unit

Kotlin 是一种表达式导向的语言,所有流程控制语句都是表达式。它没有 Java 和 C 中的 void 函数,函数总是会返回一个值。通常我们为了副作用(side effect)而调用的那些没有实际计算任何东西的函数,将会返回 Unit——一种只有一个值的类型。

大多数情况下,你不需要明确指定 Unit 作为返回类型或从函数返回 Unit。如果编写的函数具有块代码体,并且不指定返回类型,则编译器会将其视为返回 Unit 类型,否则编译器会使用推断的类型。

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

val u: Unit = example()

Unit 并没什么特别之处。就像任何其他类型,它是 Any 的子类型,而 Unit?Any? 的子类型。

Unit? 类型是一个奇怪的特殊例子,这是 Kotlin 的类型系统一致性的结果。Unit? 类型只有两个值:Unit 单例和 null。我从来没有发现需要明确使用 Unit? 类型的地方,但是在类型系统中没有特殊的 void 这一事实,使得处理各种函数泛型变得更加容易。

Nothing

在 Kotlin 类型层级结构的最底层是 Nothing 类型。

顾名思义,Nothing 是没有实例的类型。Nothing 类型的表达式不会产生任何值。

注意 UnitNothing 之间的区别,对 Unit 类型的表达式求值将返回 Unit 的单例,而对 Nothing 类型的表达式求值则永远都不会返回。

这意味着任何类型为 Nothing 的表达式之后的所有代码都是无法得到执行的(unreachable code),编译器和 IDE 会向你发出警告。

什么样的表达式类型为 Nothing 呢?流程控制中与跳转相关的表达式

例如 throw 关键字打断表达式的计算,并从函数中抛出异常。因此 throw 就是 Nothing 类型的表达式。

通过将 Nothing 作为所有类型的子类型,类型系统允许程序中的任何表达求值失败。这是真实世界的模型,例如 JVM 在计算表达式时内存不足,或者是有人拔掉了计算机的电源插头。这也意味着我们可以从任何表达式中抛出异常。

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

你可能会惊奇地发现,return 语句的类型也为 Nothingreturn 是一个流程控制语句,它立即从函数中返回一个值,打断其所在表达式的求值。

fun formatCellRounded(value: Double): String =
    val rounded: Long = if (value.isNaN()) return "#ERROR" else Math.round(value)
    rounded.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
}

与空安全一样,不可达代码分析是类型系统的一个特性。无需像 Java 一样在编译器和 IDE 中使用一些手段进行特殊处理。

可空的 Nothing?

Nothing 像任何其他类型一样,如果允许其为空则可以得到对应的类型 Nothing?Nothing? 只能包含一个值:null。事实上 Nothing? 就是 null 的类型。

Nothing? 是所有可空类型的最终子类型,所以我们可以使用 null 作为任何可空类型的值。

结论

当你同时考虑这一切时,可能会觉得 Kotlin 的整个类型层级结构相当复杂。

但不要害怕!

我希望这篇文章能证明 Kotlin 有一个简单而一致的类型系统。只有很少的几条规则需要了解:这是一个父类型/子类型关系的层级结构,而 Any? 在顶层,Nothing 在底层,以及非空类型是对应可空类型的子类型。就这么多了,没有其它特殊规则。一些有用语言特性,如空安全性、面向对象多态性、不可达代码分析都是由这些简单,可预测的规则引起的。得益于这种一致性,Kotlin 的类型检查器是一个强有力的工具,可以帮助你编写简洁正确的程序。


<a id="neat">1.</a> “很棒(neat)” 的意思是 “优雅巧妙高效”,而不是凯文 · 科斯特纳在麦当娜的舞台上表现出的含义<a id="neat"> </a>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容