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

70

假设我有一个名为Family的类。它包含一个Person列表。每个Person类都包含一个Address类,而每个Address类都包含一个PostalCode类。任何“中间”类都可能为空。

那么,是否有一种简单的方法可以在不必在每个步骤中检查null的情况下获取PostalCode?也就是说,是否有一种方法可以避免以下嵌套代码?我知道没有“本地”的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及其方便的“Elvis”运算符。

不想等待Java 8 :D

我无法相信我是第一个厌烦编写像这样代码的开发者,但我一直没有找到解决方案。


6
我简直不能相信我是第一个对编写这种代码感到病态和厌烦的开发人员。” 嗯,你并不是。 - user1329572
当然可以。但我不相信你能让代码更美观!抱歉! - Paul Vargas
4
尽管所有答案都告诉你忽略空值检查,直接尝试捕获“NullPointerException”,但不要这样做!虽然你的代码可能看起来很丑陋,但抛出异常是一个昂贵的操作,如果可以避免就要尽量避免。 - Jeffrey
1
此外,如果您在“else”子句中添加了错误消息、备用代码路径等功能,那么它们可以发挥很好的作用。考虑到这些因素,情况并不会看起来那么糟糕。 - mazaneicha
1
如果你能将Brototype移植到Java就好了... https://github.com/letsgetrandy/brototype - jonS90
显示剩余2条评论
13个回答

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
如果您可以使用Groovy进行映射,它将清理语法并使代码看起来更清洁。由于Groovy与Java共存,因此您可以利用Groovy进行映射。
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!!!
                }
            }
        }
    }
}

相反,您可以这样做。
if(family?.people?[0]?.address?.postalCode) {
   //do something
}

或者如果您需要将其映射到其他对象。
somobject.zip = family?.people?[0]?.address?.postalCode

-4

而我最喜欢的,就是简单的 try/catch,避免嵌套的空值检查...

try {
    if(order.getFulfillmentGroups().get(0).getAddress().getPostalCode() != null) {
        // your code
    } 
} catch(NullPointerException|IndexOutOfBoundsException e) {}

2
这绝不是一个建议性的方法!! - Anirudh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接