空检查链与捕获NullPointerException

Dav*_*ank 108 java null exception nullpointerexception custom-error-handling

Web服务返回一个巨大的XML,我需要访问它的深层嵌套字段.例如:

return wsObject.getFoo().getBar().getBaz().getInt()
Run Code Online (Sandbox Code Playgroud)

问题是getFoo(),getBar(),getBaz()可能所有的回报null.

但是,如果我null在所有情况下检查,代码将变得非常冗长且难以阅读.此外,我可能会错过某些领域的支票.

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
Run Code Online (Sandbox Code Playgroud)

写作是否可以接受

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}
Run Code Online (Sandbox Code Playgroud)

还是会被视为反模式?

Lii*_*Lii 135

捕捉NullPointerException是一件非常棘手的事情,因为它们几乎可以在任何地方发生.很容易从一个bug中获取一个,偶然捕获并继续好像一切正​​常,从而隐藏一个真正的问题.处理它是如此棘手,所以最好完全避免.(例如,考虑一下null的自动拆箱Integer.)

我建议你改用Optional班级.当您想要处理存在或不存在的值时,这通常是最佳方法.

使用它你可以像这样写你的代码:

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return an -1 int instead of an empty optional if any is null
        // .orElse(-1);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}
Run Code Online (Sandbox Code Playgroud)

为何选择?

使用Optionals而不是null可能缺少的值使得读者可以非常清楚地看到这一事实,类型系统将确保您不会意外忘记它.

您还可以更方便地访问处理此类值的方法,例如maporElse.


缺勤有效还是错误?

但是也要考虑它是否是中间方法返回null的有效结果,或者是否是错误的标志.如果它始终是一个错误,那么抛出异常可能比返回一个特殊值,或者中间方法本身抛出异常更好.


也许更多的选择?

另一方面,如果中间方法中缺少的值有效,那么也可以Optional为它们切换到s?

然后你可以像这样使用它们:

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}
Run Code Online (Sandbox Code Playgroud)

为什么不选择?

我可以想到不使用的唯一原因Optional是,如果这是代码中真正的性能关键部分,并且垃圾收集开销是一个问题.这是因为Optional每次执行代码时都会分配一些对象,而VM 可能无法优化这些对象.在这种情况下,您的原始if测试可能会更好.

  • `FClass :: getBar`等会更短. (7认同)
  • @Lii足够公平,但请注意方法引用可能会快一点,因为lambda可能需要更复杂的编译时结构.lambda将需要生成一个"静态"方法,这将导致非常轻微的惩罚. (5认同)

And*_*lko 12

我建议考虑一下Objects.requireNonNull(T obj, String message).您可以为每个异常构建带有详细消息的链,例如

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");
Run Code Online (Sandbox Code Playgroud)

我建议你不要使用特殊的返回值,比如-1.这不是Java风格.Java设计了异常机制,以避免使用来自C语言的这种老式方法.

投掷NullPointerException也不是最好的选择.您可以提供自己的异常(检查以确保它将由用户处理或未经检查以更简单的方式处理它)或使用您正在使用的XML解析器的特定异常.

  • 这是唯一理智的解决方案.所有其他人都建议使用流量控制的异常,这是代码气味.在旁注:我认为OP的方法链也是一种气味.如果他将使用三个局部变量和相应的if,情况会更清楚.另外我认为问题比仅仅围绕NPE更深入:OP应该问自己为什么getter可以返回null.null是什么意思?也许一些零对象会更好?或者在有一个有意义的例外的一个吸气剂中崩溃?基本上一切都比流量控制的例外要好. (4认同)

shm*_*sel 6

假设类结构确实不受我们的控制,就像在这种情况下一样,我认为按照问题中的建议捕捉NPE确实是一个合理的解决方案,除非性能是一个主要问题.一个小的改进可能是包装throw/catch逻辑以避免混乱:

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以简单地做:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);
Run Code Online (Sandbox Code Playgroud)


Cod*_*roc 5

正如汤姆在评论中已经指出的那样,

以下声明违反了得墨忒耳法,

wsObject.getFoo().getBar().getBaz().getInt()
Run Code Online (Sandbox Code Playgroud)

你想要的是什么int,你可以从中得到它Foo.得墨忒耳法则说,永远不要和陌生人说话.对于您的情况,您可以隐藏在Foo和的引擎下的实际实现Bar.

现在,您可以创建方法Foo来获取intBaz.最终,Foo将有BarBar我们可以访问Int不暴露Baz直接Foo.因此,空检查可能分为不同的类,并且只在类之间共享所需的属性.

  • 如果因为WsObject可能只是一个数据结构而违反了Demeter法则,这是值得商榷的.请参见此处:http://stackoverflow.com/a/26021695/1528880 (4认同)
  • @DerM是的,这是可能的,但由于OP已经有解析他的XML文件的东西,他还可以考虑为所需的标签创建合适的模型类,因此解析库可以映射它们.然后,这些模型类包含对其自己的子标记进行"null"检查的逻辑. (2认同)