题 避免!= null语句


我用 object != null 要避免的很多 NullPointerException

有没有一个很好的替代品呢?

例如:

if (someobject != null) {
    someobject.doCalc();
}

这避免了 NullPointerException,当不知道对象是否是 null 或不。

请注意,接受的答案可能已过期,请参阅 https://stackoverflow.com/a/2386013/12943 对于更近期的方法。


3553


起源


@Shervin鼓励空值使代码不易理解,不太可靠。 - Tom Hawtin - tackline
猫王的经营者是 建议 但看起来它不会出现 Java 7。太糟糕了, ?。 ?:和?[]是令人难以置信的节省时间。 - Scott
不使用null优于此处的大多数其他建议。抛出异常,不返回或允许空值。 BTW - 'assert'关键字没用,因为它默认是禁用的。使用始终启用的故障机制 - ianpojman
这就是我现在使用Scala的原因之一。在Scala中,一切都不可为空。如果你想允许传递或返回“nothing”,那么你必须明确地使用Option [T]而不仅仅是T als参数或返回类型。 - Thekwasti
@thSoft确实,Scala太棒了 Option 类型因语言不愿意控制或不允许而受到损害 null。我在hackernews上提到了这个问题,然后被告知“没有人在Scala中使用null”。猜猜看,我已经找到了同事写的Scala中的空值。是的,他们“做错了”,必须接受教育,但事实仍然是语言的类型系统应该保护我免受这种情况,它不会:( - Andres F.


答案:


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

换句话说,有两个实例进行空检查:

  1. 如果null是合同方面的有效回复;和

  2. 哪里不是有效的回复。

(2)很容易。要么使用 assert 陈述(断言)或允许失败(例如, 空指针异常)。断言是1.4中添加的高度未充分利用的Java功能。语法是:

assert <condition>

要么

assert <condition> : <object>

哪里 <condition> 是一个布尔表达式 <object> 是一个对象 toString() 方法的输出将包含在错误中。

一个 assert 声明抛出一个 Error (AssertionError)如果条件不正确。默认情况下,Java会忽略断言。您可以通过传递选项来启用断言 -ea 到JVM。您可以为各个类和包启用和禁用断言。这意味着您可以在开发和测试时使用断言验证代码,并在生产环境中禁用它们,尽管我的测试表明,断言没有性能影响。

在这种情况下不使用断言是可以的,因为代码只会失败,如果使用断言将会发生这种情况。唯一的区别是,断言可能会更快地发生,以更有意义的方式发生,并且可能还有额外的信息,这可能会帮助您弄清楚如果您不期望它发生的原因。

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

如果它是你控制的代码(然而通常就是这种情况),那么这是一个不同的故事。避免使用空值作为响应。使用返回集合的方法,很容易:几乎一直返回空集合(或数组)而不是null。

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

public interface Action {
  void doSomething();
}

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

Parser采用原始用户输入并找到要做的事情,也许是在为某些事情实现命令行界面时。现在,如果没有适当的操作,您可以使合同返回null。这导致你正在谈论的空检查。

另一种解决方案是永远不会返回null而是使用 空对象模式

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方法来说,抛出一个Exception比使用一个简单的NullPointerException而没有解释的调用方法要好得多。

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

或者,如果您认为try / catch机制太难看,而不是Do Nothing,则默认操作应该向用户提供反馈。

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

2392



我不同意你对DO_NOTHING行动的陈述。如果find动作方法找不到动作,那么返回null是正确的做法。您已经“找到”代码中的一个操作,该操作未真正找到,这违反了该方法的原则,以找到可用的操作。 - MetroidFan2002
我同意null在Java中被过度使用,特别是对于列表。如果返回空列表/数组/集合而不是null,那么很多api会更好。很多时候,在应该抛出异常的地方使用null。如果解析器无法解析,则应抛出异常。 - Laplie Anderson
这里的后一个例子是IIRC,Null对象设计模式。 - Steven Evers
@Cshah(和MetroidFan2002)。很简单,把它放到合同中然后很明显,一个未找到的返回动作将什么都不做。如果这是调用者的重要信息,那么提供一种方法来发现它是一个未找到的动作(即提供一种检查结果是否为DO_NOTHING对象的方法)。或者,如果通常应该找到操作,那么您仍然不应该返回null,而是抛出一个特别指示该条件的异常 - 这仍然会产生更好的代码。如果需要,提供一个单独的方法返回布尔值以检查操作是否存在。 - Kevin Brock
简洁不等于质量代码。对不起,你这么认为。您的代码隐藏了错误有利的情况。 - gshauger


如果您使用(或计划使用)Java IDE JetBrains IntelliJ IDEA,Eclipse或Netbeans或像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注释


518



@NotNull, @Nullable 和其他nullness注释是其中的一部分 JSR 305。您也可以使用它们来检测类似工具的潜在问题 FindBugs的。 - Jacek S
我觉得这很奇怪 @NotNull& @Nullable 接口存在于包中 com.sun.istack.internal。 (我想我将com.sun与关于使用专有API的警告联系起来。) - Jonik
使用jetbrains进行代码可移植性为null。在关注ide级别之前,我会三思而后行(正方形)。就像Jacek S说的那样,他们是JSR的一部分,我认为顺便说一下,这是JSR303。 - Java Ka Baby
我真的不认为使用自定义编译器是解决此问题的可行方案。 - Shivan Dragon
关于注释的好处,哪个 @NotNull 和 @Nullable 当源代码由不理解它们的系统构建时,它们会很好地降级。因此,实际上,代码不可移植的论点可能是无效的 - 如果您使用支持和理解这些注释的系统,您将获得更严格的错误检查的额外好处,否则您会得到更少但是您的代码仍然应该构建正常,运行程序的质量是相同的,因为无论如何这些注释都没有在运行时强制执行。此外,所有编译器都是自定义的;-) - amn


