When I first heard the idea that Java would be better off without null, I looked around for an explanation I could understand. Unfortunately, everything I found used code examples from languages without null. If I knew those languages, I wouldn’t be searching.
I wound up learning the “hard way” but now I figure I can answer the question using Java examples. Let’s jump right in.
Code Example 1
B are classes, not interfaces, so no Java class can extend both. There’s no way the
method() can return a meaningful answer. It could return
null, but not a real object. So let’s rename
Think about how you might implement
undefined(). Is it even possible to get this code to compile? If you replaced the method calls with
null, it would obviously work.
But methods can’t say they return
We’d need a type there instead. Ok, so what is the type of
null? Let’s look at another example.
Code Example 2
Here we’re using the same code “
throw new RuntimeException()” in two contexts that expect two incompatible types. Ok, so what is the type of “
throw new RuntimeException()” ?
The answer is: they’re both a form of the bottom type, which is logically a subtype of all other types. It’s the logical opposite of
Object, the top of the Java hierarchy. It’s a fairly weak form of the bottom type without a name. Let’s say we enhance Java by giving it the name
Nothing. This code would compile:
How can we fill in the implementation? We’ve already seen two ways:
return null, or
How else? We can recurse in an infinite loop.
How else? We can cast.
This code would compile but throw a
ClassCastException at runtime, so it’s logically the same thing as
throw new RuntimeException().
Of these choices, throwing is already really useful for pre-conditions. It’s idiomatic to call the method “error” when it takes a
Stringparameter, and “undefined” without any parameters.
Since Java doesn’t have the
Nothing type, we just use
void today. In the following code example,
Nothing is slightly more accurate than
void, but it doesn’t really matter since the return type isn’t used.
But the point of a bottom type is you can return it.
This is pretty much what Scala has. Its bottom type is called
Nothing. Haskell has a bottom type but leaves it unnamed. To see why, let’s look at my first question again.
Is it possible to get this code to compile? Yes, we can implement
undefined() without the
Nothing type, as follows.
In Java, we use
null for more than just indicating an error or from unreachable code. We also use
null to represent optionality.
map doesn’t contain
value will be
null. What about the reverse? Is it true that when
map doesn’t contain
key? Nope, because
map may contain the entry (
null), in which case we need to remember to check
In languages without
null, we need another way to represent optionality.
Now methods that sometimes return
null could be refactored to return
Option instead. For example,
Map.get(K) could return
None.get() throws, which is logically equivalent to calling a method on
null and getting a
NullPointerException. If that were the whole story,
Option wouldn’t be very useful, but it’s normal to add additional functionality. For example,
Option.getOrElse(T default) could return
None. Another example,
forEach(block) could execute the code block on
Some.value and just do nothing on
Putting it all together
The two concepts (bottom types and Options) come together in
None.get(). Since it never returns a real value, it could return the bottom type. The fact that None is generic on type
T makes the method look just like our earlier implementation of
undefined(). With type variance, we could make
None into a Singleton implementing
Map.get() could return the Singleton.
Option, we can get rid of null from the language completely. But should we? On the JVM, probably not. The
Optionwrapper objects waste a lot of memory. Let’s say we have a
Personclass with field
private final Address workAddresswhich may or may not exist. Replacing the
Option<Address>adds an extra 16 bytes per non-null workAddress (on a 64 bit JVM). Maybe this cost could be optimized away in a future version of the JVM but we’d pay it today.
However, there are big benefits of consistently using
Option instead of
null. Using the type system to reflect optionality communicates more about your API, plus it prevents common mistakes. It trades one kind of syntax cruft for another. I think it’s a clear win in languages with lambdas and so
Option will be more compelling with Java 8. You can see the difference with Scala today.
Scala and Haskell
Scala actually has two bottom types.
Nothing works just like the examples we’ve seen so far.
Scala can even infer
Nothing as the return type in this example.
def undefined: Nothing = throw new RuntimeException
def undefined = throw new RuntimeException
Scala has a second bottom type called
def undefined: Null = null
Null are both bottom types in Scala, and
Nothing is “below”
Null. In the Java version
public static <T> T undefined() we can’t differentiate between non-normal termination and
null. But in Scala we can, because it’s a compiler error to return
null from a method declared to return Nothing.
Haskell doesn’t have this problem because it doesn’t have any of the complicating factors we’ve gone over. Haskell doesn’t have type casts, primitives, or null! That means the bottom type can only recurse infinitely or throw. Without
null, there’s no need for two bottom types. In fact, Scala only names
Nothing to differentiate it from
Null. Without two bottom types, Haskell gets by without naming the bottom type (using the generics trick alone).
a is a generic type like Java’s
[Char] is a list of
Char, in other words, a
So in Haskell, the method signature
undefined :: a is enough to know with certainty that the body doesn’t return a value. Haskell’s type system is very strict and precise, so there are many examples like this where the type signature alone tells you everything you need to know about the function.
Future versions of Java will never eliminate
null, but remember that
Option is a good option.