在java中尝试/ catch vs null检查

问题描述 投票:31回答:10

有时我面对我必须写一段这样的代码(通常它有更多的嵌套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();
         }
     }
  }
}

当我不需要知道什么是失败的,如果什么是空的什么都不做,一种方法可能是

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

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

java
10个回答
20
投票

异常版本(类似于使用Groovy的安全导航操作员?.的链条)使得很容易采用得墨忒耳法则(或者我称之为Demeter的强烈建议)并使其成为夜晚的玩物。

类似地,深度嵌套的if语句导致难以阅读的代码,并且在其下面存在相同的“违规”,并且这种方法的圈复杂度很高。

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(小辅助方法)来封装检查,并且几乎完全消除了对问题的需求。


0
投票

使用Java 8可选:

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

11
投票

是。第二个版本将有糟糕的表现。

不要将异常用于正常控制流程。有效的Java项目57:仅在特殊情况下使用异常。

== ==更新

甚至忽略了性能问题(根据我的benchmark,异常比以前更快,但不如简单的if检查快得多),对于像这样的标准程序流使用异常真的很臭。即使在if语句中,JVM字节码也可以对空检查进行特殊优化。第一个代码示例是非常优选的。


8
投票
public void printIt(Object1 a){
    if(a==null){
        throw new IllegalArgumentException("a was null, but this is not allowed here."),
    }
    [...]

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

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


7
投票

第二个版本的最糟糕的部分是,当一个NPE发生在getB()内部时,getC()将被默默地忽略。如前所述,例外情况适用于特殊情况。


2
投票

使用异常在性能方面总是一个坏主意,无论机制过去和现在的速度有多慢。每当抛出异常时,将展开完整堆栈以创建堆栈跟踪。因此,就像Lois Wasserman所说,你不应该依赖它们来进行(常规)程序流程,但是对于例外情况。

嵌套ifs不是美的定义,但会让你能够打印其他信息,如'B is null'等。


2
投票

答案是使用版本A.

通常认为将“异常”用于流量控制是“糟糕的设计”。例外情况是“特殊”,尤其是使用空检查完全可以避免的NPE。此外,使用空检查,您可以告诉(即记录)哪个术语为空(您不知道null与版本B的位置)。

请注意,性能不再是抛出异常的问题(例如,只有在使用堆栈跟踪时才构建堆栈跟踪)。这是一个干净的代码问题。

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


2
投票

绝对是(a)但是你应该重新构造方法以避免嵌套前面答案中提到的if语句。异常不是它们曾经的性能损失,但仍然比检查null慢得多,并且永远不应该像这样用于程序流控制。如果对象可以为null,则应检查它,但如果不允许,则应在分配对象引用的位置快速失败。在许多情况下,您可以使用默认实现(空列表是一个很好的示例)以完全避免空值,从而产生更清晰的代码。尽可能避免使用空值。


1
投票

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


0
投票

如果迁移到Java 8,则可以使用Optional和Lambdas。首先,您需要重写类以返回每种类型的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!

© www.soinside.com 2019 - 2024. All rights reserved.