如何在Java中避免检查null值?

4393

我使用 x != null 来避免 NullPointerException。有其他的替代方法吗?

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

142
鼓励使用空值会使代码更难理解,也更不可靠。 - Tom Hawtin - tackline
83
不使用null比这里大多数其他建议都更好。抛出异常,不要返回或允许null。顺便说一下,“assert”关键字是无用的,因为它默认情况下是禁用的。使用一个始终启用的失败机制。 - ianpojman
4
在高级代码中应避免使用null值。发明了null引用的Tony Hoare称其为“价值数十亿美元的错误”。在此处查看一些想法:http://www.softwaregeek.net/2016/04/how-to-avoid-null-checks-in-java.html。 - Andrii Polunin
3
似乎是Java 8中的静态方法:static Objects.isNull(Object o)。可在以下链接中找到具体文档:https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html - Robert R Evans
7
null最常见的误用是人们返回null而不是空集合。每次这样做都会让我发疯。停止使用null,你将生活在一个更美好的世界中。此外,通过使用"final"关键字来扩展你的代码,你将生活在一个更美好的世界中。 - Jack
显示剩余8条评论
68个回答

2844
这对我来说听起来像是初级到中级开发人员在某个时候都会遇到的一个相当普遍的问题:他们要么不知道,要么不信任他们参与的合同,并且为了防御性地过度检查 null。此外,在编写自己的代码时,他们倾向于依赖返回 null 来指示某些内容,从而需要调用者检查 null。
换句话说,有两种情况需要进行 null 检查:
1. 在合同方面,null 是有效的响应; 2. 在合同方面,null 不是有效的响应。
第二种情况很容易处理。从 Java 1.7 开始,您可以使用 Objects.requireNonNull(foo)。(如果您被困在早期版本中,则 assertions 可能是一个不错的替代方案。)
正确使用该方法的方式如下。该方法返回传递给它的对象,并在对象为 null 时抛出 NullPointerException。这意味着返回值始终是非空的。该方法主要用于验证参数。
public Foo(Bar bar) {
    this.bar = Objects.requireNonNull(bar);
}

如果对象为空,它也可以像断言一样使用,因为它会抛出异常。在这两种用法中,都可以添加消息,该消息将显示在异常中。以下是像断言一样使用并提供消息的示例。

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

通常,当一个值为null但不应该为null时,抛出特定的异常,如NullPointerException,比抛出更一般的异常,如AssertionError,更可取。这是Java库采用的方法;当参数不允许为null时,优先选择NullPointerException而不是IllegalArgumentException
(1)有点难。如果您无法控制所调用的代码,则无能为力。如果null是有效的响应,您必须进行检查。
然而,如果是您控制的代码(这通常是情况),那就是另一回事了。避免使用null作为响应。对于返回集合的方法,很容易:几乎总是返回空集合(或数组),而不是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()方法中抛出一个带有有意义的错误消息的异常可能是完全合适的,特别是在您依赖用户输入的情况下。与调用方法没有解释的简单NullPointerException相比,findAction方法抛出异常会更好。

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);
        }
    }
}

4
不错的回答,特别是提到了断言。我来自C语言并经常使用它们。至于Null对象,它们并不是万能的解决方案。我经常花费几个小时调试代码,结果发现是使用了Null对象,当应该使用Null指针异常(NPE)更清晰地指出问题时,Null对象什么也没做(或返回默认值)。因此,您将不得不执行“Null对象检查”而不是空检查,实际上失去了清晰度。现在,如果我无法避免Null,我只是清楚地记录它,就这样。 - Franz D.
this.bar = Objects.requireNonNull(bar);是使用这种方法的好处,因为你不会使用this.bar。这样,当传递无效的空对象时,您会得到异常,而不是在尝试使用它时。但是,它经常被用于这种方式: Objects.requireNonNull(getThing()).doThingStuff(); 在这里,对Objects.requireNonNull()的调用根本不改变行为,它无论如何都会抛出NPE。最好在保存对象以供以后使用时使用它。在我的第二个例子中,异常很有用。它有助于跟踪错误。 - MiguelMunoz
1
我们应该建议使用异常来控制流程吗?虽然从技术上讲,这是一个可行的解决方案,但程序员应该记住,异常只应用于捕获意外情况。在验证用户输入时,无效的输入最好不要导致抛出异常。 - Lee White

