The NullPointerException
is the
most frequent error
in Java applications.
It seems to be easy to find through its stack trace, and also easy
to fix by a simple if (x != null)
condition.
But reality quite often plays a different game than the developer.
Essentially I would call it dangerous to fix any NullPointerException
by a simple if-condition.
This could hide conceptual bugs that are not so easy to see.
When there is no assert and no comment about it, you really should investigate why the thing is null.
Hard to Find?
Sometimes it is really hard to see, even when you have a stack trace. Look at following example.
Autoboxing is one of the most hated Java features, introduced in 1.5. It may make the life of certain programmers easier, but it imposes a constant threat to all. I fixed many such pitfalls, but still need a long time to recognize this bug. (Personally I decided to not use autoboxing, so I am not used to its failures.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** */ public class NullPointerFromAutoBoxing { public static void main(String[] args) { new NullPointerFromAutoBoxing().run(null); } void run(Boolean autoBoxed) { dispatchParameter(autoBoxed); } private void dispatchParameter(boolean justTrueOrFalse) { } } |
Would you expect that this code throws a NullPointerException
?
It does.
Exception in thread "main" java.lang.NullPointerException at exceptions.NullPointerFromAutoBoxing.run(NullPointerFromAutoBoxing.java:10) at exceptions.NullPointerFromAutoBoxing.main(NullPointerFromAutoBoxing.java:6)
What happens here?
The programmer didn't mind about the difference between
primitive boolean
and class Boolean
,
because auto-boxing
promised that
Java will care for that.
But it can do that just for Boolean
objects that are not null
.
Thus, in the run()
method on line 10,
Java tries to convert the parameter autoBoxed
(which is null
)
to a primitive boolean
and fails,
because primitive boolean
doesn't have a representation for null,
it can be just true
or false
.
The programmer did nothing wrong here, it is Java that fails. So consider if you still want to use autoboxing.
Prevent NullPointerException
When you call a method on an object that you would not expect ever to be null, then you should tell this in a comment. Making no statement about it causes long investigations when it actually happens.
final Runnable closure = ....; closure.run(); // must throw NPE when null
That way you can point to the fact that it is a fatal error when closure
is null
.
Even better would be to explain how and where to fix the problem when happening.
Here is the advanced version:
final Runnable closure = ....; assert closure != null : "The creation of closure failed, check its factory!"; closure.run();
Asserts have to be switched on explicitly via -ea
("enable asserts") when running the Java virtual machine. That may not be the case when the application is deployed. Thus the safest version is following:
final Runnable closure = ....; if (closure == null) throw new IllegalStateException("The creation of closure failed, check its factory!"); closure.run();
Conclusion
The null-problem is quite old, once called the
billion-dollar-mistake.
I do not consider it to be a bug.
It was the right decision to leave this up to the developer.
Suppressing a NullPointerException
would mean obscuring a programming mistake.
It is interesting how new languages like Kotlin try to get around it. Things like "Elvis Operators", "Safe Call Operators", or "!!", make our code even more unreadable, but marking parameters and variables as not-allowed-to-contain-null absolutely makes sense ("Non-Null-Type"). Also TypeScript provides such an operator. Would be nice to have this in Java too.
Keine Kommentare:
Kommentar veröffentlichen