2012-04-30 94 views
25

想象一下,我有一个类家庭。它包含一个人员列表。每个(类)Person包含一个(类)地址。每个(类)地址都包含一个(类)PostalCode。任何“中间”类都可以为null。Java:避免检查嵌套类中的空值(深空检查)

那么,有没有一种简单的方法到达PostalCode,而不必在每一步中检查null?即,有没有办法避免以下菊花链代码?我知道没有“原生”Java解决方案,但希望是否有人知道图书馆或其他东西。 (选中共享&番石榴,并没有看到任何东西)

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

我不能相信我是第一个开发过生病“N厌倦这样写代码,但是我的天堂”无法找到解决方案。

想法?

感谢

-
llappall

+0

对不起,你卡住了。有些人使用三元条件运算符使其密度稍小,但它仍然是相同的字节码,只是难以阅读。 –

+4

“*我不相信我是第一个生病的开发者,厌倦了编写这样的代码*”呃,你没有。 – user1329572

+0

当然。但我不相信你可以更加优雅的代码!抱歉! –

回答

1

没有这样一个很酷的想法,但如何捕捉异常:

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

你可以得到最接近的是采取短的优势 - 条件规则中的切入规则:

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!!! 

} 

顺便提一下,捕捉异常而不是提前测试条件是一个可怕的想法。

+0

你有一些accessive'}'那里(忘了在重构时删除它们) – amit

+0

谢谢,@amit。现在修复。 –

11

你的代码的行为一样

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 
} 

由于short circuiting evaluation,这也是安全的,因为第二个条件将不被评估,如果第一个是假的,第三个将不被评估,如果第二个是错误的,...并且你不会得到NPE,因为如果它。

+0

'null' allways想留在我们的代码! –

3

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

除此以外1)特定的帮手检查空清理代码或2)使用反射和像绳子使通用的方法方法:

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

实现留作练习。

+0

反射也不是很便宜。 –

+0

是的,它确实可以缓慢以及@保罗。尤其是方法查找等。虽然实际的方法调用可能相当快,但它又取决于(其他优化对于VM来说可能更难)。因此,如果需要,缓存方法/字段查找通常很重要。最重要的是它取决于代码的使用频率。 –

1

您可以使用某些版本的“空对象”设计模式来代替使用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<Person> { 
    @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()) {...} 

这是不理想的,因为:

  • 你坚持对每个类别进行NULL对象,
  • 很难为了使这些对象具有通用性,所以你被卡住了,使得你想要使用的每个List,Map等的null对象版本,以及
  • 子类化可能存在一些有趣的问题,并使用NULL。

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

0

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

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

/** 
* Using {@link java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion) 
* to the array of Objects x. 
* 
* <p>For example, imagine that you'd like to express: 
* 
* <pre><code> 
* Fubar[] out = new Fubar[x.length]; 
* for (int i=0; {@code i<x.length}; i++) { 
* out[i] = x[i].getFoo().getBar().getFubar(); 
* } 
* </code></pre> 
* 
* Unfortunately, the correct code that checks for nulls at every level of the 
* daisy-chain becomes a bit convoluted. 
* 
* <p>So instead, this method does it all (checks included) in one call: 
* <pre><code> 
* Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar"); 
* </code></pre> 
* 
* <p>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> 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<n; i++) { 
       Object o = x[i]; 
       if (o != null) { 
        Method method = o.getClass().getMethod(methodName); 
        Object sub = method.invoke(o); 
        out[i] = sub; 
       } 
      } 
      x = out; 
     } 
    T[] result = (T[])Array.newInstance(type.getClass().getComponentType(), n); 
    for (int i=0; i<n; i++) { 
      result[i] = (T)x[i]; 
    } 
      return result; 
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 
      throw new RuntimeException(e); 
    } 
} 
0

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

resolve(() -> people.get(0).getAddress().getPostalCode()); 
    .ifPresent(System.out::println); 

: 
public static <T> Optional<T> resolve(Supplier<T> resolver) { 
    try { 
     T result = resolver.get(); 
     return Optional.ofNullable(result); 
    } 
    catch (NullPointerException e) { 
     return Optional.empty(); 
    } 
} 

REF:avoid null checks

+0

这个解决方案依赖于捕获性能非常差的NPE。 – mojoken

0

虽然这个职位是近五年来的时候,我可能有另一种解决方案如何处理NullPointerException S中的老问题。

一言以蔽之:

end: { 
    List<People> 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,您将其命名......),并且Law of Demeter未应用,则基本上必须检查所有内容并查看是否有潜在的NPE潜伏。

我建议的解决方案致力于可读性,如果没有至少3个或更多的嵌套类(当我说嵌套时,我不是指正式上下文中的Nested classes)不应该使用。由于代码读取的次数多于写入代码的次数,因此快速浏览代码的左侧部分会比使用深层嵌套的if-else语句更清晰。

如果需要else部分,您可以使用此模式:

boolean prematureEnd = true; 

end: { 
    List<People> 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"); 
} 

某些集成开发环境将打破这种格式,除非你指示他们不要(见this question)。

您的条件必须颠倒 - 告诉代码何时应该中断,而不是何时应该继续。

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