709
如果您使用(或计划使用)像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 注释


132
@NotNull@Nullable 以及其他空值注解是 JSR 305 的一部分。你可以使用它们来检测潜在问题,例如使用 FindBugs 工具。 - Jacek S
34
我觉得@NotNull@Nullable接口存放在com.sun.istack.internal包中很奇怪,这让我感到有点烦(我猜想是因为我会把com.sun与使用专有API的警告联系起来)。 - Jonik
21
JetBrains的IDE会导致代码可移植性降为零。在绑定到IDE层之前,我会三思而后行。就像Jacek S所说,它们无论如何都是JSR的一部分,我认为这个JSR应该是JSR303。 - Java Ka Baby
13
我认为使用自定义编译器并不是解决这个问题的可行方案。 - Shivan Dragon
70
注解的好处在于,@NotNull@Nullable等注解在源代码被不理解它们的系统构建时可以很好地降级。因此,实际上,代码不可移植的论点可能是无效的 - 如果您使用支持并理解这些注解的系统,则可以获得更严格的错误检查的附加好处;否则,您将得到较少的错误检查,但您的代码仍应该能够正确构建,并且运行程序的质量是相同的,因为这些注解在运行时不被强制执行。此外,所有编译器都是定制的。;-) - Armen Michaeli
显示剩余13条评论

351

如果不允许空值

如果您的方法被外部调用,请从以下类似的代码开始:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

在那个方法的剩余部分,你会知道object不是null。

如果这是一个内部方法(不是API的一部分),只需记录它不能为null即可。

例如:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

然而,如果你的方法只是简单地将一个值传递给下一个方法等等,这可能会变得麻烦。在这种情况下,你可能需要像上面那样检查参数。

如果允许空值

这取决于具体情况。我经常会做以下这样的事情:

if (object == null) {
  // something
} else {
  // something else
}

所以我会分支,做两件完全不同的事情。没有丑陋的代码片段,因为根据数据,我确实需要做两件不同的事情。例如,我应该处理输入还是计算一个良好的默认值?


