Java:避免在嵌套类中检查空值(深度空值检查)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10391406/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java: avoid checking for null in nested classes (Deep Null checking)
提问by llappall
Imagine I have a class Family. It contains a List of Person. Each (class) Person contains a (class) Address. Each (class) Address contains a (class) PostalCode. Any "intermediate" class can be null.
想象一下,我有一个类家庭。它包含一个人员列表。每个(类)Person 包含一个(类)地址。每个(类)地址都包含一个(类)邮政编码。任何“中间”类都可以为空。
So, is there a simple way to get to PostalCode without having to check for null in every step? i.e., is there a way to avoid the following daisy chaining code? I know there's not "native" Java solution, but was hoping if anyone knows of a library or something. (checked Commons & Guava and didn't see anything)
那么,是否有一种简单的方法可以访问 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!!!
}
}
}
}
}
No, can't change the structure. It's from a service I don't have control over.
不,不能改变结构。它来自我无法控制的服务。
No, I can't use Groovy and it's handy "Elvis" operator.
不,我不能使用 Groovy,它是方便的“Elvis”运算符。
No, I'd prefer not to wait for Java 8 :D
不,我不想等待 Java 8 :D
I can't believe I'm the first dev ever to get sick 'n tired of writing code like this, but I haven't been able to find a solution.
我不敢相信我是有史以来第一个厌倦编写这样的代码的开发人员,但我一直无法找到解决方案。
Ideas?
想法?
Thanks
谢谢
--
llappall
--
拉帕
回答by amit
Your code behaves the same as
您的代码的行为与
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
}
Thanks to short circuiting evaluation, this is also safe, since the second condition will not be evaluated if the first is false, the 3rd won't be evaluated if the 2nd is false,.... and you will not get NPE because if it.
由于短路评估,这也是安全的,因为如果第一个条件为假,则不会评估第二个条件,如果第二个为假,则不会评估第三个条件,....并且您将不会获得 NPE,因为如果它。
回答by Robert
You can use for:
您可以用于:
product.getLatestVersion().getProductData().getTradeItem().getInformationProviderOfTradeItem().getGln();
optional equivalent:
可选等效项:
Optional.ofNullable(product).map(
Product::getLatestVersion
).map(
ProductVersion::getProductData
).map(
ProductData::getTradeItem
).map(
TradeItemType::getInformationProviderOfTradeItem
).map(
PartyInRoleType::getGln
).orElse(null);
回答by Paul Tomblin
The closest you can get is to take advantage of the short-cut rules in conditionals:
您可以获得的最接近的是利用条件中的快捷规则:
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!!!
}
By the way, catching an exception instead of testing the condition in advance is a horrible idea.
顺便说一句,捕捉异常而不是提前测试条件是一个可怕的想法。
回答by charleyc
Instead of using null, you could use some version of the "null object" design pattern. For example:
您可以使用某种版本的“空对象”设计模式,而不是使用 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
Then your if statement can become:
那么你的 if 语句可以变成:
if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...}
It's suboptimal since:
这是次优的,因为:
- You're stuck making NULL objects for every class,
- It's hard to make these objects generic, so you're stuck making a null-object version of each List, Map, etc that you want to use, and
- There are potentially some funny issues with subclassing and which NULL to use.
- 你被困在为每个类创建 NULL 对象,
- 很难使这些对象通用,因此您不得不为要使用的每个 List、Map 等创建空对象版本,并且
- 子类化和使用哪个 NULL 可能存在一些有趣的问题。
But if you really hate your == null
s, this is a way out.
但如果你真的讨厌你的== null
s,这是一个出路。
回答by Amit Kumar Gupta
If, in case, you are using java8 then you may use;
如果,以防万一,您使用的是 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
REF:避免空检查
回答by darioo
Although this post is almost five years old, I might have another solution to the age old question of how to handle NullPointerException
s.
虽然这篇文章已经快五年了,但对于如何处理NullPointerException
s 的古老问题,我可能有另一种解决方案。
In a nutshell:
简而言之:
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");
}
Since there is a lot of legacy code still in use, using Java 8 and Optional
isn't always an option.
由于仍有许多遗留代码仍在使用,因此使用 Java 8Optional
并不总是一种选择。
Whenever there are deeply nested classes involved (JAXB, SOAP, JSON, you name it...) and Law of Demeterisn't applied, you basically have to check everything and see if there are possible NPEs lurking around.
每当涉及深度嵌套的类(JAXB、SOAP、JSON,等等)并且没有应用Demeter 法则时,您基本上必须检查所有内容并查看是否有可能的 NPE 潜伏。
My proposed solution strives for readibility and shouldn't be used if there aren't at least 3 or more nested classes involved (when I say nested, I don't mean Nested classesin the formal context). Since code is read more than it is written, a quick glance to the left part of the code will make its meaning more clear than using deeply nested if-else statements.
我提出的解决方案力求可读性,如果不涉及至少 3 个或更多嵌套类,则不应使用(当我说嵌套时,我不是指正式上下文中的嵌套类)。由于代码读得比写得多,快速浏览一下代码的左侧部分会使它的含义比使用深层嵌套的 if-else 语句更清楚。
If you need the else part, you can use this pattern:
如果你需要 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");
}
Certain IDEs will break this formatting, unless you instruct them not to (see this question).
某些 IDE 会破坏这种格式,除非您指示他们不要这样做(请参阅此问题)。
Your conditionals must be inverted - you tell the code when it should break, not when it should continue.
你的条件必须被反转——你告诉代码它什么时候应该中断,而不是什么时候应该继续。
One more thing - your code is still prone to breakage. You must use if(family.getPeople() != null && !family.getPeople().isEmpty())
as the first line in your code, otherwise an empty list will throw a NPE.
还有一件事 - 您的代码仍然容易损坏。您必须if(family.getPeople() != null && !family.getPeople().isEmpty())
在代码的第一行使用,否则空列表将抛出 NPE。
回答by Pierre D
I was just looking for the same thing (my context: a bunch of automatically created JAXB classes, and somehow I have these long daisy-chains of .getFoo().getBar()...
. Invariably, once in a while one of the calls in the middle return null, causing NPE.
我只是在寻找相同的东西(我的上下文:一堆自动创建的 JAXB 类,不知何故我有这些长菊花链.getFoo().getBar()...
。总是,偶尔中间的一个调用返回 null,导致 NPE。
Something I started fiddling with a while back is based on reflection. I'm sure we can make this prettier and more efficient (caching the reflection, for one thing, and also defining "magic" methods such as ._all
to automatically iterate on all the elements of a collection, if some method in the middle returns a collection). Not pretty, but perhaps somebody could tell us if there is already something better out there:
我不久前开始摆弄的东西是基于反思的。我相信我们可以使这个更漂亮和更高效(一方面,缓存反射,并且还定义“魔术”方法,例如._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);
}
}
回答by Mattias Isegran Bergander
If it is rare you could ignore the null
checks and rely on NullPointerException
. "Rare" due to possible performance problem (depends, usually will fill in stack trace which can be expensive).
如果很少见,您可以忽略null
检查并依赖NullPointerException
. 由于可能的性能问题而“罕见”(取决于,通常会填充可能很昂贵的堆栈跟踪)。
Other than that 1) a specific helper method that checks for null to clean up that code or 2) Make generic approach using reflection and a string like:
除此之外 1) 检查 null 的特定辅助方法以清理该代码或 2) 使用反射和字符串创建通用方法,例如:
checkNonNull(family, "people[0].address.postalcode")
Implementation left as an exercise.
实施留作练习。
回答by MByD
Not such a cool idea, but how about catching the exception:
这不是一个很酷的主意,但是如何捕获异常:
try
{
PostalCode pc = people.get(0).getAddress().getPostalCode();
}
catch(NullPointerException ex)
{
System.out.println("Gotcha");
}
回答by Keith Klingeman
and my favorite, the simple try/catch, to avoid nested null checks...
和我最喜欢的,简单的 try/catch,以避免嵌套的空检查......
try {
if(order.getFulfillmentGroups().get(0).getAddress().getPostalCode() != null) {
// your code
}
} catch(NullPointerException|IndexOutOfBoundsException e) {}