java中的try/catch与null检查

java中的try/catch与null检查,java,Java,有时我必须写一段这样的代码(通常它有更多的嵌套if和更复杂的结构,但对于示例来说是足够的) 当我不需要知道什么失败了,如果什么是空的,什么也不做,一种方法可以是 public void printIt(Object1 a){ try{ a.getB().getC().print(); }catch (NullPointerException e) { } } 第二种形式是否有问题,如性能或其他问题?是的。第二个版本将有糟糕的性能 不要对正常控制流使用异常。有

有时我必须写一段这样的代码(通常它有更多的嵌套if和更复杂的结构,但对于示例来说是足够的)

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

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

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

是的。第二个版本将有糟糕的性能

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

==更新==


即使忽略性能问题(根据我的说法,异常比以前更快,但比简单的if检查快得多),对于这样的标准程序流来说,使用异常真的很难闻。JVM字节码具有针对空检查的特殊优化,即使在
if
语句中也是如此。第一个代码示例非常受欢迎。

第二个版本中最错误的部分是,当NPE发生在
getB()
中时,
getC()
将被默默忽略。如前所述,异常适用于异常情况。

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


嵌套的ifs不是美丽的定义,但它可以让您打印额外的信息,如“B为空”等。

答案是使用版本A

使用异常进行流量控制通常被认为是“糟糕的设计”。例外适用于“例外”,尤其是NPE,使用空检查完全可以避免。此外,使用null检查,您可以告诉(ie日志)哪个术语是null(您不知道在版本B中null在哪里)

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

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

public void printIt(Object1 a){
    if(a==null){
        throw new IllegalArgumentException("a was null, but this is not allowed here."),
    }
    [...]
失败快,失败难。如果不应为null,则引发异常。这将使您的代码更加稳定和可靠

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

异常版本(类似于使用Groovy的安全导航操作符的链)。)使您很容易接受Demeter定律(或者我称之为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(小辅助方法),它封装了检查,并且几乎完全消除了对问题的需要。

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

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

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

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";
    }
}
有关更多信息,请参阅Oracle的文章。

使用Java 8可选:

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

异常的设计并没有考虑到标准程序流,它们是为异常情况而设计的。可能的重复:不要捕获
运行时异常
,相反,要避免它。如果你必须处理这么多空值,那么你所使用的代码的定义可能有问题。@Eng.Fouad RuntimeException并不总是可以避免的,这就是为什么它们是“runtime”。这根本不是真的。Java中的异常不会像以前那样导致性能下降。请引用?(哪一个Java版本?我做了最近的基准测试,表明情况并非如此。)@MΓΓББLL,在较新的JVM版本中,它们的性能可能有所提高,但我仍然打赌它远没有达到标准的空检查。对堆栈跟踪本身进行快照是一项非常昂贵的操作。此外,“不会像以前那样造成性能损失”并不意味着它们不会慢。好吧,我有一个带有硬数字的卡尺基准!我承认,@MΓΓББLL,异常的速度比我预期的要快得多——但不比标准的空检查快。数据(和硬件细节,以及JVM版本)在,基准源在。你们比我抢先一步!不过,作为旁注,我相信理论上的a,b和c永远不会为空。实际上,异常抛出/捕获是可以优化的,所以性能并不总是很差(尽管您可能不想依赖于此)。
a.getB()
    .flatMap(SubObject::getC)
    .ifPresent(System.out::println);
Optional.ofNullable(a)
            .map(Object1::getB)
            .map(SubObject::getC)
            .ifPresent(Object2::print);