我实际上很少使用习语“if (object != null && ...”。

如果您展示通常使用此习语的示例,则可能更容易理解。


101
抛出IllegalArgumentException有什么意义?我认为抛出NullPointerException会更清晰,如果您没有进行null检查,也会抛出该异常。我要么使用assert语句,要么就不做任何处理。 - Axel
24
除了 null 以外的每个值都可接受的可能性不大。您可能会遇到 IllegalArgumentException、OutOfRageException 等等异常情况。有时这很合理,但有时你会创建很多没有任何价值的异常类,这时只需使用 IllegalArgumentException 即可。将一个异常用于空输入,另一个用于其他情况是没有意义的。 - myplacedk
9
抛出新的非法参数异常:“object==null”。 - Thorbjørn Ravn Andersen
8
我同意在这些情况下,IllegalArgumentException比普通的NPE更好。我认为NPE表示代码中某处不安全地取消引用了一个恰好为空的表达式(假设满足了所有已知和声明的前提条件和不变量)。另一方面,非法参数异常则告诉我未满足某个众所周知的前提条件或不变量。 - luis.espinal
8
也就是说,NPE告诉我在该方法内(或其调用的方法中)存在尚未记录的空指针错误,或者存在我们未编码的空前提条件。非法参数异常告诉我们,在导致当前方法的调用链中,存在一个位于该方法“外部”的错误。遵循这种模式(当然要在合理范围内),可以更快地分析错误的根本原因(特别是那些棘手、交织的错误)。 - luis.espinal
显示剩余8条评论

256

哇,我们已经有57种不同的方式推荐“NullObject模式”,我几乎不想再添加另一个答案了,但我认为一些对这个问题感兴趣的人可能想知道,在Java 7中有一个提案添加了"null-safe handling"-一种用于if-not-equal-null逻辑的简化语法。

Alex Miller给出的示例如下:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  
?.的意思是,仅当左边的标识符不为null时才解引用它,否则将余下的表达式评估为null。一些人,例如Java Posse成员Dick Wall和Devoxx的选民非常喜欢这个提案,但也有反对意见,认为它实际上会鼓励更多使用null作为哨兵值。

更新:Java 7中关于空安全运算符的官方提案已经在Project Coin下提交。语法与上面的示例略有不同,但概念相同。


更新:空安全操作符提案未被纳入Project Coin。因此,在Java 7中您将看不到这种语法。


23
我认为这是错误的。应该有一种方法来指定一个特定的变量始终为非空。 - Thorbjørn Ravn Andersen
6
更新:该提案不会使Java7。请参阅http://blogs.sun.com/darcy/entry/project_coin_final_five。 - Boris Terzic
11
有趣的想法,但语法选择荒谬;我不想要一个到处都是问号的代码库。 - Rob
8
该运算符在Groovy中存在,因此那些想要使用它的人仍然可以选择该选项。 - Muhd
11
这是我见过最巧妙的想法,应该添加到每个合理的C语法语言中。我宁愿在各处“固定问号”,也不愿整天滚动屏幕或躲避“保护子句”。 - Victor - Reinstate Monica
显示剩余6条评论

211

如果不允许未定义的值:

您可以配置您的IDE,以警告您可能存在的空指针引用。例如在Eclipse中,查看首选项> Java>编译器>错误/警告/空分析

如果允许未定义的值:

如果您想定义一个新的API,其中未定义的值有意义,请使用Option Pattern(可能来自函数式语言)。它具有以下优点:

  • 明确在API中声明输入或输出是否存在。
  • 编译器强制处理“未定义”情况。
  • Option是一种monad,因此不需要冗长的空检查,只需使用map / foreach / getOrElse或类似的组合器安全地使用该值(示例)

Java 8内置了一个Optional类(推荐使用);对于早期版本,有一些库可以替代,例如GuavaOptionalFunctionalJavaOption。但是像许多函数式模式一样,在Java中使用Option(即使是8)会导致相当多的样板代码,您可以使用一种不那么冗长的JVM语言,例如Scala或Xtend来减少这种情况。

如果您必须处理可能返回null的API,在Java中无法做太多事情。Xtend和Groovy具有Elvis运算符 ?:空安全引用运算符 ?.,但请注意,如果引用为null,则返回null,因此它仅“延迟”了对null的正确处理。


23
确实,Option模式非常棒。Java中存在一些等效的选项。Guava包含了一个叫做Optional的简化版,省略了大部分函数式内容。在Haskell中,这个模式被称为Maybe。 - Ben Hardy
10
Java 8 将提供 Optional 类。 - Pierre Henry
1
...而且它目前还没有map和flatMap:http://download.java.net/jdk8/docs/api/java/util/Optional.html - thSoft
3
可选模式并没有解决任何问题,反而让可能为null的对象变成了两个。 - Boann
另外,看一下Functional Guava Extensions(fugue)。这个库也有它自己的Option。 - ZhekaKozlov
显示剩余2条评论

211

仅适用于此情况 -

在调用equals方法之前不检查变量是否为空(下面是一个字符串比较的例子):

if ( foo.equals("bar") ) {
 // ...
}

如果foo不存在,则会导致NullPointerException

您可以通过以下方式比较String来避免这种情况:


if ( "bar".equals(foo) ) {
 // ...
}

44
我同意 - 只在那种情况下。我不能忍受程序员把这个问题升级到不必要的程度,写if (null != myVar)……这样看起来很丑,也没有任何作用! - Alex Worden
21
这是一个通用的最佳实践的特定示例,可能是最常用的:如果你知道,总是执行 <已知非空对象>.equals(<可能为空的对象>)。 如果你知道契约并且这些方法可以处理 null 参数,则对于其他方法也适用,不仅限于 equals - Stef
9
这是我见过的第一个有意义的Yoda Conditions实例。 - Erin Drummond
7
空指针异常是有原因的。它们的出现是因为某个对象的值为空,而本不应该为空。程序员的职责是修复这个问题,而不是掩盖它。 - Oliver Watkins
这只是掩盖了问题。如果你后面还要使用 foo,该怎么办? 如果一个变量不应该为 null,但实际上却是...你的应用程序需要得到 NPE,这样作为开发者的你才能真正修复潜在的原因。 - Arnab Datta

185

随着Java 8的到来,新的java.util.Optional类解决了一些问题。至少可以说它提高了代码的可读性,在公共API的情况下可以让API的合同对客户端开发人员更加清晰。

它们的工作原理如下:

为给定类型(Fruit)创建一个可选对象作为方法的返回类型。它可以为空或包含一个Fruit对象:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

现在看一下这段代码,我们要在一个给定的Fruit实例列表fruits中搜索:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}
您可以使用map()操作符对可选对象执行计算或提取值。 orElse()允许您为缺失的值提供备用选项。
String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

