java避免空指针异常_第2部分:在现代Java应用程序中避免空指针异常

java避免空指针异常

必须有更好的方法-现代null处理 (There’s got to be a better way — modern null handling)

In the previous post - part 1, we saw how in some cases null is a necessary evil and that there are right and wrong ways to use it. In this post, I will cover how you can best work safely with null values and discuss some of the solutions, we use in the Columna project to avoid errors and elegantly handle null.

在上一篇文章(第1部分)中,我们看到了在某些情况下null是必不可少的邪恶,并且有使用它的对与错方法。 在这篇文章中,我将介绍如何最好地使用null值安全地工作,并讨论一些解决方案,我们在Columna项目中使用了这些解决方案来避免错误并优雅地处理null。

在null上返回Optional (Return Optional over null)

While I hopefully convinced you in the previous post that returning null is correct in some cases. In this post, I am going to tell you that you shouldn’t if you can avoid it. Whenever you add a new method to the code base that may return nothing, you should strive to use the Optional type instead of null. The Optional type was introduced in Java 8 and is a generic container for any object. It was introduced to give developers a way to indicate that a method might return nothing, which you previously couldn’t (at least not without annotations — more on that below). There are two types of Optional objects: Those that hold a value and those that don’t. The former is created with the value to hold, whereas the latter is simply a static singleton. The typical use case has the following structure:

虽然我希望您在上一篇文章中说服您,在某些情况下返回null是正确的。 在这篇文章中,我将告诉您,如果可以避免,您就不应该这样做。 每当在代码库中添加可能不返回任何内容的新方法时,都应努力使用Optional类型而不是null。 Optional类型是Java 8中引入的,它是任何对象的通用容器。 引入它是为了为开发人员提供一种方法,以指示该方法可能不返回任何内容,而您以前无法返回任何内容(至少没有注释,更多内容请参见下文)。 可选对象有两种:持有值的对象和不持有值的对象。 前者是用要保存的值创建的,而后者只是一个静态单例。 典型的用例具有以下结构:

if (checkSomeCondition(someValue)){
return Optional.of(someValue);
} else {
return Optional.empty();
}

To see why an Optional-based method signature is superior, assume a method with the following signature:

要了解为什么基于可选的方法签名优越,请假定具有以下签名的方法:

public Admission getAdmission(Patient patient)

The signature only tells you that it returns an object of type Admission. As discussed above, null corresponds to every type. Unless we analyze the implementation of the method, we have no way to know if null is returned. In contrast, a method returning an Optional looks like this.

签名仅告诉您它返回类型为Admission的对象。 如上所述,null对应于每种类型。 除非我们分析该方法的实现,否则我们将无法知道是否返回null。 相反,返回Optional的方法看起来像这样。

public Optional<Admission> getAdmission(Patient patient)

Since it only makes sense to return an Optional, where an empty value must be returned, the method must return an empty value. Whenever you get an Optional from a method, you know that there’s a possibility of receiving nothing and must handle the value appropriately.

由于仅返回必须返回空值的Optional是有意义的,因此该方法必须返回空值。 每当从方法中获得Optional时,您就知道有可能什么也不接收,因此必须适当地处理该值。

A returned Optional value is handled similarly to null: You check if a value is provided, and if so, you do something with it. A call to a method that returns null typically looks like:

返回的Optional值的处理方式与null相似:您检查是否提供了值,如果提供,则对其进行处理。 调用返回null的方法通常如下所示:

Admission admission = getAdmission(Patient patient);
if (admission != null) {
// do stuff with admission data
}

With an Optional value the corresponding code would look like this:

使用Optional值,相应的代码如下所示:

Optional<Admission> admissionOptional = getAdmission(Patient patient);
if (admissionOptional.isPresent()) {
Admission admission = admissionOptional.get();
// do stuff with admission data
}

While it is easy to forget checking for null in the first example, an Optional type is safer as it makes it obvious that there may or may not be value provided. If you call get() on an empty Optional value, you’ll get an exception, just as you would by calling any method on a null value. You always need to call isPresent() followed by get() in the most basic use case, but the API offers multiple other alternatives, where a default value can be returned from the call if no value is present.

