51

想象一下我有一个班级家庭。它包含一个人员列表。每个(类)Person 包含一个(类)地址。每个(类)地址包含一个(类)邮政编码。任何“中间”类都可以为空。

那么,有没有一种简单的方法可以访问 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,它是方便的“Elvis”运算符。

不,我不想等待 Java 8 :D

我不敢相信我是第一个厌倦了编写这样的代码的开发人员,但我一直无法找到解决方案。

4

12 回答 12

26

您可以用于:

product.getLatestVersion().getProductData().getTradeItem().getInformationProviderOfTradeItem().getGln();

可选等价物:

Optional.ofNullable(product).map(
            Product::getLatestVersion
        ).map(
            ProductVersion::getProductData
        ).map(
            ProductData::getTradeItem
        ).map(
            TradeItemType::getInformationProviderOfTradeItem
        ).map(
            PartyInRoleType::getGln
        ).orElse(null);
于 2019-01-22T05:55:31.847 回答
20

您的代码的行为与

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,因为如果它。

于 2012-04-30T22:35:53.920 回答
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!!!

}

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

于 2012-04-30T22:34:48.547 回答
5

If, in case, you are using java8 then you may use;

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

于 2016-10-12T04:19:57.640 回答
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的s,这是一条出路。

于 2012-05-01T01:38:28.807 回答
1

NullPointerException虽然这篇文章已经快 5 年了,但对于如何处理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 8Optional并不总是一种选择。

每当涉及深度嵌套的类(JAXB、SOAP、JSON 等等)并且没有应用Demeter 法则时,您基本上必须检查所有内容,看看是否有可能的 NPE 潜伏在周围。

我提出的解决方案力求提高可读性,如果不涉及至少 3 个或更多嵌套类,则不应使用(当我说嵌套时,我并不是指正式上下文中的嵌套类)。由于代码的阅读量多于编写量,因此快速浏览一下代码的左侧部分将比使用深度嵌套的 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");
}

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

你的条件必须被反转——你告诉代码什么时候应该中断,而不是什么时候应该继续。

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

于 2017-01-30T07:07:45.337 回答
0

不是一个很酷的主意,但是如何捕捉异常:

    try 
    {
        PostalCode pc = people.get(0).getAddress().getPostalCode();
    }
    catch(NullPointerException ex)
    {
        System.out.println("Gotcha");
    }
于 2012-04-30T22:33:48.103 回答
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);
    }
}
于 2013-04-26T22:14:26.410 回答
0

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

除此之外 1) 一个特定的辅助方法检查 null 以清理该代码或 2) 使用反射和字符串进行通用方法,如:

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

实施留作练习。

于 2012-04-30T22:37:18.803 回答
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
于 2020-08-17T15:15:56.807 回答
0

我个人更喜欢类似的东西:

nullSafeLogic(() -> family.people.get(0).getAddress().getPostalCode(), x -> doSomethingWithX(x))

public static <T, U> void nullSafeLogic(Supplier<T> supplier, Function<T,U> function) {
    try {
        function.apply(supplier.get());
    } catch (NullPointerException n) {
        return null;
    }
}

或类似的东西

nullSafeGetter(() -> family.people.get(0).getAddress().getPostalCode())

public static <T> T nullSafeGetter(Supplier<T> supplier) {
    try {
        return supplier.get();
    } catch (NullPointerException n) {
        return null;
    }
}

最好的部分是静态方法可以与任何功能重用:)

于 2022-02-10T02:32:08.850 回答
-5

和我最喜欢的简单的 try/catch,以避免嵌套的空检查......

try {
    if(order.getFulfillmentGroups().get(0).getAddress().getPostalCode() != null) {
        // your code
    } 
} catch(NullPointerException|IndexOutOfBoundsException e) {}
于 2019-03-07T22:14:03.900 回答