空检查链与捕获NullPointerException
Web服务返回一个巨大的XML,我需要访问它的深层嵌套字段。 例如:
return wsObject.getFoo().getBar().getBaz().getInt()
问题是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();
写作是否可以接受?
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
还是会被视为反模式?
捕捉NullPointerException
是一件非常有问题的事情,因为它们几乎可以发生在任何地方。 从一个bug中获得一个非常容易,偶然发现并继续,就好像一切都是正常的,因此隐藏了一个真正的问题。 处理起来非常棘手,所以最好完全避免。 (例如,考虑一个空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);
}
为什么选择?
对于可能缺失的值,使用Optional
s而不是null
可以使读者清楚地看到这一事实,并且类型系统将确保您不会意外忘记它。
您还可以更方便地访问处理这些值的方法,如map
和orElse
。
缺席是否有效或错误?
但是也要考虑一下,如果这是中间方法返回null的有效结果,或者如果这是错误的标志。 如果它总是一个错误,那么最好抛出一个异常,而不是返回一个特殊的值,或者让中间方法本身抛出一个异常。
也许更多的选择?
另一方面,如果中间方法中缺少的值是有效的,也许你可以切换到可Optional
吗?
然后你可以像这样使用它们:
public Optional<Integer> mo(Ws wsObject) {
return wsObject.getFoo()
.flatMap(f -> f.getBar())
.flatMap(b -> b.getBaz())
.flatMap(b -> b.getInt());
}
为什么不可选?
我可以考虑不使用Optional
的唯一原因是,如果这是在代码的真正性能关键部分,并且垃圾收集开销是一个问题。 这是因为每次执行代码时会分配几个Optional
对象,并且虚拟机可能无法优化这些对象。 在这种情况下,您的原始if-tests可能会更好。
我建议考虑Objects.requireNonNull(T obj, String message)
。 您可以为每个异常构建带有详细消息的链,如
requireNonNull(requireNonNull(requireNonNull(
wsObject, "wsObject is null")
.getFoo(), "getFoo() is null")
.getBar(), "getBar() is null");
我建议你不要使用特殊的返回值,比如-1
。 这不是Java风格。 Java设计了异常机制来避免这种来自C语言的旧式方式。
抛出NullPointerException
也不是最好的选择。 您可以提供自己的异常(使其被检查以确保它将由用户处理或未经检查而以更简单的方式进行处理),或者使用您正在使用的XML解析器中的特定异常。
正如汤姆在评论中指出的那样,
以下声明违反德米特法,
wsObject.getFoo().getBar().getBaz().getInt()
你想要的是int
,你可以从Foo
得到它。 德米特法律说,绝不和陌生人交谈。 对于你的情况,你可以在Foo
和Bar
隐藏下隐藏实际的实现。
现在,您可以在Foo
创建方法从Baz
获取int
。 最终, Foo
将拥有Bar
和Bar
我们可以访问Int
而无需将Baz
直接暴露给Foo
。 所以,空检查可能被分为不同的类,只有所需的属性才会在这些类中共享。