Null check chain vs catching NullPointerException
A web service returns a huge XML and I need to access deeply nested fields of it. For example:
return wsObject.getFoo().getBar().getBaz().getInt()
The problem is that getFoo()
, getBar()
, getBaz()
may all return null
.
However, if I check for null
in all cases, the code becomes very verbose and hard to read. Moreover, I may miss the checks for some of the fields.
if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();
Is it acceptable to write
try {
return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
return -1;
}
or would that be considered an antipattern?
Catching NullPointerException
is a really problematic thing to do since they can happen almost anywhere. It's very easy to get one from a bug, catch it by accident and continue as if everything is normal, thus hiding a real problem. It's so tricky to deal with so it's best to avoid altogether. (For example, think about auto-unboxing of a null Integer
.)
I suggest that you use the Optional
class instead. This is often the best approach when you want to work with values that are either present or absent.
Using that you could write your code like this:
public Optional<Integer> m(Ws wsObject) {
return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
.map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
.map(b -> b.getBaz())
.map(b -> b.getInt());
// Add this if you want to return an -1 int instead of an empty optional if any is null
// .orElse(-1);
// Or this if you want to throw an exception instead
// .orElseThrow(SomeApplicationException::new);
}
Why optional?
Using Optional
s instead of null
for values that might be absent makes that fact very visible and clear to readers, and the type system will make sure you don't accidentally forget about it.
You also get access to methods for working with such values more conveniently, like map
and orElse
.
Is absence valid or error?
But also think about if it is a valid result for the intermediate methods to return null or if that is a sign of an error. If it is always an error then it's probably better throw an exception than to return a special value, or for the intermediate methods themselves to throw an exception.
Maybe more optionals?
If on the other hand absent values from the intermediate methods are valid, maybe you can switch to Optional
s for them also?
Then you could use them like this:
public Optional<Integer> mo(Ws wsObject) {
return wsObject.getFoo()
.flatMap(f -> f.getBar())
.flatMap(b -> b.getBaz())
.flatMap(b -> b.getInt());
}
Why not optional?
The only reason I can think of for not using Optional
is if this is in a really performance critical part of the code, and if garbage collection overhead turns out to be a problem. This is because a few Optional
objects are allocated each time the code is executed, and the VM might not be able to optimize those away. In that case your original if-tests might be better.
I suggest considering Objects.requireNonNull(T obj, String message)
. You might build chains with a detailed message for each exception, like
requireNonNull(requireNonNull(requireNonNull(
wsObject, "wsObject is null")
.getFoo(), "getFoo() is null")
.getBar(), "getBar() is null");
I would suggest you not to use special return-values, like -1
. That's not a Java style. Java has designed the mechanism of exceptions to avoid this old-fashioned way which came from the C language.
Throwing NullPointerException
is not the best option too. You could provide your own exception (making it checked to guarantee that it will be handled by a user or unchecked to process it in an easier way) or use a specific exception from XML parser you are using.
As already pointed out by Tom in the comment,
Following statement disobeys the Law of Demeter,
wsObject.getFoo().getBar().getBaz().getInt()
What you want is int
and you can get it from Foo
. Law of Demeter says, never talk to the strangers. For your case you can hide the actual implementation under the hood of Foo
and Bar
.
Now, you can create method in Foo
to fetch int
from Baz
. Ultimately, Foo
will have Bar
and in Bar
we can access Int
without exposing Baz
directly to Foo
. So, null checks are probably divided to different classes and only required attributes will be shared among the classes.
上一篇: 避免空检查?