Java 和 Kotlin 中的可空性(Nullability)
可空性(Nullability) 是指一个变量能否为 null 值的能力. 当变量值为 null, 使用这个变量指向的对象将会导致 NullPointerException 异常. 有很多种方法来编写代码, 来尽量减少发生指针异常的可能.
这篇向导会介绍在 Java 和在 Kotlin 中, 处理可为 null 值的变量的方案之间的区别. 这可以帮助你从 Java 迁移到 Kotlin, 并按照纯正的 Kotlin 风格来编写你的代码.
这篇向导的第一部分介绍最重要的差别 – Kotlin 对可为 null 类型的支持, 以及 Kotlin 如何处理 来自 Java 代码的类型. 第二部分, 从 检查函数调用的结果 开始, 通过几种具体的情况, 解释二者的差异.
对可为 null 类型的支持
Kotlin 和 Java 的类型系统最重要的区别是, Kotlin 明确支持 可为 null 的类型. 通过这种方式, 指定哪个变量可能包含 null 值. 如果一个变量可以为 null, 那么对这个变量调用方法是不安全的, 因为可能导致 NullPointerException 异常. Kotlin 在编译期禁止这样的调用, 因此防止很多潜在的异常. 在运行期, 可为 null 类型的对象与不可为 null 类型的对象的处理方式是一样的: 可为 null 的类型 并不是不可为 null 类型的一个包装. 所有的检查都在编译期进行. 这就意味着, 在 Kotlin 中使用可为 null 类型, 几乎不存在运行期负担.
在 Java 中, 如果你不编写 null 检查代码, 那么方法可能会抛出 NullPointerException 异常:
这个调用会产生下面的输出:
在 Kotlin 中, 所有的通常类型默认都是不可为 null 的, 除非你将它们明确的标记为可为 null. 如果你不期望 a 可以为 null, 可以将 stringLength() 函数声明如下:
参数 a 类型为 String 类型, 在 Kotlin 中代表它永远包含一个 String 实例, 而且不能为 null. Kotlin 中可为 null 的类型使用问号 ? 来标记, 例如, String?. 如果 a 是 String 类型, 那么运行期出现 NullPointerException 是不可能的, 因为编译器会强制要求所有传递给 stringLength() 的参数不能为 null.
试图向 stringLength(a: String) 函数传递 null 值参数, 会导致编译期错误, "Null can not be a value of a non-null type String":

如果你想要向这个函数传递任意值的参数, 包括 null 值, 请在参数类型之后添加一个问号 String?, 并在函数体内部进行检查, 以确保参数值不是 null:
在检查通过之后, 在编译器执行检查的范围内, 编译器会将这个变量当作是不可为 null 的类型 String.
你不进行这样的检查, 代码会编译失败, 错误信息如下: "Only safe (?.) or non-nullable asserted (!!.) calls are allowed on a nullable receiver of type String?".
这段代码还可以写得更简短一些 – 使用 安全调用操作符 ?. (If-not-null 的简写表达), 可以将 null 检查和方法调用结合为一个操作符:
平台类型(Platform types)
在 Java 中, 你可以使用注解来表示一个变量是否可以为 null. 这些注解不是标准库的一部分, 但你可以分别添加这些注解. 例如, 你可以使用 JetBrains 注解 @Nullable 和 @NotNull (来自 org.jetbrains.annotations 包), 或 Eclipse 的注解(org.eclipse.jdt.annotation 包). 当你 从 Kotlin 代码调用 Java 代码 时, Kotlin 能够识别这些注解, 并根据注解来处理这些类型.
如果你的 Java 代码没有这样的注解, Kotlin 会将 Java 类型当作 平台类型(Platform types). 但由于 Kotlin 没有这些类型的可空性信息, 它的编译器会允许对这些类型进行操作. 需要由你来决定是否执行 null 检查, 因为:
和 Java 中一样, 如果你试图在
null值上执行操作, 那么会发生NullPointerException异常.编译器不会对多余的 null 检查进行高亮度显示, 如果你对一个不可为 null 类型的值,执行 null 值安全的操作, 通常会高亮度显示.
对确定不为 null (definitely non-nullable) 类型的支持
在 Kotlin 中, 如果要覆盖一个包含 @NotNull 参数的 Java 方法, 你需要 Kotlin 的确定不为 null (definitely non-nullable) 类型.
例如, 对于 Java 中的这个 load() 方法:
要在 Kotlin 中成功的覆盖 load() 方法, 你需要将 T1 声明为确定不为 null (T1 & Any):
关于泛型中的确定不为 null 类型, 详情请参见 确定不为 null 类型.
检查函数调用的结果
需要进行 null 值检查的一种常见的情况是, 通过函数调用得到结果.
在下面的示例中, 有 2 个 类, Order 和 Customer. Order 包含一个指向 Customer 实例的引用. findOrder() 函数返回 Order 类的一个实例, 如果它无法找到订单, 则返回 null 值. 目标要是处理得到的订单的客户实例.
下面是 Java 中的类:
在 Java 中, 调用函数, 并对结果进行 if-not-null 检查, 然后取得需要的属性值:
如果将上面的 Java 代码直接转换为 Kotlin 代码, 结果是:
这里可以使用 安全调用操作符 ?. (If-not-null 的简写表达), 结合标准库中的任何 作用域函数. 通常可以使用 let 函数:
下面是更加简短的版本:
使用默认值代替 null
null 值检查通常用于对值为 null 的情况 设置默认值.
带有 null 检查的 Java 代码:
要在 Kotlin 中表达同样的功能, 请使用 Elvis 操作符 (If-not-null-else 的简写表达):
返回一个值或返回 null 的函数
在 Java 中, 操作列表元素时你需要很小心. 你必须检查某个下标位置对应的元素是否存在, 然后才能使用这个元素:
Kotlin 标准库通常会提供一些函数, 其名称表示它们可能会返回 null 值. 在集合 API 中, 这样的函数尤其普遍:
聚合(Aggregate)操作
你需要得到最大元素, 或者如果不存在元素则得到 null 值, 在 Java 中你可以使用 Stream API:
在 Kotlin 中, 可以使用 聚合操作:
更多详情请参见 Java 和 Kotlin 中的集合.
安全的类型转换
如果你需要安全的转换一个类型, 在 Java 中你会使用 instanceof 操作符, 然后检查它是否成功:
在 Kotlin 中为了避免异常, 可以使用 安全的转换操作符 as?, 它会在转换失败时返回 null:
下一步做什么?
阅读其他的 Kotlin 惯用法.
学习如何使用 Java-to-Kotlin (J2K) 转换器, 将既有的 Java 代码转换为 Kotlin.
阅读其他的迁移向导:
如果你有喜欢的惯用法, 欢迎你发送一个 pull request, 分享给我们!