如果不允许空值

如果您的方法是外部调用的,请从以下内容开始:

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

然后,在该方法的其余部分,你会知道 object 不是空的。

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

例:

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

但是,如果您的方法只是传递了值,并且下一个方法将其传递给它,则可能会出现问题。在这种情况下,您可能需要检查上面的参数。

如果允许null

这真的取决于。如果发现我经常做这样的事情:

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

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


我真的很少使用这个成语“if (object != null && ...”。

如果您显示通常使用成语的示例,则可能更容易为您提供示例。


282



抛出IllegalArgumentException有什么意义?我认为NullPointerException会更清楚,如果你不自己进行空检查,也会抛出。我要么使用断言,要么根本不使用。 - Axel
除null之外的所有其他值都不可能是可接受的。你可能有IllegalArgumentException,OutOfRageException等等。有时这是有道理的。其他时候你最终创建了许多不添加任何值的异常类,那么你只需使用IllegalArgumentException。对于null-input有一个例外是没有意义的,而另一个例外则没有意义。 - myplacedk
是的,我同意失败 - 快速原则,但在上面给出的例子中,值不传递,而是应该调用方法的对象。所以它失败同样快,并且添加一个空检查只是为了抛出一个异常,无论如何在同一时间和地点抛出它似乎不会使调试变得更容易。 - Axel
一个安全漏洞? JDK充满了这样的代码。如果您不希望用户看到堆栈跟踪,则只需禁用它们。没有人暗示行为没有记录。 MySQL是用C编写的,其中解除引用空指针是未定义的行为,就像抛出异常一样。 - fgb
throw new IllegalArgumentException("object==null") - Thorbjørn Ravn Andersen


哇,当我们有57种不同的推荐方法时,我几乎不想添加另一个答案 NullObject pattern,但我认为有些人对这个问题感兴趣,可能想知道Java 7上有一个提议要添加 “无效安全处理” - if-not-equal-null逻辑的简化语法。

Alex Miller给出的例子如下:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

?. 表示仅在左标识符为空时才取消引用,否则将表达式的其余部分计算为 null。有些人,像Java Posse成员Dick Wall和 Devoxx的选民 真的很喜欢这个提议,但也存在反对意见,理由是它实际上会鼓励更多地使用它 null 作为哨兵价值。


更新: 一个 官方提案 对于Java 7中的null安全运算符已经提交 项目硬币。 语法与上面的示例略有不同,但它是相同的概念。


更新: 无效的运营商提案没有进入项目硬币。因此,您将不会在Java 7中看到此语法。


215



我认为这是错误的。应该有一种方法来指定给定变量始终为非null。 - Thorbjørn Ravn Andersen
更新:提案不会制作Java7。看到 blogs.sun.com/darcy/entry/project_coin_final_five 。 - Boris Terzic
有趣的想法,但语法的选择是荒谬的;我不希望在每个关节中都插入充满问号的代码库。 - Rob
该运算符存在于 Groovy的所以那些想要使用它的人仍然可以选择。 - Muhd
这是我见过的最巧妙的想法。它应该添加到C语法的每种合理语言中。我宁愿在任何地方“钉上问号”而不是滚动屏幕线或整天躲避“警卫条款”。 - Victor


如果不允许使用未定义的值:

您可以配置IDE以警告您可能存在空取消引用。例如。在Eclipse中,请参阅 首选项> Java>编译器>错误/警告/空分析

如果允许未定义的值:

如果要定义一个新的API,其中未定义的值是有意义的, 使用 选项模式 (可能熟悉函数式语言)。它具有以下优点:

  • API中明确说明输入或输出是否存在。
  • 编译器强制您处理“未定义”的情况。
  • 选项是monad,所以不需要详细的空检查,只需使用map / foreach / getOrElse或类似的组合器来安全地使用该值 (例)

Java 8有一个内置的 Optional 上课(推荐);例如,对于早期版本,有库替代品 番石榴Optional 要么 FunctionalJavaOption。但是,与许多功能样式模式一样,在Java中使用Option(甚至8)会产生相当多的样板,使用较简洁的JVM语言可以减少这种样板,例如: Scala或Xtend。

如果必须处理可能返回null的API,你在Java中做不了多少。 Xtend和Groovy有 猫王经营者  ?: 和 零安全解除引用运算符  ?.,但请注意,如果是空引用,则返回null,因此它只是“延迟”对null的正确处理。


177



实际上,Option模式非常棒。存在一些Java等价物。 Guava包含一个名为Optional的限制版本,它遗漏了大部分功能。在Haskell中,这种模式称为Maybe。 - Ben Hardy
Java 8中将提供Optional类 - Pierre Henry
......它还没有(还)有map或flatMap: download.java.net/jdk8/docs/api/java/util/Optional.html - thSoft
Optional模式没有解决任何问题;而不是一个潜在的空对象,现在你有两个。 - Boann
@Boann,如果小心使用,你可以解决所有NPE问题。如果没有,那么我猜有一个“使用”问题。 - Louis F.


仅限于这种情况 -

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

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

会导致一个 NullPointerException 如果 foo 不存在。

如果你比较你的,你可以避免这种情况 String是这样的:

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

160



我同意 - 只是在那种情况下。我不能忍受程序员已经把它带到下一个不必要的级别并写if(null!= myVar)...只是看起来很丑我没有用! - Alex Worden
这是一个特殊的例子,可能是最常用的一般做法:如果你知道,总是这样做 <object that you know that is not null>.equals(<object that might be null>);。它适用于除以外的其他方法 equals 如果你知道合同,那些方法可以处理 null 参数。 - Stef
这是我见过的第一个例子 尤达的条件 这实际上是有道理的 - Erin Drummond
出于某种原因抛出NullPointerExceptions。它们被抛出,因为一个对象在它不应该的地方是null。程序员的工作是修复这个,而不是隐藏问题。 - Oliver Watkins
Try-Catch-DoNothing隐藏了这个问题,这是解决语言中缺糖问题的有效方法。 - echox


随着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)对于给定的Fruit实例:

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 / empty值仍然是必要的,但至少开发人员意识到该值可能是空的并且忘记检查的风险是有限的。

