在 Java 中避免 NullPointerException

新手上路,请多包涵

我使用 x != null 来避免 NullPointerException 。有替代方案吗?

 if (x != null) {
    // ...
}

原文由 Goran Martinic 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 612
2 个回答

对我来说,这听起来像是初级到中级开发人员在某些时候往往会面临的一个相当普遍的问题:他们要么不知道,要么不信任他们参与的合约,并且防御性地过度检查空值。此外,在编写自己的代码时,他们倾向于依靠返回空值来指示某些内容,因此需要调用者检查空值。

换句话说,有两种情况会出现空值检查:

  1. 如果 null 是合同条款中的有效响应;和

  2. 如果它不是有效的响应。

(2) 容易。从 Java 1.7 开始,您可以使用 Objects.requireNonNull(foo) 。 (如果您坚持使用以前的版本,那么 assert 离子 可能是一个不错的选择。)

这种方法的“正确”用法如下所示。该方法返回传递给它的对象,如果对象为空,则抛出 NullPointerException 。这意味着返回的值总是非空的。该方法主要用于验证参数。

 public Foo(Bar bar) {
    this.bar = Objects.requireNonNull(bar);
}

它也可以像 assert 离子一样使用,因为如果对象为空,它会引发异常。在这两种用途中,都可以添加一条消息,该消息将显示在异常中。下面是像断言一样使用它并提供消息。

 Objects.requireNonNull(someobject, "if someobject is null then something is wrong");
someobject.doCalc();

通常抛出一个特定的异常,如 NullPointerException 当值为 null 但不应该有利于抛出更一般的异常,如 AssertionError 。这是 Java 库采用的方法;当参数不允许为空时,支持 NullPointerException 而不是 IllegalArgumentException

(1) 有点难。如果您无法控制所调用的代码,那么您将陷入困境。如果 null 是有效响应,则必须检查它。

但是,如果它是您确实控制的代码(通常是这种情况),那么情况就不同了。避免使用空值作为响应。使用返回集合的方法很容易:几乎总是返回空集合(或数组)而不是空值。

对于非收藏品,可能会更难。以此为例:如果您有这些接口:

 public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

Parser 获取原始用户输入并找到要做的事情,也许如果您正在为某事实现命令行界面。现在,如果没有适当的操作,您可能会制定返回 null 的合同。这会导致您正在谈论的空值检查。

另一种解决方案是永远不要返回 null 而是使用 Null Object 模式

 public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

相比:

 Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

ParserFactory.getParser().findAction(someInput).doSomething();

这是一个更好的设计,因为它导致更简洁的代码。

也就是说,findAction() 方法抛出一个带有有意义的错误消息的异常可能是完全合适的——尤其是在这种依赖用户输入的情况下。 findAction 方法抛出一个异常要比调用方法用一个简单的 NullPointerException 炸毁而没有任何解释要好得多。

 try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

或者,如果您认为 try/catch 机制太丑陋,而不是什么都不做,您的默认操作应该向用户提供反馈。

 public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}

原文由 cletus 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果您使用(或计划使用) JetBrains IntelliJ IDEAEclipseNetbeans 等 Java IDE 或 findbugs 等工具,则可以使用注释来解决此问题。

基本上,你有 @Nullable@NotNull

您可以在方法和参数中使用,如下所示:

 @NotNull public static String helloWorld() {
    return "Hello World";
}

要么

@Nullable public static String helloWorld() {
    return "Hello World";
}

第二个示例无法编译(在 IntelliJ IDEA 中)。

当您在另一段代码中使用第一个 helloWorld() 函数时:

 public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

现在 IntelliJ IDEA 编译器会告诉您检查没有用,因为 helloWorld() 函数永远不会返回 null

使用参数

void someMethod(@NotNull someParameter) { }

如果你写这样的东西:

 someMethod(null);

这不会编译。

最后一个例子使用 @Nullable

 @Nullable iWantToDestroyEverything() { return null; }

这样做

iWantToDestroyEverything().something();

您可以确定这不会发生。 :)

这是让编译器检查比平时更多的东西并使您的合同更强大的好方法。不幸的是,并非所有编译器都支持它。

在 IntelliJ IDEA 10.5 及更高版本中,他们添加了对任何其他 @Nullable @NotNull 实现的支持。

请参阅博客文章 _更灵活和可配置的@Nullable/@NotNull 注释_。

原文由 Luca Molteni 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题