Handling of null
values is often considered a weak spot in Java. There are several reasons for it.
Most frequently mentioned issue is famous NullPointerException
, although there is no clear justification why this is an issue. After all, this is just a sign of the problem, but not the problem itself.
Actual problem is deeper. Those who have experience writing code with C or C++ know this issue much better. While coding in C/C++ engineer should always keep in mind countless nuances related to access safety, undefined behaviors, memory allocation, object copying/moving, and so on and so forth. All these numerous details create constant "pressure", consume precious engineer brain resources and result to slower development speed. Even hardcore C/C++ proponents can't deny this fact.
Similar problem exists with null
values in Java.
Java often considered verbose language. That's true. This is back side of its explicitness. Almost every intent in Java can be (and usually is) explicitly expressed in code. By adding some extra text we in turn get immediately accessible context, which don't need to be carried in head while reading Java code. The null
values break this rule. They don't manifest themselves in the code.
The null
values may appear for various reasons. It can be default initialization, value returned from some method or even explicitly assigned null
value. Keeping in mind that sometimes null
's may appear, checking method documentation or code for possibility to return such value - all these creates "pressure" very similar to described above for C/C++.
What can be done to solve this issue? How remove that constant "pressure" which affects our productivity?
Of course, there are more than one way to solve it.
We can:
- Add
@NotNull
(or alike) annotations. Does, basically nothing, except adding some (false) feeling thatnull
values are somehow handled. - Establish strict rules and check every value which potentially be
null
. It's working solution, but in expense of polluting code with countlessnull
checks. And, well, nobody is perfect, we can make mistakes and compiler will not help us. - Invent own language. Authors of Kotlin done just that. Again, this is working solution, but it's overkill, as for me. Nevertheless it has one important thing: nullable and non-nullable types are distinct and clearly expressed in the code.
- Use functional programming approaches -
Maybe
monad in some form.
Last point is what I'm going to discuss in more details.
Optional
: good, bad, ugly
Introduced in Java 8 Optional
class is targeted to handle missing values instead of null
. To some extent it is a Java implementation of Maybe
monad (but not completely, see below).
While some can consider this as just fancy way to work with null
values, it actually has far more important benefit: explicit expression of intent. Once you wrap field, parameter or variable into Optional
you immediately making your intent explicit and clear to the reader. In other words - you're adding that missing context. In the same time compiler starts seeing difference, you're not alone anymore.
So, by using Optional
engineer kills three birds with one stone:
- Makes potentially missing value explicit in code and removes "pressure" from reader.
- Enables compiler to control access to the value.
- Makes handling of
null
values convenient.
Unfortunately Optional
has its own issues.
As mentioned above, Optional
"to some extent" implementation of Maybe
monad. It makes reverences to imperative world and has externally accessible value. By calling get()
method you can try to retrieve it. But if you do, it may throw NullPointerException
. Which defies whole purpose of the Optional
- be safe container for potential null
(i.e. missing) values.
Perhaps this is why some smart heads suggests that "using Optional
for fields and parameters is bad practice" and static analysis tools show warnings for such usage of Optional
.
The solution might be to write your own version of Maybe
and use it instead. There are several different implementations around. I'm using one from my Reactive Toolbox Core library.
Summary
The Optional
/Option
/Maybe
enables Java engineers to use following convention:
- Every variable/field/parameter with plain type is not null and never can be.
- Every variable/field/parameter whose type is wrapped into
Optional
/Option
/Maybe
- potentially can be empty. - If some external library/call can return
null
value, result should be immediately wrapped intoOptional
/Option
/Maybe
.
Without any additional efforts strict following the convention above enables writing Java code which:
- never throws
NullPointerException
and have allnull
values handled properly. - clearly and explicitly distinguishes nullable and non-nullable values in code and lets compiler enforce this distinction at compile time.
Top comments (4)
So in this sense, basically we can see only one type: Option, everything, Object field, parameter, return value. The only place we can use the plain type is method body.
It sounds overkilled.
checkerframework.org/ is pretty verbose and force you to mark methods/fields with nullable/notnullable but can be integrated in compilation process and won't let you to miss potential NPE.
Thanks for the reference, I'll definitely take a look.
Didn't get it. Why we should see Option everywhere? If we sure that value can't be null, then we use plain type. It can be either, method body, parameter or field.