当然,检查null/空值仍然是必要的,但至少开发人员意识到该值可能为空,并且忘记检查的风险有限。

在使用Optional构建API时,每当返回值可能为空时都使用Optional,并且仅在无法为空时返回普通对象(约定),客户端代码可能会放弃对简单对象返回值进行null检查...

当然,Optional也可以用作方法参数,这也许是在某些情况下比使用5或10个重载方法来指示可选参数更好的方式。

Optional提供了其他方便的方法,例如orElse允许使用默认值,以及与lambda表达式一起工作的ifPresent

我邀请您阅读这篇文章(我写这篇答案的主要来源),其中详细解释了NullPointerException(以及一般的空指针)问题,以及Optional带来的(部分)解决方案:Java Optional Objects


12
谷歌的Guava库有一个可选的Java 6+实现。 - Bradley Gottfried
19
强调使用Optional只有在ifPresent()的情况下并没有比普通空值检查带来更多的价值是非常重要的。它的核心价值在于作为一个单子类型,在map/flapMap函数链中可以使用,以实现类似Groovy中提到的Elvis操作符的结果。即使没有这种用法,我也发现orElse/orElseThrow语法非常有用。 - Cornel Masson
我真的从来没有理解为什么人们对这个样板代码感到如此高兴。https://dzone.com/articles/java-8-elvis-operator - Mike
6
为什么人们倾向于使用 if(optional.isPresent()){ optional.get(); } 而不是 optional.ifPresent(o -> { ...})? - Satyendra Kumar
1
除了 API 合同提示之外,这实际上只是为喜欢无休止地链接方法的函数式程序员提供服务。 - crush
显示剩余2条评论

134

根据您要检查的对象类型,您可以使用apache commons中的一些类,例如:apache commons langapache commons collections

示例:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

或者(取决于您需要检查的内容):
String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

StringUtils类只是众多类中的一个; commons中有许多优秀的类可以进行null安全操作。

以下是一个示例,展示了您在包含apache库(commons-lang-2.4.jar)时如何在JAVA中使用null验证。

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

如果你正在使用Spring框架,它的包中也有同样的功能库,请查看library(spring-2.4.6.jar)

示例演示如何使用Spring的静态类(org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");

5
你可以使用Apache Commons中更通用的版本,在方法开始时检查参数非常有用。Validate.notNull(object,“object must not be null”);http://commons.apache.org/lang/apidocs/org/apache/commons/lang/Validate.html - monojohnny
@monojohnny,Validate是否使用Assert语句?我问这个问题是因为Assert可能在JVM上被激活/停用,建议不要在生产中使用。 - TomasMolina
不这么认为 - 我相信如果验证失败,它只会抛出一个 RuntimeException。 - monojohnny

104
  • 如果你认为一个对象不应该为null(或者这是一个错误),请使用assert。
  • 如果你的方法不接受null参数,请在javadoc中说明并使用assert。

只有当你想处理对象可能为null的情况时,才需要检查对象!=null...

Java7中有一个提案,用于帮助处理null/notnull参数:http://tech.puredanger.com/java7/#jsr308


4
不要在生产代码中使用断言(assertions)。 - phil294

95

我是“快速失败”的代码粉丝。问问自己 - 如果参数为空,您是否在做有用的事情?如果您对代码在这种情况下应该执行什么没有明确的答案...即-它在第一次出现时就不应该为空,则忽略它并允许抛出NullPointerException。调用代码将对抛出NPE和IllegalArgumentException有同样的理解,但如果抛出NPE而不是您的代码尝试执行一些其他意外的容错逻辑,开发人员将更容易地调试和理解出了什么问题- 最终导致应用程序失败。


2
最好使用断言,即Contract.notNull(abc, "abc必须为非空值,在xyz期间是否加载失败?");- 这比执行if (abc!= null) {throw new RuntimeException...}更紧凑。 - ianpojman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接