关于Java:避免!=空语句

Avoiding != null statements

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

有没有更好的选择?

例如:

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

当不知道对象是否为null时,避免使用NullPointerException

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


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

换言之,有两种情况会出现空检查:

  • 如果"无效"是合同条款中的有效答复;以及

  • 如果它不是有效的响应。

  • (2)容易。使用assert语句(断言)或allow failure(例如nullpointerException)。断言是在1.4中添加的一个高度未充分使用的Java特征。语法是:

    1
    assert <condition>

    1
    assert <condition> : <object>

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

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

    在这种情况下不使用断言是可以的,因为代码只会失败,如果使用断言,就会发生这种情况。唯一的区别是,对于断言,它可能以更有意义的方式发生得更快,而且可能会有额外的信息,这可能帮助您了解如果您没有预料到它会发生的原因。

    (1)有点难。如果你无法控制你所呼叫的代码,那么你就被卡住了。如果空是有效的响应,则必须检查它。

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

    对于非集合,这可能更难。举个例子:如果您有这些接口:

    1
    2
    3
    4
    5
    6
    7
    public interface Action {
      void doSomething();
    }

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

    解析器获取原始用户输入并找到要做的事情,如果您正在为某件事情实现命令行接口的话。现在,如果没有适当的操作,您可以使契约返回空值。这导致了你所说的无效检查。

    另一种解决方案是从不返回空值,而是使用空对象模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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;
        }
      }
    }

    比较:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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();
    }

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

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

    也就是说,对于findaction()方法来说,抛出带有有意义的错误消息的异常可能是完全合适的,特别是在您依赖用户输入的情况下。对于findaction方法来说,引发异常比调用方法使用简单的nullPointerException而不进行解释要好得多。

    1
    2
    3
    4
    5
    try {
        ParserFactory.getParser().findAction(someInput).doSomething();
    } catch(ActionNotFoundException anfe) {
        userConsole.err(anfe.getMessage());
    }

    或者,如果您认为尝试/捕获机制过于丑陋,而不是什么都不做,那么您的默认操作应该向用户提供反馈。

    1
    2
    3
    4
    5
    6
    7
    8
    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);
            }
        }
    }


    如果您使用(或计划使用)JaveIDE,如JETBEEL ITELLIJ想法、Eclipse或NETBeBes或像FunBug这样的工具,那么您可以使用注释来解决这个问题。

    基本上,你有@Nullable@NotNull

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

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

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

    第二个例子不会编译(在intellij思想中)。

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

    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args)
    {
        String result = helloWorld();
        if(result != null) {
            System.out.println(result);
        }
    }

    现在Intellij IDEA编译器会告诉您检查是无用的,因为helloWorld()函数永远不会返回null

    使用参数

    1
    void someMethod(@NotNull someParameter) { }

    如果你写的是:

    1
    someMethod(null);

    这无法编译。

    最后一个使用@Nullable的示例

    1
    @Nullable iWantToDestroyEverything() { return null; }

    这样做

    1
    iWantToDestroyEverything().something();

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

    这是一种让编译器检查比平时更多的东西的好方法,而且可以加强合同的执行。不幸的是,并非所有编译器都支持它。

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

    请参阅更灵活和可配置的博客帖子@nullable/@notnull注释。


    如果不允许空值

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

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

    然后,在该方法的其余部分中,您将知道object不是空的。

    如果它是一个内部方法(不是API的一部分),只需记录它不能为空,就可以了。

    例子:

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

    但是,如果您的方法只是传递值,而下一个方法传递值等等,那么它可能会有问题。在这种情况下,您可能需要如上所述检查参数。

    如果允许空值

    这要看情况而定。如果发现我经常这样做:

    1
    2
    3
    4
    5
    if (object == null) {
      // something
    } else {
      // something else
    }

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

    实际上,我很少用"EDOCX1"(3)这个成语。

    如果你举例说明你通常在哪里使用这个习语,给你举个例子可能会更容易。


    哇,当我们有57种不同的方法来推荐EDCOX1的4度时,我几乎不喜欢添加另一个答案,但是我认为有些人对这个问题感兴趣可能想知道在JAVA 7的表上有一个建议添加"NULL安全处理"和MQASH;如果不是相等的空逻辑,则有一个简化的语法。

    亚历克斯·米勒给出的例子如下:

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

    ?.表示只有当左标识符不为空时才取消引用,否则将表达式的其余部分计算为null。一些人,比如JavaPasSE成员Dick Wall和DeVox的选民们真的很喜欢这个提议,但也有反对意见,理由是它实际上会鼓励更多地使用EDOCX1 6作为前哨值。

    更新:在Project Boin上提交了Java 7中的零安全操作员的官方建议。语法与上面的示例稍有不同,但它的概念相同。

    更新:空的安全操作员建议没有进入Project Coin。所以,在Java 7中,你不会看到这种语法。


    如果不允许未定义的值:

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

    如果允许未定义的值:

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

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

    Java 8有一个内置的EDCOX1×0的类(推荐);对于早期的版本,有库的替代品,例如番石榴的EDOCX1×0或OntalJava的EDCOX1(2)。但是,与许多功能样式模式一样,使用Java中的选项(甚至8)会产生相当多的样板,可以使用较少冗长的JVM语言(例如Scala或XCATEN)来减少。

    如果你必须处理一个可能返回空值的API,那么在爪哇你做不了多少。xtend和groovy有elvis操作符?:和NULL安全解引用操作符?.,但请注意,如果引用为NULL,则返回NULL,因此它只是"延迟"对NULL的正确处理。


    只有在这种情况下-

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

    1
    2
    3
    if ( foo.equals("bar") ) {
     // ...
    }

    如果不存在foo,将导致NullPointerException

    如果您将您的String比较如下,可以避免这种情况:

    1
    2
    3
    if ("bar".equals(foo) ) {
     // ...
    }