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.
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.
@NotNull(or alike) annotations. Does, basically nothing, except adding some (false) feeling that
nullvalues 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 countless
nullchecks. 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 -
Maybemonad in some form.
Last point is what I'm going to discuss in more details.
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
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
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
Maybe- potentially can be empty.
- If some external library/call can return
nullvalue, result should be immediately wrapped into
Without any additional efforts strict following the convention above enables writing Java code which:
- never throws
NullPointerExceptionand have all
nullvalues handled properly.
- clearly and explicitly distinguishes nullable and non-nullable values in code and lets compiler enforce this distinction at compile time.