尽管在第一个示例中很容易忘记检查null,但是Optional类型更安全,因为它很明显可能会或可能不会提供值。 如果在一个空的Optional值上调用get(),则会得到一个异常,就像在null值上调用任何方法一样。 在最基本的用例中,您始终需要先调用isPresent()然后再调用get(),但是API提供了多种其他选择,如果不存在任何值,则可以从调用中返回默认值。

Note that the Optional type only really makes sense to use for return values. For instance, in the case below, assigning to Optional in the loop below has no benefits over null, in fact, using null for the initial assignment is more elegant and your IDE should have no problem detecting if you’re missing a null-check, when the variable is used in the same method in which it is declared.

请注意,仅将Optional类型用于返回值才有意义。 例如,在以下情况下,在下面的循环中将Optional分配给null不会带来任何好处,实际上,对初始分配使用null更为优雅,并且IDE可以毫无问题地检测是否缺少null检查,如果在与声明变量相同的方法中使用该变量。

Optional<Admission> admissionOptional = Optional.empty()for (Admission admission : admissions) {
if (admission.isActive()) {
admissionOptional= admission;
break;
}
}if (admissionOptional.isPresent()) {
// Do stuff
} else {
// Do something else
}

However, if we instead had a method that returned the active admission, we’d be in Optional territory:

但是,如果相反,我们有一个方法返回了主动入场,那么我们将处于Optional领域:

for (Admission admission : admissions) {
if (admission.isActive()) {
return Optional.of(admission);
}
}
return Optional.empty();

What about using Optional for parameters?

使用Optional作为参数呢?

SonarLint says no as it is better to provide an actual value than an Optional that may or may not have a value, as we would have to check for both of these inside the method as well as ensuring that somebody didn’t provide null instead. However, you shouldn’t mix null and Optional. It defeats the whole purpose of an Optional value.

SonarLint表示不可以,因为提供实际值比提供可能有或没有值的Optional更好,因为我们必须在方法内部检查这两个值,并确保有人不提供null。 但是,您不应混用null和Optional。 它破坏了Optional值的全部目的。

Don’t ever think about doing this or providing null as the argument for a parameter with an optional value.

永远不要考虑这样做或为具有可选值的参数提供null作为参数。

Optional<Object> optional = null; // BAD!

If you do something like this, you’ll also get an NPE.

如果您这样做,您还将获得NPE。

Optional<Object> optional = Optional.of(null);

If you have some value that possibly holds null and you want to convert it to an Optional, you need to call Optional.ofNullable() instead, and it will return an Optional-wrapped value if the argument is not null; otherwise, it returns an empty Optional. If you need to do the reverse operation, that is, convert a possibly empty optional into null or the value it holds, use orElse(null) that returns the provided argument if no value is present.

如果您有一些可能保留null的值,并且想要将其转换为Optional,则需要调用Optional.ofNullable(),如果参数不为null,它将返回Optional-wrapped值; 否则,它返回一个空的Optional。 如果需要执行相反的操作,即将可能为空的可选值转换为null或它包含的值,请使用orElse(null),如果不存在任何值,则返回提供的参数。

使用Nullable / Nonnull注释辅助IDE (Assist the IDE with Nullable/Nonnull annotations)

For most codebases not passing null around at all is a utopia. Another way to make both your intent clearer and to aid the type system in helping you is to use annotations for parameters and return values that indicate, where null is and isn’t expected or supposed to be used. Many frameworks enable this (see this thorough summary on SO), but the basic idea is the same: A parameter can be tagged either as Nonnull meaning that it is never expected to have a null value or as a Nullable value, meaning that this variable may hold a null-value. Fields and return values may similarly be tagged.

对于大多数代码库而言,根本不传递空值是乌托邦。 使您的意图更加清晰并帮助类型系统帮助您的另一种方法是对参数和返回值使用注释,这些注释和返回值指示在何处应该使用或不应该使用null。 许多框架都启用了此功能(请参阅SO的完整摘要 ),但是基本思想是相同的:可以将参数标记为Nonnull ,这意味着永远不要期望它具有null值,也可以将其标记为Nullable ,这意味着该变量可以包含一个空值。 可以类似地标记字段和返回值。

One example of one such method signature:

一种此类方法签名的示例:

@Nullable
public Admission getAdmission(@Nonnull Patient patient) {
...
}

