前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何避免 Java 中的“NullPointerException”

如何避免 Java 中的“NullPointerException”

作者头像
终码一生
发布2022-04-15 13:13:29
2.9K0
发布2022-04-15 13:13:29
举报
文章被收录于专栏:终码一生

1

最常见的异常

NullPointerException (NPE) 是 Java 中最常见的异常。此异常的原因是已知的,但在大多数情况下,开发人员更愿意忽略它并且不采取任何措施。我个人认为这种行为的原因如下:

  • 大多数开发人员在这里没有看到任何问题,并将所有 NPE 异常都视为开发人员的错。
  • 意识到这个设计问题的开发人员不知道如何解决它。

在本文中,我将解释这个问题的根源并提供解决该问题的方法。

2

问题的根源:Java 弱类型安全

你听说过编译类型安全吗?如果不在本文中,您可以了解它是什么以及编译时和类型安全之间的区别。

Java 提供了编译类型安全,它向开发人员保证他不能不匹配不同的变量类型。而且,如果您这样做了 - Java 甚至会在编译步骤中让他知道。在上面的示例中,我们尝试分配给 String 变量 Integer 值:

字符串变量

3

空引用破坏了 Java 类型的安全性

Java 在编译期间验证变量的类型和赋值的类型。那有什么问题呢?好吧,问题是 NULL 值。Null 值代表所有未初始化的对象。而且,只要可以初始化任何对象,就可以将 Null 值分配给任何类型。

因此,Java 允许下一个分配:

这里有什么问题?对象未初始化,因此它们指向空引用。看似很自然,实则是 万恶之源。

4

弱类型安全的后果

就 Java 而言,Null 和真实对象之间没有区别,它会导致不可能的操作,如下面的:不可能的操作。

所以,从编译器的角度来看,没有错。Null 属于 String 类型,Java 甚至不会打印警告。实际上,您甚至可以编译下一个代码:可编译的代码。

但是,一旦我们运行这个程序,它将失败并出现 NullPointerException:

空指针异常

5

NullPointerException 定义

NullPointerException 是一个运行时异常,当 Java 尝试调用真实对象上的任何方法但在运行时该对象引用 Null 引用时会引发该异常。您可以在本文中找到有关异常及其性质的更多详细信息。

6

为什么 NullPointerException 是最常见的异常?

开发人员是人类,总是习惯于忘记一些事情。因此,他们错过了:

  • 初始化对象
  • 验证对象

没有治愈人性的方法,也与它无关。避免NPE的实用方法是什么?让我们在下面回顾一个示例并尝试修复它。

7

NullPointerException

在我们的示例中,我们有一个带有地址字段的用户对象。潜在地,它们都可能为空。让我们看看如何避免 NullPointerException。

潜在的空指针异常

8

使用 != Null 检查避免

现在,让我们通过简单的检查来防止这个问题,而不是空检查:

简单检查

我们可以改进这个解决方案吗?

是的,我们可以使用 Optional。使用 map 函数,我们可以编写与前面的语句类似的等价物:

与简单的空检查相比,可选是否提供好处?是的,它确实。Optional 向我们保证我们在 ifPresent lambda 中使用的数据不为空。但是,如果用户或地址为空怎么办?然后, ifPresent 将被静默忽略。

而且,即使我们忘记使用 Optional 功能,这个想法也会突出显示 .get() ,提醒我们为设计提供空检查。

9

Optional 为什么不那么受欢迎?

可选功能在 Java 1.8 中发布,但并没有被广泛使用。有几个原因:

  • 它非常冗长并且污染了代码(我个人认为这是主要原因,Java 本身非常冗长,而使用 Optional 它变得非常大)。
  • 目前还不清楚,在所有 map/flatmap/ifpresent 背后,你可能会失去逻辑的意义。所以丑陋的空检查是简单明了的。
  • Optional 本身可能会导致开发人员创建更多的 NPE,例如通过使用 Optional.of(nullable)。

因此,出于上述原因,一些团队更喜欢使用空检查。为了避免任何 NPE 异常,用一堆测试来覆盖这样的逻辑。

10

