Kotlin 类型层次结构

Kotlin 的类型层次结构需要学习的规则很少。这些规则一致且可预测地结合在一起。由于这些规则,Kotlin 可以提供有用的、用户可扩展的语言特性——空安全、多态性和无法访问的代码分析——而无需在编译器和 IDE 中求助于特殊情况和临时检查。

从顶部开始

所有类型的 Kotlin 对象都被组织成子类型/超类型关系的层次结构。

在该层次结构的“顶部”是抽象类Any。例如,类型 String 和 Int 都是Any.

image.png

Any相当于Java的Object类。与 Java 不同,Kotlin 没有区分语言固有的“原始”类型和用户定义的类型。它们都是同一类型层次结构的一部分。

如果您定义的类不是从另一个类显式派生的,则该类将是 Any 的直接子类型。

class Fruit(val ripeness: Double)
image.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)
image.png

如果您的类实现了一个或多个接口,它将具有多个直接超类型,其中 Any 作为最终祖先。

interface ICanGoInASalad
interface ICanBeSunDried

class Tomato(ripeness: Double): 
    Fruit(ripeness), 
    ICanGoInASalad, 
    ICanBeSunDried
image.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: inferred type is Fruit but Banana was expected

可空类型

与 Java 不同,Kotlin 区分“非空”和“可空”类型。到目前为止我们看到的类型都是“非空”的。Kotlin 不允许null用作这些类型的值。您可以保证取消引用对“非空”类型值的引用永远不会抛出 NullPointerException。

类型检查器拒绝尝试使用 null 或期望非 null 类型的可为 null 类型的代码。

例如:

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

如果您希望某个值可能为空,则需要使用该值类型的可空等价物,由后缀 '?' 表示。例如,该类型String?是可空等价的String,因此允许所有字符串值加上空值。

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

类型检查器确保您永远不会在没有首先测试它不为空的情况下使用可空值。Kotlin 提供了操作符来使处理可为空类型更加方便。有关示例,请参阅Kotlin 语言参考的Null Safety 部分

当非空类型通过子类型关联时,它们的可空等价物也以相同的方式关联。例如,因为String是 的子类型Any,String?是 的子类型Any?,因为Banana是 的子类型Fruit,Banana?是 的子类型Fruit?。

正如Any非空类型层次结构Any?的根一样, 是可为空类型层次结构的根。因为Any?是Any 的超类型,Any?是 Kotlin 类型层次结构的最顶层。

image.png

非空类型是其可空等价物的子类型。例如,String作为Any的子类型,也是String?的子类型。

image.png

这就是为什么您可以将非空字符串值存储到可为空字符串中的原因,但非空字符串变量不能存储可为空的字符串。Kotlin 的空安全性不是由特殊规则强制执行的,而是适用于非空类型之间的相同子类型/超类型规则的结果。

这也适用于用户定义的类型层次结构。

image.png

Unit

Kotlin 是一种面向表达式的语言。所有控制流语句(除了变量赋值,异常情况下)都是表达式。Kotlin 没有像 Java 和 C 那样的 void 函数。函数总是返回一个值。实际上不计算任何东西的函数——例如,因为它们的副作用而被调用—— return Unit,一种具有单个值的类型,也称为Unit.

大多数情况下,您不需要显式指定 Unit 作为返回类型或从函数返回 Unit。如果你写了一个带有块体的函数并且没有指定结果类型,编译器会把它当作一个单元函数。如果编写单表达式函数,编译器可以推断 Unit 返回类型,就像任何其他类型一样。

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

val u1: Unit = example1()

fun example2() =
    println("single-expression function for which the compiler infers the return type as Unit")

val u2: Unit = example2()

没什么特别的Unit。像任何其他类型一样,它是Any, 它可以为空,因此是Unit?的子类型,它是Any?的子类型。

image.png

类型Unit?是一个奇怪的小边缘情况,是 Kotlin 类型系统一致性的结果。它只有两个成员:Unit值和null。我从来没有发现需要明确地使用它,但是类型系统中没有“void”的特殊情况这一事实使得通用地处理所有类型的函数变得更加容易。

Nothing

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

image.png

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

请注意 Unit 和 Nothing 之间的区别。表达式类型 Unit 的计算结果为单例值Unit。对类型为 Nothing 的表达式的求值根本不会返回。

这意味着任何跟在 Nothing 类型表达式后面的代码都是不可访问的。编译器和 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语句的类型为 Nothing 时可能会感到惊讶。Return 是一个控制流语句,它立即从封闭函数返回一个值,中断对它所属的任何表达式的求值。

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
}

与空安全一样,无法访问的代码分析不是通过 IDE 和编译器中的临时、特殊情况检查来实现的,因为它必须在 Java 中进行。这是类型系统的一个函数。

什么都可以为空?

Nothing,与任何其他类型一样,可以设为可为空,并给出类型Nothing?。 Nothing?可以只包含一个值:null。事实上,Nothing? 是null的类型。

Nothing?是所有可空类型的最终子类型,它允许将该值null用作任何可空类型的值。

image.png

结论

当您一次性考虑所有这些时,Kotlin 的整个类型层次结构可能会感觉非常复杂。

image.png

但永远不要害怕!

我希望这篇文章已经证明 Kotlin 有一个简单且一致的类型系统。需要学习的规则很少:Any?顶部和Nothing底部的超类型/子类型关系的层次结构,以及非空类型和可空类型之间的子类型关系。就是这样。没有特殊情况。空安全、面向对象的多态性和无法访问的代码分析等有用的语言功能都源于这些简单、可预测的规则。由于这种一致性,Kotlin 的类型检查器是一个强大的工具,可以帮助您编写简洁、正确的程序。

英文好的同学可以直接阅读原文

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

推荐阅读更多精彩内容