有时我面对我必须写一段这样的代码(通常它有更多的嵌套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) {
}
}
第二种形式如性能或其他类型的问题是否有问题?
异常版本(类似于使用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(小辅助方法)来封装检查,并且几乎完全消除了对问题的需求。
使用Java 8可选:
Optional.ofNullable(a)
.map(Object1::getB)
.map(SubObject::getC)
.ifPresent(Object2::print);
是。第二个版本将有糟糕的表现。
不要将异常用于正常控制流程。有效的Java项目57:仅在特殊情况下使用异常。
== ==更新
甚至忽略了性能问题(根据我的benchmark,异常比以前更快,但不如简单的if检查快得多),对于像这样的标准程序流使用异常真的很臭。即使在if
语句中,JVM字节码也可以对空检查进行特殊优化。第一个代码示例是非常优选的。
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不能为空,那么你会隐藏错误情况。
第二个版本的最糟糕的部分是,当一个NPE发生在getB()
内部时,getC()
将被默默地忽略。如前所述,例外情况适用于特殊情况。
使用异常在性能方面总是一个坏主意,无论机制过去和现在的速度有多慢。每当抛出异常时,将展开完整堆栈以创建堆栈跟踪。因此,就像Lois Wasserman所说,你不应该依赖它们来进行(常规)程序流程,但是对于例外情况。
嵌套ifs不是美的定义,但会让你能够打印其他信息,如'B is null'等。
答案是使用版本A.
通常认为将“异常”用于流量控制是“糟糕的设计”。例外情况是“特殊”,尤其是使用空检查完全可以避免的NPE。此外,使用空检查,您可以告诉(即记录)哪个术语为空(您不知道null与版本B的位置)。
请注意,性能不再是抛出异常的问题(例如,只有在使用堆栈跟踪时才构建堆栈跟踪)。这是一个干净的代码问题。
但是,在某些情况下,使用流控制异常是不可避免的,例如从SimpleDateFormat.parse()抛出的异常,因为在调用之前没有合理的方法告诉您输入是不可解析的。
绝对是(a)但是你应该重新构造方法以避免嵌套前面答案中提到的if语句。异常不是它们曾经的性能损失,但仍然比检查null慢得多,并且永远不应该像这样用于程序流控制。如果对象可以为null,则应检查它,但如果不允许,则应在分配对象引用的位置快速失败。在许多情况下,您可以使用默认实现(空列表是一个很好的示例)以完全避免空值,从而产生更清晰的代码。尽可能避免使用空值。
代码永远不应包含未经检查的异常的异常处理程序。应始终将null检查用于可能为null的对象引用。
如果迁移到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!。