空检查链与捕获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自动拆箱。)

我build议你改用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); } 

缺席有效还是错误?

但也要考虑一下,如果这是中间方法返回null的有效结果,或者如果这是一个错误的迹象。 如果它总是一个错误,那么最好抛出一个exception,而不是返回一个特殊的值,或者让中间方法本身抛出一个exception。


也许更多的select?

另一方面,如果中间方法缺less的值是有效的,也许你可以切换到Optional的?

那么你可以像这样使用它们:

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

为什么select?

对于可能缺less的值,使用Optional s而不是null可以使读者清楚地看到这个事实,并且types系统将确保您不会意外地忘记它。

您还可以更方便地访问使用这些值的方法,如maporElse


为什么不可选?

我可以考虑不使用Optional的唯一原因是如果这是在一个真正的性能关键的代码部分,如果垃圾收集开销是一个问题。 这是因为每次执行代码时都会分配几个Optional对象,并且虚拟机可能无法优化这些对象。 在这种情况下,你的原始if-tests可能会更好。

我build议考虑Objects.requireNonNull(T obj, String message) 。 有了它,你可以build立链条与每个例外的详细信息,如

 import static java.util.Objects.requireNonNull; ... return requireNonNull(requireNonNull(requireNonNull( wsObject, "wsObject is null") .getFoo(), "getFoo() is null") .getBar(), "getBar() is null"); 

另外,我build议你不要使用特殊的返回值,比如-1 。 这不是Java风格。 Javadevise了一个exception机制来避免这种来自C语言的老式方式。

顺便说一下,抛出NullPointerException也不是最好的方法。 您可以提供您自己的exception(使其被检查以确保它将由用户处理或者未经检查以便于处理),或者使用您正在使用的XMLparsing器中的特殊exception。

汤姆在评论中已经指出,

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

 wsObject.getFoo().getBar().getBaz().getInt() 

你想要的是int ,你可以从Foo得到它。 得墨忒耳的律法说, 不要跟陌生人说话 。 对于你的情况,你可以在FooBar隐藏下隐藏实际的实现。

现在,您可以在Foo创build方法从Baz获取int 。 最终, Foo将有BarBar我们可以访问Int而不必将Baz直接暴露给Foo 。 所以,空检查可能被分为不同的类,只有需要的属性才会在类之间共享。

我的答案几乎和@janki差不多,但我想稍微修改代码片段,如下所示:

 if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) return wsObject.getFoo().getBar().getBaz().getInt(); else return something or throw exception; 

你也可以为wsObject添加一个空的检查,如果这个对象wsObject话。

假设阶级结构确实不在我们的控制范围内,但似乎是这样的话,我认为如果问题中提到的那样捕捉到NPE,这确实是一个合理的解决办法,除非performance是一个主要关切。 一个小的改进可能是包装throw / catch逻辑以避免混乱:

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

现在你可以简单地做:

 return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1); 

NullPointerException是一个运行时exception,所以一般来说不build议抓住它,但要避免它。

无论您想调用方法,您都必须捕获exception(否则它将传播到堆栈中)。 尽pipe如此,如果你的情况下,你可以继续使用值为-1的结果,并且确信它不会传播,因为你没有使用任何可能为空的“块”,那么对我来说似乎是正确的抓住它

编辑:

我同意@xenteros的后面的答案 ,最好是启动自己的exception,而不是返回-1,你可以称之为InvalidXMLException