The signature now reveals that the method is not intended to be called with a Patient-parameter with a null-value, and the IDE will give you a warning for doing so. We are also informed that the method might return null, and you will similarly get a warning when using the return value of the method.

现在,签名表明该方法不打算使用带有空值的Patient参数来调用,IDE会警告您这样做。 我们还被告知该方法可能返回null,并且使用该方法的返回值时,您同样会收到警告。

While new methods should use Optional over null in return values, extensive refactoring of existing methods is usually outside the scope of feature development. However, just throwing a tag on whenever you examine whether a value ever becomes null as part of writing new code is a helping hand to anybody, who uses the code in the future, and will help to prevent NPEs.

尽管新方法在返回值中应使用Optional而不是null,但是对现有方法进行广泛的重构通常不在功能开发的范围之内。 但是,只要您在检查值是否曾经为空时(在编写新代码的过程中)时都加上一个标签,便会对将来使用该代码的任何人有所帮助,并且将有助于防止NPE。

使用Objects.requireNonNull()防止在运行时出现空参数 (Use Objects.requireNonNull() to prevent null-parameters at run-time)

Annotations only matter at compile-time and don’t affect the behaviour of the running program. If you want to enforce at runtime that a method is not called with a null argument, you should use the Objects.requireNonNull(T obj, String message) method. Objects is a static utility class for performing basic operations like equals, hashCode and toString in a null-safe manner.

注释仅在编译时起作用,而不会影响正在运行的程序的行为。 如果要在运行时强制不使用空参数调用方法,则应使用Objects.requireNonNull(T obj,String message)方法。 对象是一个静态实用程序类,用于以null安全的方式执行基本操作,例如equals,hashCode和toString。

To defend a method against null arguments, pass the relevant parameter(s) to the requireNonNull() method along with a descriptive error-text. If the parameter is null, it results in an NPE with the error text. Otherwise, the method just returns the parameter. It is typically used in constructors, like below:

要针对空参数保护方法,请将相关参数与描述性错误文本一起传递给requireNonNull()方法。 如果参数为null,则将导致NPE带有错误文本。 否则,该方法仅返回参数。 它通常在构造函数中使用,如下所示:

public Foo(Bar bar, Baz baz, Qux qux) {
this.bar = Objects.requireNonNull(bar, “bar must not be null”);
this.baz = Objects.requireNonNull(baz, “baz must not be null”);
this.qux = qux; // qux is allowed to be null
}

Now you might ask yourself, if the purpose is to prevent a parameter from being null because it can unintentionally result in an NPE. Is throwing an NPE intentionally before that happens really any better?

现在您可能会问自己,目的是防止参数为空,因为它可能无意中导致NPE。 在此之前故意扔NPE真的更好吗?

It seems like the same thing. And from the users’ point of view, it will look the same — an error happens. But when diagnosing the cause of the problem later, the latter is much preferable. The problem with NPEs is that they don’t tell you what variable was null; they only provide a line number. In a line, where multiple methods are called on objects, it can be hard to diagnose which one is the culprit.

似乎是同一回事。 从用户的角度来看,它看起来是一样的-会发生错误。 但是,当稍后诊断问题的原因时,后者是更可取的。 NPE的问题在于它们不会告诉您什么变量为null。 他们只提供一个行号。 在一行中,在对象上调用多种方法的一行中,很难诊断哪个是罪魁祸首。

Say you get an NPE in a line of code like below:

假设您在以下代码行中获得了NPE:

computeStuff(bar.getValue(), baz.getAnotherValue(), qux.getSomeObject().getStuff());

Which variable was null? Both bar, baz and qux could have been null. If qux was not null, the object that qux returns could have been. You are going to spend some time investigating which of these scenarios are the most likely culprit.

哪个变量为空? bar,baz和qux都可能为null。 如果qux不为null,则可能是qux返回的对象。 您将花费一些时间来研究以下哪种情况最可能是罪魁祸首。

Throwing your own exception for an empty parameter value will immediately give away in log files where to look for an issue because your own error message gives it away.

将自己的异常抛出为空参数值将立即在日志文件中泄漏寻找问题的位置,因为您自己的错误消息会将其泄漏出去。

使用字符串 (Working with strings)

