避免!= 空语句

我经常使用object != null来避免NullPointerException

有没有好的替代方法?

例如:

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

如果不知道对象是否为null ,则可以避免NullPointerException

请注意,接受的答案可能已过期,请参阅https://stackoverflow.com/a/2386013/12943以获取最新的方法。

答案

在我看来,这似乎是一个相当普遍的问题,初级和中级开发人员往往会在某个时候遇到这些问题:他们要么不知道,要么不信任他们所参与的合同,并且防御性地检查了 null。另外,在编写自己的代码时,他们倾向于依靠返回空值来表示某些内容,因此要求调用者检查空值。

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

  1. 如果为 null,则表示合同中的有效回复;和

  2. 无效的地方。

(2)容易。使用assert语句(断言)或允许失败(例如NullPointerException )。断言是 1.4 中新增的一个未被充分利用的 Java 功能。语法为:

assert <condition>

要么

assert <condition> : <object>

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

如果条件不成立,则assert语句将引发ErrorAssertionError )。默认情况下,Java 会忽略断言。您可以通过将选项-ea传递给 JVM 来启用断言。您可以启用和禁用单个类和程序包的断言。这意味着尽管我的测试几乎没有显示断言对性能的影响,但是您可以在开发和测试时使用断言来验证代码,并在生产环境中禁用它们。

在这种情况下,不使用断言是可以的,因为代码只会失败,这就是使用断言时会发生的情况。唯一的区别是,有了断言,它可能会更早地发生,以更有意义的方式出现,并可能带有额外的信息,这可以帮助您弄清楚为什么不期望它会发生。

(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()方法来说,抛出带有有意义的错误消息的 Exception 异常是完全适当的 - 特别是在这种情况下,您依赖于用户输入。对于 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);
        }
    }
}

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

基本上,您有@Nullable@NotNull

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

@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 ,他们添加了对其他@Nullable @NotNull实现的支持。

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

如果不允许使用空值

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

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

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

如果允许为 null

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

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

因此,我分支了,做了两件事完全不同。没有丑陋的代码片段,因为我确实需要根据数据做两件事。例如,我应该在输入上工作,还是应该计算一个好的默认值?


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

如果您显示了通常使用该惯用法的示例,则可能更容易为您提供示例。

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

Alex Miller 给出的示例如下所示:

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

?.表示仅在不为 null 的情况下取消引用左标识符,否则将表达式的其余部分评估为null 。有些人,例如 Java Posse 成员 Dick Wall 和Devoxx选民确实喜欢这个提议,但也有人反对,理由是它实际上将鼓励更多地使用null作为定点值。


更新:Project Coin下已提交了有关 Java 7 中的空安全运算符的正式建议 语法与上面的示例略有不同,但是是相同的概念。


更新:空安全运算符建议未纳入 Project Coin。因此,您不会在 Java 7 中看到此语法。

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

您可以将 IDE 配置为警告您有关潜在的空解除引用。例如,在 Eclipse 中,请参见首选项 > Java > 编译器 > 错误 / 警告 / 空分析

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

如果要在有意义的地方定义新的 API ,请使用 “ 选项模式” (功能语言可能很熟悉)。具有以下优点:

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

Java 8 具有内置的Optional类(推荐);对于早期版本,有一些库替代方案,例如GuavaOptionalFunctionalJavaOption 。但是,就像许多功能样式的模式一样,在 Java 中使用 Option(甚至 8)会产生很多样板,您可以使用不太冗长的 JVM 语言(例如 Scala 或 Xtend)来减少这种样板。

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

仅在这种情况下 -

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

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

如果foo不存在,将导致NullPointerException

如果像这样比较String ,则可以避免这种情况:

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

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

现在看这段代码,我们搜索的列表, Fruitfruits )对于给定的水果如:

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

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

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

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

Optional提供其他方便的方法,如orElse ,允许使用默认值,并ifPresent与工作lambda 表达式

我邀请您阅读本文(我写此答案的主要来源),其中很好地解释了NullPointerException (通常是空指针)以及Optional带来的(部分)解决方案: Java Optional Objects

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

例:

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

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

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

StringUtils 类只是众多类之一。公共领域中有很多不错的类可以进行空安全操作。

以下是一个示例,说明了在包含 apache 库(commons-lang-2.4.jar)时如何在 JAVA 中使用空 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(org.springframework.util.Assert)使用此静态类的示例

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
  • 如果您认为一个对象不应该为 null(或者它是一个 bug),请使用断言。
  • 如果您的方法不接受空参数,请在 javadoc 中说出来并使用断言。

仅当您要处理对象可能为 null 的情况时,才需要检查对象!= null。

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

我喜欢 “快速失败” 代码。问问自己 - 在参数为 null 的情况下,您是否正在做一些有用的事情?如果在这种情况下您对代码应该做的事情没有一个明确的答案... 即,一开始它永远不应该为 null,然后忽略它并允许抛出 NullPointerException。调用代码对 NPE 的意义与 IllegalArgumentException 一样,但对于开发人员来说,调试和了解如果抛出 NPE 会导致错误,比起尝试执行其他意外意外事件的代码要容易得多逻辑 - 最终导致应用程序无论如何都失败。