【问题标题】:try/catch vs null check in javajava中的try/catch vs null检查
【发布时间】:2012-04-03 05:55:39
【问题描述】:

有时我面临我必须写一段这样的代码(通常它有更多的嵌套 if 和更复杂的结构,但示例就足够了)

public void printIt(Object1 a){
  if (a!=null){
     SubObject b= a.getB();
     if (b!=null){
         SubObject2 c=b.getC();
         if(c!=null){
             c.print();
         }
     }
  }
}

当我不需要知道什么失败了,如果某些东西是 null 什么都不做,那么一种方法可能是

public void printIt(Object1 a){
    try{
      a.getB().getC().print();
    }catch (NullPointerException e) {
    }
}

第二种形式是否有问题,例如性能或其他类型的问题?

【问题讨论】:

  • 异常在设计时并未考虑到标准程序流程,它们是为特殊情况而设计的。
  • 不要抓住RunTimeException的,而是避开它。
  • 如果你必须处理这么多的空值,你使用的代码的定义可能有问题。
  • @Eng.Fouad RuntimeExceptions 并不总是可以避免的,这就是为什么它们是“运行时”的。

标签: java


【解决方案1】:

例外版本(类似于使用 Groovy 的安全导航运算符 ?. 的链)使您可以很容易地采用 Demeter 法则(或者我称之为 Demeter 的强词建议)并将其作为您的玩物晚上。

同样,深度嵌套的if-statements 会导致代码难以阅读,而在这一切之下,同样存在“违规”,并且此类方法的圈复杂度很高。

public void printIt(Object1 a) {
    if (null == a) {
        return;
    }

    SubObject b = a.getB();
    if (null == b) {
        return;
    }

    SubObject2 c = b.getC();
    if (null == c) {
        return;
    }

    c.print();
}

我宁愿在适当的地方看到 LAM(小辅助方法)来封装检查并且几乎完全消除对问题的需求。

【讨论】:

  • 你打败了我!不过,作为旁注,我确信理论上的 a、b 和 c 永远不会为空。这只是对它们进行不同编码的情况。
【解决方案2】:

是的。第二个版本的性能会很糟糕。

不要对正常的控制流使用异常。有效的 Java 条款 57:仅在异常情况下使用异常。

==更新==

即使忽略性能问题(根据我的benchmark,异常比以前更快,但不如简单的 if 检查快),在这样的标准程序流中使用异常确实是代码异味。 JVM 字节码对空值检查进行了特殊优化,即使在 if 语句中也是如此。第一个代码示例非常受欢迎。

【讨论】:

  • 这根本不是真的。 Java 中的异常不会像过去那样对性能造成影响。
  • 引用,好吗? (以及哪个 Java 版本?我已经完成了最近的基准测试。)
  • @MДΓΓБДLL,它们的性能可能在较新的 JVM 版本中有所提高,但我仍然敢打赌,它远不及标准的空检查。制作堆栈跟踪快照本身是一项非常昂贵的操作。
  • 此外,“没有像过去那样受到性能影响”并不意味着它们不慢。
  • 好的,我有一个带有硬数字的 Caliper 基准!我承认,@MДΓΓБДLL,异常比我预期的要快得多——但不比标准的空检查快。 microbenchmarks.appspot.com/run/wasserman.louis@gmail.com/… 上的数据(和硬件详细信息以及 JVM 版本),gist.github.com/2071935 上的基准测试源。
【解决方案3】:
public void printIt(Object1 a){
    if(a==null){
        throw new IllegalArgumentException("a was null, but this is not allowed here."),
    }
    [...]

快速失败并努力失败。如果 a 不应该为 null,则抛出异常。这将使您的代码更加稳定和可靠。

所以如果我必须在你的 a) 和你的 b) 之间做出决定,我会选择 a)。但是,如果 a 不能为 null,您将隐藏错误情况。

【讨论】:

    【解决方案4】:

    第二个版本最错误的部分是当 NPE 发生在 getB(), getC() 内部时,它将被默默地忽略。如前所述,例外是针对特殊情况。

    【讨论】:

      【解决方案5】:

      就性能而言,使用异常总是一个坏主意,无论该机制过去和现在有多慢。每当抛出异常时,将展开整个堆栈以创建堆栈跟踪。因此,就像 Lois Wasserman 所说,您不应该在(常规)程序流程中依赖它们,而应该在特殊情况下依赖它们。

      嵌套的 if 不是美的定义,但可以让您打印附加信息,例如“B 为空”等。

      【讨论】:

      • 实际上异常抛出/捕获可以被优化掉,所以性能并不总是很差(尽管你可能不想依赖它)。
      【解决方案6】:

      答案是使用版本 A。

      使用异常来进行流控制通常被认为是“糟糕的设计”。例外适用于“异常”,尤其是使用空检查完全可以避免的 NPE。此外,使用空值检查,您可以判断(即记录)哪个 项为空值(您不会知道版本 B 中空值在哪里)。

      请注意,性能不再是引发异常的问题(例如,堆栈跟踪仅在您使用它时才会构建)。这是一个干净的代码问题。

      但是,在某些情况下使用异常进行流控制是不可避免的,例如从 SimpleDateFormat.parse() 抛出的异常,因为在调用之前没有合理的方法来判断您的输入不可解析.

      【讨论】:

        【解决方案7】:

        肯定是 (a),但您应该重组方法以避免嵌套 if 语句,如先前答案中所述。异常不再像以前那样对性能造成影响,但仍然比检查 null 慢得多,并且永远不应该像这样用于程序流控制。如果一个对象可以为空,则应检查它,但如果不允许,则应在分配对象引用时快速失败。在许多情况下,您可以使用默认实现(空列表是一个很好的例子)来完全避免空值,从而产生更简洁的代码。尽可能避免使用空值。

        【讨论】:

          【解决方案8】:

          使用 Java 8 可选:

          Optional.ofNullable(a)
                      .map(Object1::getB)
                      .map(SubObject::getC)
                      .ifPresent(Object2::print);
          

          【讨论】:

            【解决方案9】:

            如果您迁移到 Java 8,您可以使用 Optional 和 Lambda。首先,您需要重写您的类以返回每种类型的Optional

            class Object1 {
                private SubObject b;
            
                Optional<SubObject> getB() {
                    return Optional.ofNullable(b);
                }
            }
            
            class SubObject {
                private SubObject2 c;
            
                Optional<SubObject2> getC() {
                    return Optional.ofNullable(c);
                }
            }
            
            class SubObject2 {
                @Override
                public String toString() {
                    return "to be printed";
                }
            }
            

            现在,您可以以简洁的方式链接调用而不会出现 NullPointerExceptions 的风险:

            a.getB()
                .flatMap(SubObject::getC)
                .ifPresent(System.out::println);
            

            有关详细信息,请参阅 Oracle 的文章 Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!

            【讨论】:

              【解决方案10】:

              代码不应包含未检查异常的异常处理程序。对于有可能为 null 的对象引用,应始终使用 null 检查。

              【讨论】:

                猜你喜欢
                • 2023-04-05
                • 2016-02-19
                • 1970-01-01
                • 2012-09-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-12-18
                相关资源
                最近更新 更多