作为Java开发人员,我们经常遇到在应用程序的运行时抛出的NullPointerExceptions(NPE), 这几乎总是由于null值的无法识别而引用的。 空引用通常用于指示缺少的值,在许多情况下,这对于程序员来说并不明显。 虽然Java依赖于强静态类型,但它不允许您区分可以和不能保存空引用的引用类型。 看看下面的代码示例:

322338853100a57d9399c03c3d1972cb.png

方法getAudioDevice返回类型为Device对象,但可能返回null以表示特定系统上缺少该设备。 良好的方法将准确描述该行为,这仍然需要开发人员非常专注。 不知道返回的null引用的可能性将在后续的getName调用中导致可怕的NullPointerException。 如果我们能够将方法的返回设备类型识别为可以为空,那么不是很好吗?

676c97fd3f540e1ceaad93833260a790.png

Java中的Null安全

我们必须找到有助于我们避免因NPE而导致的不必要错误的策略。 一种常见的方法是防御性地检查空引用并以更有意义的方式处理这些情况,例如提供默认值或抛出更有意义的异常。 将此应用于前面的示例将带来以下解决方案:

6e0b4eccce1c44ab28326db7832d86da.png

像这样的构造是许多Java项目的一部分,并且该方法运行良好,除非您忘记添加对某些变量的检查。但是这是一种相当容易出错的方法,并没有减轻使用null的风险。 Java是否为此问题提供了更复杂的解决方案?至少在某些情况下,它确实如此。 Java SE 8引入了Optional类型,它充当容器对象,可能包含也可能不包含null值。

da37cf30c7b8257376e50e37b2c7b20f.png

Java SE 8 Optional

Optional类型是一个包装另一个对象的容器,理论上它可以为null。 适用于Optional实例的代码需要以相当明确的方式处理可能的可空性:

7ab0eca4aab5c6015680ff9f4fa27403.png

调整getAudioDevice方法以返回Optional ,它向客户端指示设备可能不存在。Optional类型可以用于以各种方式处理可能的可空性,这些包括分别使用orElse和orElseThrow等简单方法提供默认值和抛出异常。此外,它确实迫使客户考虑潜在的空值。

5d6380717db6297bff2a0922f1980b3e.png

不幸的是,Optional类型并非旨在作为“通用的Maybe [...]类型”,而是一种让库和API表示缺少返回类型的方法。Optional类型是提供更明确的API的好方法,只需通过观察方法的签名,相应的调用者就可以确切地知道何时需要进行空值处理。然而,它不是一个整体解决方案,因为它不是要替换源代码中的每个空引用。除此之外,您是否可以安全地依赖方法返回类型?

f6b66a756482ca292c30cfee268ee0f7.png

Kotlin中的空值安全

在我们看到Java语言中相当不安全的空值处理之后,我们将介绍另一种方法:Kotlin中提供了非常复杂的方法来避免NullPointerExceptions。语言的类型系统区分可空类型和非可空类型,并且每个类都可以在两个版本中使用。 默认情况下,String类型的引用不能保留null,而String? 允许它。 显然,这种区别本身并没有产生很大的不同。 因此,无论何时选择使用可空类型,编译器都会强制您适当地处理可能的问题,即潜在的NPE。

0005efce322f29157f3542a06050820d.png

此代码示例显示了使用可空类型的不同方法。首先说明,直接访问可空类型的成员是不可能的,因为这会导致与Java相同的问题。此操作让编译器接受对变量的调用(在if块内),就像它不可为空一样。表达式b?.length可以转换为“如果b不为null则在调用b的长度,否则返回null”。此表达式的返回类型是Int?类型因为它可能导致null。

43038c3b2c192bb29bf16bc4ceeda9de.png

除了显示的运算符之外,Kotlin库还提供了许多有用的函数,比如String?:: IsNullOrEmpty(),String :: toDoubleOrNull()和List :: filterNotNull()等等。 所有这些都支持开发人员进行适当的可空性处理,并使NPE几乎不可能发生。

d41d868946b16dd88107085f77c1c172.png

两种语言之间的互操作

Kotlin的一个关键属性是它与Java源代码的出色的互操作性。您可以轻松地在项目中混合使用两种语言,并从Java调用Kotlin,反之亦然。但是,在null安全的情况下,它是如何工作的?正如本文前面所学到的,每个Java引用都可以为null,这使得Kotlin很难将其安全性原则有意义地应用于它们。

e0e83a82bd67576e709e4a10eeaceaa5.png

来自Java的类型称为平台类型,用感叹号表示,例如,串!。对于这些平台类型,编译器无法确定是否由于缺少信息而可以为空。作为开发人员,例如,当平台类型分配给变量时,可以显式设置正确的类型。如果您指定平台类型String!对于非可空类型String的变量,编译器允许它,因此,不强制安全访问该变量。然而,如果该决定结果是错误的,即返回空引用,则NPE将在运行时抛出。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