Java:如何有效地检查空指针

有一些模式可用于检查是否已为方法的参数赋值。null

首先是经典的。这在自制代码中很常见,并且显而易见。

public void method1(String arg) {
  if (arg == null) {
    throw new NullPointerException("arg");
  }
}

其次,您可以使用现有的框架。该代码看起来更好一些,因为它只占用一行。缺点是它可能会调用另一个方法,这可能会使代码运行速度变慢,具体取决于编译器。

public void method2(String arg) {
  Assert.notNull(arg, "arg");
}

第三,您可以尝试调用一个对对象没有副作用的方法。乍一看,这可能看起来很奇怪,但它的令牌比上述版本少。

public void method3(String arg) {
  arg.getClass();
}

我还没有看到广泛使用的第三种模式,感觉好像是我自己发明的。我喜欢它的简短性,并且因为编译器很有可能完全优化它或将其转换为单个机器指令。我还使用行号信息编译我的代码,因此如果抛出a,我可以将其追溯到确切的变量,因为我每行只有一个这样的检查。NullPointerException

您更喜欢哪种支票,为什么?


答案 1

方法#3:很聪明,但除非这个成语被广泛采用,否则我更喜欢更清晰,更详细的方法,而不是保存几个字符。我是一个“一次写,读很多”的程序员。arg.getClass();

其他方法是自我记录:您可以使用一条日志消息来阐明发生了什么 - 此日志消息在读取代码时以及在运行时使用。就目前而言,它不是自我记录的。您可以使用注释至少向代码的审阅者澄清:arg.getClass()

arg.getClass(); // null check

但是,您仍然没有机会像使用其他方法那样将特定消息放入运行时中。


方法 #1 与 #2(空检查+NPE/IAE 与断言):我尝试遵循这样的准则:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

  • 使用断言检查私有方法上的参数
    assert param > 0;

  • 使用空值检查 + 非法参数异常检查公共方法上的参数
    if (param == null) throw new IllegalArgumentException("param cannot be null");

  • 在需要时使用空检查 + 空值指针异常
    if (getChild() == null) throw new NullPointerException("node must have children");


但是,由于这个问题可能是关于最有效地捕获潜在问题,那么我必须提到我的首选处理方法是使用静态分析,例如类型注释(例如)a la JSR-305。我最喜欢的检查工具是:nullnull@NonNull

Checker 框架:
Java https://checkerframework.org/manual/#checker-guarantees
自定义可插拔类型

如果这是我的项目(例如,不是具有公共API的库),并且如果我可以在整个过程中使用Checker框架:

  • 我可以在API中更清楚地记录我的意图(例如,这个参数可能不是null(默认值),但这个参数可能是null(;方法可能返回null;等等)。这个注释就在声明中,而不是在Javadoc中更远的地方,所以更有可能被维护。@Nullable

  • 静态分析比任何运行时检查都更有效

  • 静态分析将提前标记潜在的逻辑缺陷(例如,我试图将一个可能为null的变量传递给一个只接受非null参数的方法),而不是依赖于运行时发生的问题。

另一个好处是,该工具允许我将注释放在注释中(例如'/@Nullable/),因此我的库代码可以与类型注释项目和非类型注释项目兼容(不是说我有任何这些项目)。


如果链接再次失效,以下是GeoTools开发人员指南中的部分:

http://data.opengeo.org/GEOT-290810-1755-708.pdf

5.1.7 使用断言、非法论证异常和 NPE

几年来,Java语言已经提供了一个 assert 关键字。此关键字可用于执行仅调试检查。虽然此工具有多种用途,但常见的用途是检查私有(非公共)方法上的方法参数。其他用途是后置条件和不变量。

参考:使用断言进行编程

前提条件(如私有方法中的参数检查)通常是断言的简单目标。后置条件和不变量有时不那么直截了当,但更有价值,因为非平凡条件有更多的风险被打破。

  • 示例 1:在引用模块中进行地图投影后,断言将执行反向地图投影,并使用原始点(后置条件)检查结果。
  • 示例 2:在 DirectPosition.equals(Object) 实现中,如果结果为 true,则断言可确保 hashCode() 与 Object 协定的要求相同。

使用 Assert 检查私有方法上的参数

private double scale( int scaleDenominator ){
 assert scaleDenominator > 0;
 return 1 / (double) scaleDenominator;
}

您可以使用以下命令行参数启用断言:

java -ea MyApp

您可以使用以下命令行参数仅打开 GeoTools 断言:

java -ea:org.geotools MyApp

您可以禁用特定包的断言,如下所示:

java -ea:org.geotools -da:org.geotools.referencing MyApp

使用非法参数异常检查公共方法上的参数

严格不鼓励在公共方法上使用断言;因为报告的错误是在客户端代码中犯下的 - 诚实地告诉他们,当他们搞砸了时,提前告诉他们一个非法的论据。

public double toScale( int scaleDenominator ){
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}

在需要时使用 NullPointerException

如果可能的话,执行你自己的空检查;抛出一个 IllegalArgumentException 或 NullPointerException,其中包含有关出错的详细信息。

public double toScale( Integer scaleDenominator ){
 if( scaleDenominator == null ){
 throw new NullPointerException( "scaleDenominator must be provided");
 }
 if( scaleDenominator > 0 ){
 throw new IllegalArgumentException( "scaleDenominator must be greater than 0");
 }
 return 1 / (double) scaleDenominator;
}

答案 2

你是不是过早地优化了一个biiiiiiiiiiiii!?

我只使用第一个。它清晰简洁。

我很少使用Java,但我认为有一种方法可以让Assert仅在调试版本上运行,所以这将是一个禁忌。

第三个给了我毛骨悚然的感觉,我想如果我在代码中看到它,我会立即诉诸暴力。完全不清楚它在做什么。


推荐