为了提高可读性,您可能希望使用多个variables,如

 Foo theFoo; Bar theBar; Baz theBaz; theFoo = wsObject.getFoo(); if ( theFoo == null ) { // Exit. } theBar = theFoo.getBar(); if ( theBar == null ) { // Exit. } theBaz = theBar.getBaz(); if ( theBaz == null ) { // Exit. } return theBaz.getInt(); 

你说有些方法“可能返回null ”,但不要说在什么情况下它们返回null 。 你说你发现了NullPointerException但是你没有说出为什么要捕获它。 这种信息的缺乏表明,对于什么是例外情况以及为什么它们优于另一种情况,你并没有清楚的认识。

考虑一个类方法,它意味着要执行一个动作,但是这个方法不能保证它会执行这个动作,这是因为它无法控制的情况(实际上Java中的所有方法都是这种情况 )。 我们称之为方法并返回。 调用该方法的代码需要知道它是否成功。 它怎么知道? 怎样才能应付成功或失败的两种可能性呢?

使用exception,我们可以编写成功的方法作为后置条件 。 如果该方法返回,则成功。 如果抛出exception,则失败。 这是一个明确的胜利。 我们可以编写清楚处理正常,成功案例的代码,并将所有error handling代码移入catch子句。 经常发现一个方法不成功的细节,对于调用者来说并不重要,所以相同的catch子句可以用来处理多种types的失败。 通常情况下,一个方法根本不需要捕获exception,但可以允许它们传播给调用者。 由于程序错误导致的exception在后一类中; 有什么方法可以在出现错误时作出适当的反应。

那么,那些返回null方法。

  • null值是否代表您的代码中的错误? 如果是这样的话,你根本不应该抓住这个例外。 而你的代码不应该试图自己猜测。 只要写出明确而简洁的假设,即可行。 方法调用链是否清晰简洁? 那就用它们吧
  • null值是否表示对程序的无效input? 如果是这样, NullPointerException不是一个适当的exception抛出,因为传统上它是保留指示错误。 您可能想要抛出从IllegalArgumentException (如果您想要一个未经检查的exception )或IOException (如果您想要一个检查的exception)派生的自定义exception。 当input无效时,您的程序是否需要提供详细的语法错误消息? 如果是这样,检查每个方法的null返回值,然后抛出一个适当的诊断exception是唯一可以做的事情。 如果您的程序不需要提供详细的诊断信息,将方法调用链接在一起,捕获任何NullPointerExceptionexception,然后抛出您的自定义exception是最清晰和最简洁的。

其中一个答案声称,链式方法调用违反德米特法,因此是不好的。 这个说法是错误的。

  • 说到程序devise,关于什么是好的,什么是坏的,并没有什么绝对的规则。 只有启发式:规则是正确的(甚至几乎所有的时间)。 编程技巧的一部分就是知道什么时候可以打破这些规则。 所以一个简短的断言,“这是违反X规则”,根本不是一个真正的答案。 这是规则应该被打破的情况之一吗?
  • 德米特法则是一个关于API或类接口devise的规则。 在devise课程时,有一个抽象层次是有用 。 您有低级别的类,它们使用语言基元直接执行操作,并在高于语言基元的抽象中表示对象。 您有中等级别的类委托给低级别类,并在比低级别级别更高的级别上实现操作和表示。 你有高水平的class级授予中等水平的class级,并实施更高层次的操作和抽象。 (我在这里只谈到了三个抽象层次,但更多的是可能的)。 这允许您的代码在每个级别的适当的抽象方面expression自己,从而隐藏了复杂性。 德米特法律的基本原理是,如果你有一系列的方法调用,这表明你有一个高水平的课程通过中等水平的课程直接处理低水平的细节,因此你的中等水平的课程没有提供了高级类所需的中级抽象操作。 但是这似乎不是你在这里的情况:你没有devise方法调用链中的类,它们是一些自动生成的XML序列化代码(右?)的结果,并且调用链不下降通过一个抽象层次结构,因为解序列化的XML与抽象层次结构(右?)在同一层次上?

不要捕获NullPointerException 。 你不知道它来自哪里(我知道你的情况不太可能,但也许别的东西扔了),它是缓慢的。 你想访问指定的字段,并为此每隔一个字段必须不为空。 这是检查每个领域的完美正当理由。 我可能会检查它,如果然后创build一个可读性的方法。 正如其他人指出,已经返回-1是非常老派,但我不知道你是否有理由(例如与另一个系统交谈)。

 public int callService() { ... if(isValid(wsObject)){ return wsObject.getFoo().getBar().getBaz().getInt(); } return -1; } public boolean isValid(WsObject wsObject) { if(wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) { return true; } return false; } 

编辑:这是值得商榷的,因为WsObject可能只是一个数据结构(请查看https://stackoverflow.com/a/26021695/1528880 ),否则Demeter是Demeter法则。

如果您不想重构代码,并且可以使用Java 8,则可以使用Method引用。

首先进行一个简单的演示(借口静态内部类)

 public class JavaApplication14 { static class Baz { private final int _int; public Baz(int value){ _int = value; } public int getInt(){ return _int; } } static class Bar { private final Baz _baz; public Bar(Baz baz){ _baz = baz; } public Baz getBar(){ return _baz; } } static class Foo { private final Bar _bar; public Foo(Bar bar){ _bar = bar; } public Bar getBar(){ return _bar; } } static class WSObject { private final Foo _foo; public WSObject(Foo foo){ _foo = foo; } public Foo getFoo(){ return _foo; } } interface Getter<T, R> { R get(T value); } static class GetterResult<R> { public R result; public int lastIndex; } /** * @param args the command line arguments */ public static void main(String[] args) { WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241)))); WSObject wsObjectNull = new WSObject(new Foo(null)); GetterResult<Integer> intResult = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt); GetterResult<Integer> intResult2 = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt); System.out.println(intResult.result); System.out.println(intResult.lastIndex); System.out.println(); System.out.println(intResult2.result); System.out.println(intResult2.lastIndex); // TODO code application logic here } public static <R, V1, V2, V3, V4> GetterResult<R> getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4) { GetterResult result = new GetterResult<>(); Object tmp = value; if (tmp == null) return result; tmp = g1.get((V1)tmp); result.lastIndex++; if (tmp == null) return result; tmp = g2.get((V2)tmp); result.lastIndex++; if (tmp == null) return result; tmp = g3.get((V3)tmp); result.lastIndex++; if (tmp == null) return result; tmp = g4.get((V4)tmp); result.lastIndex++; result.result = (R)tmp; return result; } } 

产量

241
4

空值
2

接口Getter只是一个function接口,你可以使用任何等价的东西。
GetterResult类, GetterResult访问器,保留getter链的结果(如果有的话)或最后一个getter的索引。

getterChain方法是一个简单的样板代码,可以自动生成(或在需要时手动生成)。
我构造的代码,以便重复块是不言而喻的。


这不是一个完美的解决scheme,因为您仍然需要为每个getter定义一个getterChain重载。

我会重构代码,但是如果不能,而且你经常使用长的getter链,那么你可能会考虑用2到10的getters重载构build一个类。

正如其他人所说,尊重德米特法律绝对是解决scheme的一部分。 另一部分,只要可能,就是改变这些链接的方法,使它们不能返回null 。 你可以避免返回null ,而是返回一个空的String ,一个空的Collection或者其他一些哑元对象,这个哑元对象意味着调用者会用null来做任何事情。

我想添加一个关于错误含义的答案。 Nullexception本身并不提供任何含义完整的错误。 所以我build议避免直接与他们打交道。

有数以千计的情况下,你的代码可能会出错:无法连接到数据库,IOexception,networking错误…如果你一个接一个地处理它们(像这里的空检查),这将是太麻烦了。

在代码中:

 wsObject.getFoo().getBar().getBaz().getInt(); 

即使你知道哪个字段是空的,你也不知道哪里出了问题。 也许酒吧是空的,但它是预期的? 还是数据错误? 想想阅读你的代码的人

像在xenteros的答案一样,我会build议使用自定义的未经检查的exception 。 例如,在这种情况下:Foo可以为null(有效数据),但Bar和Baz不应该为null(无效数据)

代码可以重写:

 void myFunction() { try { if (wsObject.getFoo() == null) { throw new FooNotExistException(); } return wsObject.getFoo().getBar().getBaz().getInt(); } catch (Exception ex) { log.error(ex.Message, ex); // Write log to track whatever exception happening throw new OperationFailedException("The requested operation failed") } } void Main() { try { myFunction(); } catch(FooNotExistException) { // Show error: "Your foo does not exist, please check" } catch(OperationFailedException) { // Show error: "Operation failed, please contact our support" } } 

如果效率是一个问题,那么应该考虑“捕获”选项。 如果'catch'不能被使用,因为它会传播(如'SCouto'所述),那么使用局部variables来避免对方法getFoo()getBar()getBaz()多次调用。

值得考虑创build自己的例外。 我们称之为MyOperationFailedException。 你可以抛出它,而不是返回一个值。 结果将是相同的 – 您将退出该函数,但不会返回硬编码值-1,即Java反模式。 在Java中,我们使用例外。

 try { return wsObject.getFoo().getBar().getBaz().getInt(); } catch (NullPointerException ignored) { throw new MyOperationFailedException(); } 

编辑:

根据评论中的讨论,让我添加一些我以前的想法。 在这个代码中有两种可能性。 一个是你接受null,另一个是,这是一个错误。

如果发生错误并且发生错误,则可以使用其他结构来debugging代码,以便在断点不足时进行debugging。

如果可以接受的话,你不关心这个空出现在哪里。 如果你这样做,你绝对不应该链接这些请求。

自昨天以来一直关注这个post。

我一直在评论/投票评论说,捕捉NPE是不好的。 这就是为什么我一直这样做。

 package com.todelete; public class Test { public static void main(String[] args) { Address address = new Address(); address.setSomeCrap(null); Person person = new Person(); person.setAddress(address); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { try { System.out.println(person.getAddress().getSomeCrap().getCrap()); } catch (NullPointerException npe) { } } long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime) / 1000F); long startTime1 = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { if (person != null) { Address address1 = person.getAddress(); if (address1 != null) { SomeCrap someCrap2 = address1.getSomeCrap(); if (someCrap2 != null) { System.out.println(someCrap2.getCrap()); } } } } long endTime1 = System.currentTimeMillis(); System.out.println((endTime1 - startTime1) / 1000F); } } 

  public class Person { private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } } 

 package com.todelete; public class Address { private SomeCrap someCrap; public SomeCrap getSomeCrap() { return someCrap; } public void setSomeCrap(SomeCrap someCrap) { this.someCrap = someCrap; } } 

 package com.todelete; public class SomeCrap { private String crap; public String getCrap() { return crap; } public void setCrap(String crap) { this.crap = crap; } } 

产量

3.216

0.002

我在这里看到一个明确的赢家。 如果检查的成本比获得例外要便宜得多。 我已经看到了Java-8的做法。 考虑到当前70%的应用程序仍然在Java-7上运行,我正在添加这个答案。

底线对于任何关键任务应用,处理NPE都是昂贵的。

你有的方法是冗长的,但非常可读。 如果我是来自您的代码库的新开发人员,我可以很快看到您正在做什么。 其他答案(包括捕捉exception)的大部分似乎没有使事情变得更加可读,而且有些使我的意见不那么可读。

鉴于您可能无法控制生成的源代码,并假设您真的需要访问几个深层嵌套的字段,那么我build议使用方法来包装每个深度嵌套的访问。

 private int getFooBarBazInt() { if (wsObject.getFoo() == null) return -1; if (wsObject.getFoo().getBar() == null) return -1; if (wsObject.getFoo().getBar().getBaz() == null) return -1; return wsObject.getFoo().getBar().getBaz().getInt(); } 

如果你发现自己写了很多这些方法,或者如果你发现自己想要制作这些公共的静态方法,那么我会创build一个单独的对象模型,嵌套你想如何,只有你关心的领域,并从networking转换服务对象模型到你的对象模型。

在与远程Web服务进行通信时,通常有一个“远程域”和一个“应用程序域”,并在两者之间切换。 远程域通常受到Web协议的限制(例如,您不能在纯RESTful服务中来回发送帮助程序方法,深度嵌套的对象模型通常会避免多个API调用),因此不适合直接用于你的客户。

例如:

 public static class MyFoo { private int barBazInt; public MyFoo(Foo foo) { this.barBazInt = parseBarBazInt(); } public int getBarBazInt() { return barBazInt; } private int parseFooBarBazInt(Foo foo) { if (foo() == null) return -1; if (foo().getBar() == null) return -1; if (foo().getBar().getBaz() == null) return -1; return foo().getBar().getBaz().getInt(); } } 
 return wsObject.getFooBarBazInt(); 

by applying the the Law of Demeter,

 class WsObject { FooObject foo; .. Integer getFooBarBazInt() { if(foo != null) return foo.getBarBazInt(); else return null; } } class FooObject { BarObject bar; .. Integer getBarBazInt() { if(bar != null) return bar.getBazInt(); else return null; } } class BarObject { BazObject baz; .. Integer getBazInt() { if(baz != null) return baz.getInt(); else return null; } } class BazObject { Integer myInt; .. Integer getInt() { return myInt; } } 

Giving answer which seems different from all others.

I recommend you to check for NULL in if s.

Reason :

We should not leave a single chance for our program to be crashed. NullPointer is generated by system. The behaviour of System generated exceptions can not be predicted . You should not leave your program in the hands of System when you already have a way of handling it by your own. And put the Exception handling mechanism for the extra safety.!!

For making your code easy to read try this for checking the conditions :

 if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) return -1; else return wsObject.getFoo().getBar().getBaz().getInt(); 

编辑:

Here you need to store these values wsObject.getFoo() , wsObject.getFoo().getBar() , wsObject.getFoo().getBar().getBaz() in some variables. I am not doing it because i don't know the return types of that functions.

Any suggestions will be appreciated..!!

I wrote a class called Snag which lets you define a path to navigate through a tree of objects. Here is an example of its use:

 Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing(); 

Meaning that the instance ENGINE_NAME would effectively call Car?.getEngine()?.getName() on the instance passed to it, and return null if any reference returned null :

 final String name = ENGINE_NAME.get(firstCar); 

It's not published on Maven but if anyone finds this useful it's here (with no warranty of course!)

It's a bit basic but it seems to do the job. Obviously it's more obsolete with more recent versions of Java and other JVM languages that support safe navigation or Optional .