科特林可空性功能

空安全编程-Kotlin方式

免责声明:这篇ktor文章最初发布在Dzone Java Guide 2018中,可以下载 这里.

在本文中,我们将回顾由空指针引起的问题以及如何在Java中避免它们。之后,本文将演示Kotlin可空性功能如何工作以及如何改进您的代码。

As Java developers, we're very accustomed to NullPointerExceptions (NPE) that are thrown at the runtime of an application. This almost always happens unintentionally in consequence of a bug, which is based on unrecognized references to null. The null reference is often used to indicate absent values, which isn't obvious to the programmer in many cases. Although Java relies on strong static typing, it doesn't let you distinguish between reference types that can 和 cannot hold a null reference. Have a look at the following code example:

Device audio = new DeviceProvider().getAudioDevice();
String audioName = audio.getName();

The method getAudioDevice returns an object of type Device but might return null in order to denote the absence of that device on particular systems. Well documented methods will describe exactly that behavior, which still requires the developer to be very attentive. Not knowing about the possibility of a returned null reference is going to cause an awful NullPointerException in the subsequent call to getName. Wouldn't it actually be nice if we were able to identify the method's returned Device type as nullable (or non-nullable respectively) firsthand?

Java中的零安全

We have to find strategies that help us avoiding unwanted bugs due to NPEs. A common approach is to defensively check for null references 和 handle these cases in a way that makes more sense, such as providing default values or throwing more meaningful exceptions. Applying this to the previous example brings us to the following solution:

Device audio = new DeviceProvider().getAudioDevice();
String audioName;
if (audio != null) {
    audioName = audio.getName();
} else {
    throw new IllegalStateException("This system does 不 provide an audio device.");
}

Constructs like these are part of any Java project 和 the approach works well unless you forget to add checks for certain variables. It's a fairly error-prone approach 和 doesn't mitigate the fact that using null as a representative for absent things is risky.

Java是否提供任何更复杂的解决方案来解决此问题?至少在 一些 情况。 Java SE 8引入了 Optional 充当 “可能包含也可能不包含非空值的容器对象”。这听起来很有希望,下一步需要考虑。

Java SE 8可选

The Optional type is a container that wraps another object, which can theoretically be null. Code that works on instances of Optional needs to handle the possible nullability in a rather explicit way:

Optional audio = new DeviceProvider().getAudioDevice();
String audioName = audio
    .flatMap(Device::getName)
    .orElseThrow(() -> new IllegalStateException("This system does 不 provide an audio device."));

The getAudioDevice method was adjusted to return an Optional, which doubtlessly indicates to the client that the device can be absent. Instead of carelessly accessing that nullable type, the Optional type can be used for handling the possible nullability in various ways. These include providing default values 和 throwing exceptions with the help of simple methods like orElseorElseThrow respectively. Furthermore, it literally forces the client to think about the potential null case.

Unfortunately, the whole Optional story already ends with this use case very suddenly. As stated 通过 Java language architect Brian Goetz in this StackOverflow发布, the Optional type was 不 intended as a "general purpose 也许 [...] type" but a way to let libraries 和 APIs express the absence of a return type (as we saw in the previous example).

For further usage examples of Optional, you should find a suitable 文章 in your preferred search engine.

After all, the Optional type is a great way to provide more explicit APIs that let the corresponding callers know exactly when null handling is required just 通过 observing the method's signature. Nevertheless, it's 不 a holistic solution since it isn't meant to replace each 和 every null reference in the source code. Aside from that, can you safely rely on method return types, which are 不 marked as Optional?

科特林可空性:Kotlin中的零安全

在我们看到了Java语言中相当不安全的null处理之后,本节将介绍另一种方法: 科特林 programming language, as an example, provides very sophisticated means for avoiding NullPointerExceptions.

The language's type system differentiates between nullable 和 non-nullable types 和 every class can be used in both versions. By default, a reference of type String cannot hold null, whereas String? allows it. This distinction on its own doesn't make a very big difference obviously. Therefore, whenever you choose to work with nullable types, the compiler forces you to handle possible issues, i.e. potential NPEs, appropriately.

//declare a variable with nullable String type, it's OK to assign `null` to it
var b: String? = "possiblyNull"

// 1. does 不 compile, could throw NPE
val len = b.length

// 2. Check nullability before access
if (b != null){
    b.length
}

// 3. Use safe operator
val len = b?.length