在从头开始构建的API中使用 Optional 每当返回值可能为空时,只有当它不能返回时才返回普通对象 null (约定),客户端代码可能会放弃对简单对象返回值的空检查...

当然 Optional 也可以用作方法参数,在某些情况下,可能是指示可选参数而不是5或10重载方法的更好方法。

Optional 提供其他方便的方法,如 orElse 允许使用默认值,和 ifPresent 适用于 lambda表达式

我邀请你阅读这篇文章(我写这个答案的主要来源) NullPointerException (以及一般的空指针)问题以及由此带来的(部分)解决方案 Optional 很好解释: Java可选对象


135



谷歌的番石榴对Java 6+有一个可选的实施方案。 - Bradley Gottfried
它的 非常 重要的是要强调只在ifPresent()中使用Optional 不 在正常的空值检查之上添加很多值。它的核心价值在于它是一个可以在map / flapMap的函数链中使用的monad,它实现了类似于其他地方提到的Groovy中的Elvis运算符的结果。即使没有这种用法,我发现orElse / orElseThrow语法也非常有用。 - Cornel Masson
这个博客在Optional上有一个很好的条目 winterbe.com/posts/2015/03/15/avoid-null-checks-in-java - JohnC
我真的不明白为什么人们对这个样板代码如此满意 dzone.com/articles/java-8-elvis-operator - Mykhaylo Adamovych
为什么人们倾向于这样做 if(optional.isPresent()){ optional.get(); } 代替 optional.ifPresent(o -> { ...}) - Satyendra Kumar