I frequently see bugs, where string-generating logic fails to consider null values and as a result, “null” appears in the middle of strings in the user interface. This sneaky behaviour is due to the fact that when concatenating strings in Java, an unexpected null value does not lead to an NPE as long as no methods are explicitly called on it. If we look under the hood of the JRE, it turns out that null-values are often explicitly handled and converted into “null” values. Whenever we’re using the “+” shorthand, a StringBuilder is actually created and used to create the joined value. This means that the right-hand values of the assignments of s3 and s4 are actually equivalent:

我经常看到一些错误,在这些错误中,字符串生成逻辑无法考虑空值,结果,“ null”出现在用户界面中的字符串中间。 这种偷偷摸摸的行为是由于以下事实:在Java中连接字符串时,只要未显式调用任何方法,意外的null值都不会导致NPE。 如果我们看一下JRE的内幕,事实证明,空值通常被显式处理并转换为“空”值。 每当我们使用“ +”速记时,实际上都会创建一个StringBuilder并将其用于创建联接值。 这意味着s3和s4赋值的右侧值实际上是等效的:

String s1 = “hello “;
String s2 = null;
String s3 = s1 + s2;
String s4 = new StringBuilder(String.valueOf(s1)).append(s2).toString();

Both will lead to a “hello null” string. Concatenation can also be performed using s1.concat(s2), which interestingly does result in an NPE whenever s2 is null. However, the concat method seems largely out of favour, even though it performs better than StringBuilder when concatenating only a few strings, so we should generally expect null-strings to fly under the radar until they appear in the UI.

两者都将导致“ hello null”字符串。 也可以使用s1.concat(s2)进行串联,有趣的是,只要s2为null,它就会生成NPE。 但是,concat方法似乎并不受欢迎,尽管它在连接几个字符串时比StringBuilder表现更好,因此,我们通常应该期望空字符串在雷达下飞行直到它们出现在UI中。

使用Objects类和StringUtils库简化样板代码 (Use Objects class and StringUtils library to simplify boilerplate code)

Often errors find a way into code-bases, when we don’t have a good mental model of what some code does or why it does so; maintainability is important. I often experience that string creation code is convoluted. For some reason, people tend to go wild with ternary expressions by putting them inside expressions or nesting ternary expressions in ternary expressions. It’s a very effective way to make simple logic much harder to read than it needs to be.

当我们对某些代码做什么或为什么这么做没有很好的思维模型时,错误通常会在代码库中找到出路。 可维护性很重要。 我经常遇到字符串创建代码复杂的问题。 由于某些原因,人们倾向于将三元表达式放在表达式内部或将三元表达式嵌套在三元表达式中,从而对三元表达式感到狂热。 这是使简单的逻辑比需要的难理解的一种非常有效的方法。

The ternary operator is often used to return a default value if a variable holds null, as in the example below, straight from the Columna code base

如果变量直接为Columna代码库中的变量,如以下示例所示,通常使用三元运算符返回默认值

String s = (form != null ? form : "-") + " " + (nameAndStrength != null ? nameAndStrength: "");

Admittedly, it is not the hardest code to analyze, but I think we can do a lot better. In C# you have a so-called null-coalescing operator, related to the ternary operator, that explicitly checks for a null value, as in:

诚然,它不是最难分析的代码,但我认为我们可以做得更好。 在C#中,您具有一个与三元运算符相关的所谓的空合并运算符,该运算符显式检查空值,如下所示:

String s = form ?? "-" + " " + nameAndStrength ?? "-";

Where the left-hand-side value of the operator is assigned if it is not null, otherwise the right-hand side gets used. While Java lacks an operator for doing this, we can use the built-in Objects class discussed above for the same purpose. The toString(Object o, String s) method of the Objects class returns the toString() value of the first object if the object isn’t null, otherwise, it returns the provided string value.

如果运算符的左侧值不为null,则在该位置赋值,否则将使用右侧。 尽管Java缺少用于执行此操作的运算符,但出于相同的目的,我们可以使用上面讨论的内置Objects类。 如果对象不为null,则Objects类的toString(Object o,String s)方法返回第一个对象的toString()值,否则,它返回提供的字符串值。

String hyphen = "";
String s = Objects.toString(form, hyphen) + " " + Objects.toString(nameAndStrength, hyphen);

Not as nice as the C# example, but in my opinion much more readable than the original example and thus a much smaller chance that errors are introduced when working with this code.

