Kotlin 的类型层次结构需要学习的规则很少。这些规则一致且可预测地结合在一起。由于这些规则,Kotlin 可以提供有用的、用户可扩展的语言特性——空安全、多态性和无法访问的代码分析——而无需在编译器和 IDE 中求助于特殊情况和临时检查。
从顶部开始
所有类型的 Kotlin 对象都被组织成子类型/超类型关系的层次结构。
在该层次结构的“顶部”是抽象类Any。例如,类型 String 和 Int 都是Any.
Any相当于Java的Object类。与 Java 不同,Kotlin 没有区分语言固有的“原始”类型和用户定义的类型。它们都是同一类型层次结构的一部分。
如果您定义的类不是从另一个类显式派生的,则该类将是 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
可空类型
与 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 类型层次结构的最顶层。
非空类型是其可空等价物的子类型。例如,String作为Any的子类型,也是String?的子类型。
这就是为什么您可以将非空字符串值存储到可为空字符串中的原因,但非空字符串变量不能存储可为空的字符串。Kotlin 的空安全性不是由特殊规则强制执行的,而是适用于非空类型之间的相同子类型/超类型规则的结果。
这也适用于用户定义的类型层次结构。
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?的子类型。
类型Unit?是一个奇怪的小边缘情况,是 Kotlin 类型系统一致性的结果。它只有两个成员:Unit值和null。我从来没有发现需要明确地使用它,但是类型系统中没有“void”的特殊情况这一事实使得通用地处理所有类型的函数变得更加容易。
Nothing
在 Kotlin 类型层次结构的最底层是 Nothing类型。
顾名思义,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用作任何可空类型的值。
结论
当您一次性考虑所有这些时,Kotlin 的整个类型层次结构可能会感觉非常复杂。
但永远不要害怕!
我希望这篇文章已经证明 Kotlin 有一个简单且一致的类型系统。需要学习的规则很少:Any?顶部和Nothing底部的超类型/子类型关系的层次结构,以及非空类型和可空类型之间的子类型关系。就是这样。没有特殊情况。空安全、面向对象的多态性和无法访问的代码分析等有用的语言功能都源于这些简单、可预测的规则。由于这种一致性,Kotlin 的类型检查器是一个强大的工具,可以帮助您编写简洁、正确的程序。
英文好的同学可以直接阅读原文