根据您检查的对象类型,您可以使用apache公共中的某些类,例如: apache commons lang 和 apache commons集合

例:

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

或(取决于您需要检查的内容):

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

StringUtils类只是其中之一;在公共场所中有很多优秀的类可以安全操作。

下面是一个如何在包含apache库(commons-lang-2.4.jar)时在JAVA中使用null vallidation的示例

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

如果您使用的是Spring,Spring在其包中也具有相同的功能,请参阅library(spring-2.4.6.jar)

有关如何从spring使用此静态classf的示例(org.springframework.util.Assert)

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

113



您也可以使用Apache Commons中更通用的版本,在检查我发现的参数的方法开始时非常有用。 Validate.notNull(object,“object must not null”); commons.apache.org/lang/apidocs/org/apache/commons/lang/... - monojohnny
@monojohnny确实使用Assert语句进入?我问,因为Assert可能在JVM上激活/停用,建议不要在生产中使用。 - Kurapika
不要这么认为 - 我认为如果验证失败,它只会抛出RuntimeException - monojohnny


  • 如果你认为一个对象不应该为null(或者它是一个bug),请使用一个断言。
  • 如果你的方法不接受null params在javadoc中说它并使用断言。

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

有人建议在Java7中添加新的注释来帮助null / notnull参数: http://tech.puredanger.com/java7/#jsr308


87



没有, 不要在生产代码中使用断言。 - Blauhirn


我是“快速失败”代码的粉丝。问问自己 - 在参数为null的情况下,你是否正在做一些有用的事情?如果你没有明确的答案,你的代码在这种情况下应该做些什么...... I.e。它首先应该永远不为null,然后忽略它并允许抛出NullPointerException。调用代码与IllegalArgumentException一样具有NPE意义,但是如果抛出NPE而不是代码试图执行其他意外的意外事件,开发人员将更容易调试并理解出现了什么问题。逻辑 - 最终导致应用程序失败。


80



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


有时,您有一些方法可以对其参数进行操作,从而定义对称操作:

a.f(b); <-> b.f(a);

如果你知道b永远不能为空,你可以交换它。它对equals最有用: 代替 foo.equals("bar"); 更好 "bar".equals(foo);


69



但是你必须假设 equals (可以是任何方法)将正确处理null。实际上,所有这一切都是将责任转嫁给其他人(或其他方法)。 - Supericy
@Supericy基本上是的,但是 equals (或任何方法)必须检查 null 无论如何。或明确说明它没有。 - Angelo Fuchs