不如C#示例好,但我认为它比原始示例更具可读性,因此在使用此代码时引入错误的可能性要小得多。

将StringUtils用于null安全的String方法 (Use StringUtils for null-safe String methods)

The StringUtils library simplifies handling of null and the empty string for you — like the API says “Operations on String that are null safe.”. The library contains many common string operations with checks for null built-in. By using it, the code-base will become less cluttered as you don’t need to implement all the null-checking defensive boilerplate code, making your conditional statements easier to read. Your code-base will become more uniform when you use the library to standardize these checks and most importantly, your code will become safer in regard to null as you avoid the risk of making absent-minded mistakes by not implementing the checks yourself. In particular, aggressively guarding concatenation expressions with conditional statements based on the isBlank(s) method ensures that no unwanted “null” values (or empty strings for that matter) sneak into strings.

StringUtils库为您简化了null和空字符串的处理-就像API所说的“对String的操作都是安全的null”。 该库包含许多常见的字符串操作,并检查是否内置null。 通过使用它,代码库将变得更加混乱,因为您无需实现所有的空检查防御样板代码,从而使条件语句更易于阅读。 当您使用库对这些检查进行标准化时,您的代码库将变得更加统一,最重要的是,您的代码将变得更加安全 关于空值,因为您可以避免不亲自执行检查而犯错的风险。 特别是,使用基于isBlank(s)方法的条件语句积极地保护连接表达式,可确保不会有不想要的“空”值(或空字符串)潜入字符串中。

最后的话 (Final words)

To summarize and put it all together:

归纳总结:

  • Null has several valid uses; don’t be afraid of using it whenever it makes sense as part of the modelling of a given domain.

    Null有几种有效用法; 只要在给定域建模的一部分有意义时,就不要害怕使用它。
  • Minimize usage of null and don’t use it in ways where others don’t expect it — watch out at the interface between methods.

    尽量减少null的使用,并且不要以其他人不期望的方式使用它-留意方法之间的接口。
  • Don’t use null to implicitly indicate errors — be explicit and throw an exception.

    不要使用null隐式表示错误-明确表示并引发异常。
  • Use method overloading and the Builder pattern to avoid passing null-values in your method calls.

    使用方法重载和Builder模式可避免在方法调用中传递空值。
  • Never assign a Collection type variable to null — use Collections.emptyList(), emptySet(), emptyMap() to create empty data structures.

    切勿将Collection类型变量分配为null -使用Collections.emptyList(),emptySet(),emptyMap()创建空数据结构。
  • Use Optional over null when returning empty values to let callers know to expect empty values and provide proper handling of these.

    返回空值时,使用Optional over null可以使调用者知道期望空值并提供适当的处理方法。
  • When you can’t use Optional: Make your intention of handling/not handling null parameters and returning/not returning null in your methods explicit with NonNull/Nullable annotations.

    当您不能使用Optional时:使用NonNull / Nullable注释使处理/不处理null参数以及在方法中返回/不返回null的意图明确。
  • Use Objects.requireNonNull() to intentionally fail on unwanted null-parameters.

    使用Objects.requireNonNull()故意使不需要的空参数失败。
  • Be on the lookup for sneaky null-strings and use the Objects class and StringUtils to handle null elegantly.

    查找隐秘的null字符串,并使用Objects类和StringUtils优雅地处理null。

By Jens Christian B. MadsenSystems Developer, Systematic

作者:Jens Christian B. Madsen系统开发人员

About meJens Christian B. Madsen is a developer on the Columna clinical information system, holds master’s degrees from Aarhus University in Molecular biology and Computer Science and is a certified ethical hacker. He has contributed to books on such topics as Python, Computer Science and Linux. He enjoys heavy books, heavy metal and heavy weights.

关于我 Jens Christian B. Madsen是Columna临床信息系统的开发人员,拥有奥尔胡斯大学分子生物学和计算机科学专业的硕士学位,并且是一名经过认证的道德黑客。 他为Python,计算机科学和Linux等主题的书籍做出了贡献。 他喜欢沉重的书籍,沉重的金属和沉重的重物。

翻译自: https://medium.com/destinationaarhus-techblog/avoiding-null-pointer-exceptions-in-a-modern-java-application-c048ba872f7e

java避免空指针异常

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值