空指针(NullPointerException,NPE)是Java中最常见的异常。其原因虽然显而易见,但往往被开发人员忽视或未能及时采取行动。本文将详细讨论空指针问题的根源和相应的解决方案。空引用破坏Java类型安全Java提供了CompileTypeSafety来保证开发者不会错配不同的变量类型。在下面的示例中,我们试图将一个Integer值分配给某个String变量,Java会及时提醒您。虽然Java在编译时会校验变量的类型和赋值,但是由于NULL代表所有未初始化的对象,所以NULL可以赋值给任何类型(如下图所示),Java不会报错。例如,Java允许以下赋值:这些对象未经初始化就指向空引用,通常会产生Java类型的安全漏洞。如下代码片段所示,对于Java来说,Null可能和真正的对象没有区别,但是会造成一些不可实现的操作:同时,由于Null属于String类型,在编译时下面的代码片段,Java甚至不会有警告。但是,一旦我们运行程序代码,就会出现故障,提示如下空指针异常:空指针异常的定义空指针异常属于运行时异常。当Java试图调用一个真实对象的任何方法时,如果该对象在运行时调用了一个空引用(NullReference),就会抛出异常。您可以通过链接https://dzone.com/articles/java-exceptions-1找到有关异常及其根本原因的更多详细信息。由于各种原因,开发人员经常忘记初始化和验证对象。这通常是空指针异常的根本原因。下面我们根据上面的例子讨论一下如何修复NPE。解释空指针异常下面是一个带有地址字段的用户对象。他们可能都是空的。UseSimple!=NullChecktoavoidnullpointerexceptions下面是一个简单的检查(不是NullCheck)来防止这个问题的发生:作为改进,我们可以使用Optional并使用map函数来写类似前面的内容示例等效语句:与简单的NullCheck相比,Optional再次能够确保我们在ifPresentlambda中使用的数据不为空。这里又是指:如果User或Address确实为空,忽略ifPresent,即使我们忘记使用Optional的相关功能,也会高亮显示.get()的方式,提醒我们设计ProvideNullCheck。其实早在2014年,Optional就作为可选特性在Java1.8中发布了。但是至今没有被广泛使用,原因如下:由于Java本身就很冗长,Optional也变得冗长,容易影响代码的整体质量。对于各种map/flatmap/ifpresent逻辑,开发者更喜欢使用简单明了的NullCheck。Optional本身可能会导致开发者创建更多的NPE,例如使用Optional.of(nullable)。因此,鉴于以上原因,一些开发团队会更倾向于使用NullCheck,会使用一堆逻辑测试覆盖来避免潜在的NPE。NullCheck和Optional真的能解决问题吗?虽然上面讨论的NullCheck和Optional的目的是验证空数据。其中,Optional还可以提醒开发者返回值为空。然而,它们未能解决潜伏在开发人员头脑中的关键问题——编译步骤中的疏忽和遗漏。@NotNull@Nullable注释处理器有助于识别潜在的空值。因此,我们需要一种解决方案,能够在编译步骤中读取代码,并通知开发人员他们可能错过的潜在NPE场景。为此,我们可以使用功能丰富的Java注解处理器(AnnotationProcessors)。您可以通过访问链接查看可变性示例-https://www.javacodegeeks.com/2015/09/java-annotation-processors.html以了解如何使用注释处理器。目前,业界有几个与NPE问题相关的注解处理器。他们不遵循完全相同的方法。下面我们将重点介绍@NotNull和@Nullable注解提供工具。Lombok的@NotNull注解Lombok的@NotNull注解可用于生成仅在运行时(Runtime)阻塞执行的非空检查。以下代码片段显示了注释及其等效语句。CheckerFramework的@NonNull和@NullableAnnotationProcessorCheckerFramework提供了@NonNull和@Nullable注释,以及可以识别潜在空检查的编译处理器的步骤。该框架可以通过强制执行开发人员指定的可空性来检测潜在的空值。因此,您的代码必须显式声明返回结果为Nullable或NotNullable。让我们看一个可能返回Null而不是String的简单方法:现在,让我们使用检查器框架来验证是否可以完成编译。如您所见,它会抛出一个错误并返回一个疑似空字符串,该字符串未使用@Nullable注解进行标记。所以,让我们将它标记为@Nullable并尝试:如果我们再次运行编译检查,我们会得到以下错误信息:可见,检查器框架在第19行发现了一个潜在的问题,即:WeareonaNullablestring.length()被称为。接下来,我们使用NullCheck和Optional的ifPresent来修复它:编译后,我们会得到如下构建成功的信息:checker框架的limitsofar,checker框架向我们展示了良好的检查结果,并突出了PotentialNPEs。但是,代价是我们必须通过@Nullable方法标记所有可能为null的方法。为了突破这个强制限制,我们可以创建一个简单的类,它有两个字段,并将其中一个字段标记为@NonNull:这是通过检查器框架检查的结果:显然,检查器框架会强制我们构造一个构造函数,它初始化id值,eg:可见,checker框架不仅能够识别出潜在的NPE,还强制我们遵循特定的设计要求。这在一定程度上牺牲了框架开发的灵活性。如果您对此问题感兴趣,可以使用以下命令克隆我为您准备的示例:gitclonehttps://github.com/isicju/checker_framework_example要运行检查器框架,请使用以下命令:Mvncleancompile检查器框架的替代方案:IntellijIdea@NotNull注解当然,检查器框架不是唯一的解决方案。IntellijIdea还提供了自己的注解——@NotNull和@Nullable,它们嵌入在IDE的插件中。目前,我还没有找到在Maven编译步骤中添加它的方法。如果您有这方面的经验,欢迎补充意见。小结通过上面的讨论,我们可以看出,避免空指针异常的方式可以概括为:宁愿使用Optional,也不要传递Null使用检查器框架当然,检查器框架会给你的开发带来一些限制。在实践中,如果你一定要避免Lombok,甚至是BuilderPattern,我建议根据你生产环境的稳定性使用检查器框架。译者简介JulianChen,社区编辑,拥有十余年IT项目实施经验,善于把控内外部资源和风险,专注传播网络与信息安全方面的知识和经验;通过其他形式分享前沿技术和新知识;经常在线上和线下开展信息安全培训和讲座。【原标题】Java中的空指针异常:原因及避免方法(作者:DmitryEgorov)