This code example shows different ways of working with nullable types (String? in this case). As demonstrated first, it's 不 possible to access members of nullable types directly since this would lead to the same problems as in Java. Instead, traditionally checking whether that type is null makes a difference. This action persuades the compiler to accept invocations on the variable (inside the if-block) as if it were 不 nullable. Note that this does 不 work with mutable vars because they could possibly be set to null from another thread between the check 和 first action in the block.
As an alternative to explicit checks, the safe call operator ?. can be used. The expression b?.length can be translated to "call length on b if b is 不 null, otherwise return null". The return type of this expression is of type Int? because it may result in null. Chaining such calls is possible 和 very useful because it lets you safely omit a great number of explicit checks 和 makes the code much more readable:

person?.address?.city ?: throw IllegalStateException("No city associated to person.")

Another very useful operator is the elvis operator ?: that perfectly complements the safe call operator for handling else cases. If the left-hand expression is 不 null, the elvis operator returns it, otherwise, the right-hand expression will be called.

您需要知道的最后一个运算符称为 非空断言运算符 !!. It converts any reference to a non-null type. This unchecked conversion may cause an NPE if that reference is null after all. The 非空断言运算符 should only be used with care:

person!!.address!!.city //NPE will be thrown if person or address is null

In addition to the shown operators, the 科特林 library provides plenty of helpful functions like String?::IsNullOrEmpty(), String::toDoubleOrNull()List::filterNotNull(), to name just a few. All of them support the developer in proper nullability handling 和 make NPEs almost impossible.

两种语言互操作

科特林的关键特性之一是其与Java源代码的出色互操作性。您可以在项目中轻松混合两种语言,并从Java调用Kotlin,反之亦然。但是,在没有安全性的情况下,该如何工作?

As learned earlier in this 文章, every Java reference can be null, which makes it hard for 科特林 to apply its safety principles to them meaningfully. A type coming from Java is called 平台类型, denoted with an exclamation mark, e.g. String!. For these 平台类型s, the compiler isn't able to determine whether it's nullable or 不 due to missing information. As a developer, when a 平台类型 is e.g. assigned to a variable, the correct type can be set explicitly. If you assign a 平台类型 String! to a variable of the non-nullable type String, the compiler allows it 和, as a consequence, safe access to that variable isn't being enforced. Nevertheless, if that decision turns out to be wrong, i.e. a null reference is returned, NPEs will be thrown at runtime. Fortunately, there's a solution that allows providing more information to the 科特林 compiler 通过 applying certain 注解 到相应的Java方法。这使编译器能够确定实际的可空性信息,并使平台类型不再需要。

底线

It's important to understand that 科特林 does 不 try to avoid null references as such but raises the attention for null-related issues 和 especially NPEs enormously. If you take care of 平台类型s 和 defensively decide to use them as nullable ones or apply the mentioned 注解 to your Java libraries, you should be safe. NullPointerExceptions will be a thing of the past. As set out above, the 科特林 language provides many useful operators 和 other functions that simplify working with nullable types tremendously 通过 making use of its clever type system. We can hope to see similar solutions in future Java versions soon.

关于2个想法“科特林可空性功能

  • 罗德·费舍尔

    Isn’真正的问题是,可选不是’设计为通用“Maybe”-类型?我认为,在所有情况下,尤其是作为返回类型,我们仍应尽最大努力避免null。

    例如那里’这是语义问题,因为null值实际上是什么意思?这是故意的吗?是否由于某些错误而导致(例如,编写错误的SQL返回了不应 ’在那里吗?这是什么意思?即使Kotlin看到了“?”我假设它在中添加@NotNull批注“pure”Java,某些IDE将使用Java来创建适当的警告,但我认为该合同不够强大。

    从数据/内存的逻辑也可以看出’也是一个奇怪的变形虫。它要么指向一个不存在的内存位置,要么指向一个带有垃圾数据的内存位置。两者都是胡说八道,只会制造一罐蠕虫。幸运的是,这不是C / C ++’更糟。正确的解决方案指向显式说明的内存位置的位置–我代表空对象。

    不幸的是,在Java中(由于Option有缺陷,因为它没有设计成适当的Maybe表示形式),这要求我们要么使用NullObject模式。或者至少要确保一个永远不会返回null,例如,使用null作为内部值,而始终返回Optional。我认为前者是最正确的,但我个人通常出于实用主义而退回到后者。

    空是设计不良的遗产,应尽可能避免使用IMO。即使是空引用的创建者也这样说。与大量人通过PHP提倡ASP.net时的感觉有些相似,他们的支持可以归结为高级模板系统,用于将服务器端代码注入到客户端代码中。–基本上是做错事的更好方法。仅仅因为有解决错误的好工具并不能’并不意味着我们应该拥抱它并开始使用它

  • 扬尼克·马洛洛斯(Yannick Majoros)

    本文结尾处与asp.net和php的比较没有’感觉像个诚实的结论。提倡任何类型安全,成熟的语言和平台而不是php的混乱,还有比模板更多的原因。

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *