Java:避免在嵌套类中检查null(深度空检查)

想象一下,我有一个class级家庭。 它包含一个人员列表。 每个(类)Person包含一个(类)地址。 每个(类)地址包含一个(类)PostalCode。 任何“中间”类都可以为null。

那么,有没有一种简单的方法来获取PostalCode而无需在每一步检查null? 即,有没有办法避免以下菊花链代码? 我知道没有“原生”Java解决方案,但希望有人知道库或其他东西。 (已检查Commons&Guava并没有看到任何内容)

if(family != null) { if(family.getPeople() != null) { if(family.people.get(0) != null) { if(people.get(0).getAddress() != null) { if(people.get(0).getAddress().getPostalCode() != null) { //FINALLY MADE IT TO DO SOMETHING!!! } } } } } 

不,不能改变结构。 它来自我无法控制的服务。

不,我不能使用Groovy,它是方便的“猫王”操作员。

不,我宁愿不等待Java 8:D

我不敢相信我是第一个生病的人,厌倦了写这样的代码,但我找不到解决办法。

想法?

谢谢


llappall

您的代码行为与…相同

 if(family != null && family.getPeople() != null && family.people.get(0) != null && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) { //My Code } 

由于短路评估 ,这也是安全的,因为如果第一个是假的话,第二个条件将不会被评估,如果第二个是假的,第三个条件将不会被评估,……并且你不会得到NPE,因为如果它。

你可以得到的最接近的是利用条件中的捷径规则:

 if(family != null && family.getPeople() != null && family.people.get(0) != null && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) { //FINALLY MADE IT TO DO SOMETHING!!! } 

顺便说一句,捕捉exception而不是提前测试条件是一个可怕的想法。

如果很少见,您可以忽略null检查并依赖于NullPointerException 。 “罕见”由于可能的性能问题(取决于,通常会填写堆栈跟踪,这可能是昂贵的)。

除此之外1)一个特定的辅助方法,它检查null以清理该代码或2)使用reflection和类似字符串的generics方法:

 checkNonNull(family, "people[0].address.postalcode") 

实施留作练习。

没有这么酷的想法,但如何捕获exception:

  try { PostalCode pc = people.get(0).getAddress().getPostalCode(); } catch(NullPointerException ex) { System.out.println("Gotcha"); } 

您可以使用某种版本的“null object”设计模式,而不是使用null。 例如:

 public class Family { private final PersonList people; public Family(PersonList people) { this.people = people; } public PersonList getPeople() { if (people == null) { return PersonList.NULL; } return people; } public boolean isNull() { return false; } public static Family NULL = new Family(PersonList.NULL) { @Override public boolean isNull() { return true; } }; } import java.util.ArrayList; public class PersonList extends ArrayList { @Override public Person get(int index) { Person person = null; try { person = super.get(index); } catch (ArrayIndexOutOfBoundsException e) { return Person.NULL; } if (person == null) { return Person.NULL; } else { return person; } } //... more List methods go here ... public boolean isNull() { return false; } public static PersonList NULL = new PersonList() { @Override public boolean isNull() { return true; } }; } public class Person { private Address address; public Person(Address address) { this.address = address; } public Address getAddress() { if (address == null) { return Address.NULL; } return address; } public boolean isNull() { return false; } public static Person NULL = new Person(Address.NULL) { @Override public boolean isNull() { return true; } }; } etc etc etc 

然后你的if语句可以成为:

 if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...} 

这是次优的,因为:

  • 你被困在为每个class级制作NULL对象,
  • 很难使这些对象成为通用的,因此您不得不制作要使用的每个List,Map等的空对象版本,以及
  • 子类化可能存在一些有趣的问题,以及要使用的NULL。

但如果你真的讨厌你的== null s,这是一个出路。

我只是在寻找相同的东西(我的上下文:一堆自动创建的JAXB类,不知怎的,我有.getFoo().getBar()...这些长菊花链.getFoo().getBar()...总是.getFoo().getBar()...一个中间的调用返回null,导致NPE。

我开始摆弄一段时间的东西是基于反思。 我确信我们可以使这更漂亮和更高效(缓存reflection,一方面,并​​定义“魔术”方法,如._all自动迭代集合的所有元素,如果中间的某些方法返回一个集合)。 不漂亮,但也许有人可以告诉我们是否已经有更好的东西:

 /** * Using {@link java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion) * to the array of Objects x. * * 

For example, imagine that you'd like to express: * *

 * Fubar[] out = new Fubar[x.length]; * for (int i=0; {@code i

* * Unfortunately, the correct code that checks for nulls at every level of the * daisy-chain becomes a bit convoluted. * *

So instead, this method does it all (checks included) in one call: *

 * Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar"); * 

* *

The cost, of course, is that it uses Reflection, which is slower than * direct calls to the methods. * @param type the type of the expected result * @param x the array of Objects * @param methods the methods to apply * @return */ @SuppressWarnings("unchecked") public static T[] apply(T[] type, Object[] x, String...methods) { int n = x.length; try { for (String methodName : methods) { Object[] out = new Object[n]; for (int i=0; i

如果,如果您使用的是java8,那么您可以使用;

 resolve(() -> people.get(0).getAddress().getPostalCode()); .ifPresent(System.out::println); : public static  Optional resolve(Supplier resolver) { try { T result = resolver.get(); return Optional.ofNullable(result); } catch (NullPointerException e) { return Optional.empty(); } } 

REF: 避免空检查

虽然这篇文章差不多有五年之久了,但我可能还有另一个解决如何处理NullPointerException问题的解决方案。

简而言之:

 end: { List people = family.getPeople(); if(people == null || people.isEmpty()) break end; People person = people.get(0); if(person == null) break end; Address address = person.getAddress(); if(address == null) break end; PostalCode postalCode = address.getPostalCode(); if(postalCode == null) break end; System.out.println("Do stuff"); } 

由于仍有大量遗留代码在使用,因此使用Java 8和Optional并不总是一种选择。

每当涉及深层嵌套类(JAXB,SOAP,JSON,你给它命名……)并且不应用Demeter法则时,你基本上必须检查所有内容,看看是否有潜伏的NPE潜伏。

我提议的解决方案力求可读性,如果不涉及至少3个或更多嵌套类,则不应使用(当我说嵌套时,我不是指正式上下文中的嵌套类 )。 由于代码的读取比编写代码更多,因此快速浏览代码的左侧部分将使其含义比使用深层嵌套的if-else语句更清晰。

如果你需要else部分,你可以使用这个模式:

 boolean prematureEnd = true; end: { List people = family.getPeople(); if(people == null || people.isEmpty()) break end; People person = people.get(0); if(person == null) break end; Address address = person.getAddress(); if(address == null) break end; PostalCode postalCode = address.getPostalCode(); if(postalCode == null) break end; System.out.println("Do stuff"); prematureEnd = false; } if(prematureEnd) { System.out.println("The else part"); } 

某些IDE会破坏此格式,除非您指示他们不要(请参阅此问题 )。

您的条件必须反转 – 您应该告诉代码何时应该中断,而不是应该继续。

还有一件事 – 你的代码仍然容易破损。 您必须使用if(family.getPeople() != null && !family.getPeople().isEmpty())作为代码中的第一行,否则空列表将抛出NPE。