空检查和可选它们是否解决了问题?

上面显示了两个“解决方案”,它们真的是解决方案吗?Null 检查与 Optional 一起用于相同目的 - 为可能为 null 的数据提供验证。另外,Optional 提醒开发者返回值可以为空。但是,总的来说,关键问题隐藏在人性中——忘记或错过潜在的无效场景。我们需要一个解决方案来指出开发人员在编译步骤中遗漏了什么。

11

@NotNull @Nullable

我们需要一个解决方案,它可以在编译步骤中读取我们的代码,并通知我们错过了潜在的 NPE 场景。为此,我们可以使用 Java 注释处理器。Java 注释处理器有很多用途,但也可以用于我们的案例。在本文中,您可以找到一个如何使用注释处理器来检查可变性的示例。

有几个与 NPE 问题相关的注释处理器。并非所有这些都是相同的,并且遵循完全不同的方法。

12

Lombok @NotNull 注解

Lombok @NotNull Annotation 用于生成可以阻止执行但仅在 Runtime 中的非空检查。所以它不符合我们的目的。很快,这个注解做了接下来的事情:

注解

13

使用检查器框架

Checker Framework提供了 @NonNull 和 @Nullable 注释以及可以识别潜在空检查的编译器处理器步骤。该框架可以通过强制开发人员指定 Nullability 来找到潜在的空值。因此,每当您返回某些内容时,您必须显式声明返回的结果可以是 Nullable 还是 NotNullable...让我们看下一个示例:

一个可能返回 Null 而不是 String 的简单方法:

现在,让我们使用我们的 Checker 框架,看看它是否愿意编译它:

使用检查器框架

不,一点都不快乐。它说我们返回一个可能为空的字符串,并且它没有用 @Nullable 注释标记。现在,让我们将其标记为@Nullable,并尝试使用它:

使用 @Nullable 注释

该框架会在该代码中发现任何错误吗?让我们再次运行编译检查:

运行编译

因此,它在第 19 行发现了一个潜在问题,我们尝试在 Nullable 字符串上调用 .length()。让我们使用 Null 检查和可选的 ifPresent 来修复它:

使用 Null 检查和可选的 ifPresent 修复问题

而且,编译后,我们得到了一个成功的构建:

构建成功

15

检查器框架限制

到目前为止,Checker Framework 显示出良好的结果并突出了潜在的 NPE。但是,代价是什么?现在我们有义务通过@Nullable 方法标记所有可能为Nullable 的方法。这似乎是一个强制性的步骤,我们无法避免。但是,这不是唯一的限制。让我们创建一个简单的类,其中包含两个字段,其中一个是我们标记为@NonNull 的字段:

具有两个字段的简单类

Checker Framework 会接受此代码吗?

Checker Framework 强制我们有一个初始化 id 值的构造函数,例如:

构造函数

因此,Framework 不仅识别了潜在的 NPE,还迫使我们遵循特定的要求或设计。其实这个要求很关键。而且,这是值得怀疑的。我们是否应该为了适应框架限制而牺牲这种开发灵活性?这是一个悬而未决的问题。要使用 Checker Framework,您可以在此处获取我的示例:

git clone https://github.com/isicju/checker_framework_example

要运行 Checker Framework,请运行以下命令:

代码语言:javascript
复制
mvn clean compile

检查器框架替代方案:Intellij Idea @NotNull 注释

Checker Framework 不是唯一的解决方案,Intellij Idea 提供了自己的注释 @NotNull 和 @Nullable 以及嵌入在 IDE 插件中。不幸的是,我还没有找到在 maven 编译步骤中添加它的方法。因此,如果存在,请在评论中告诉我,我会对其进行测试并将其添加到文章中。

16

总结

总结整篇文章,我建议如下:

  • 更喜欢 Optional 而不是传递 Null
  • 使用检查器框架

老实说,在实践中,Checker Framework 给您的开发带来了限制。如果我必须实现自己的解决方案并且它必须在生产中保持稳定,即使我必须摆脱 Lombok 甚至 Builder Pattern,我也会使用 Checker Framework。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 终码一生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档