No More Excuses to Use Null References in Java 8
Join the DZone community and get the full member experience.
Join For FreeTony Hoare introduced null references in ALGOL W back in 1965 “simply because it was so easy to implement”. After many years he regretted his decision calling it "my billion dollar mistake".
Unfortunately the vast majority of the languages created in the last decades have been built with the same wrong design decision so language designers and software engineers started to look for workarounds to avoid the infamous NullPointerException.
Functional languages like Haskell or Scala structurally resolve this problem by wrapping the nullable values in an Option/Maybe monad. Other imperative languages like Groovy introduced a null-safe dereferencing operator (?. operator) to safely navigate values that could be potentially null. A similar feature has been proposed (and then discarded) as part of the project Coin in Java 7.
Honestly I don't miss a null safe dereferencing operator in Java even because I can imagine that the majority of developers would start abusing it "just in case". Moreover, since the upcoming Java 8 will have lambda expressions, it will be straightforward to implement an Option monad that, as I hope to show in the remaining part of the post, is a far more powerful and flexible construct.
I don't want to delve in category theory and explain what a monad is, even because there are already tons of very goodarticlesdoing this. My purpose is to quickly implement an Option monad using the Java 8 lambda expression syntax and then show how to use it with a very practical example. In Scala, a monad M is any class having the following 3 methods:
Unfortunately the vast majority of the languages created in the last decades have been built with the same wrong design decision so language designers and software engineers started to look for workarounds to avoid the infamous NullPointerException.
Functional languages like Haskell or Scala structurally resolve this problem by wrapping the nullable values in an Option/Maybe monad. Other imperative languages like Groovy introduced a null-safe dereferencing operator (?. operator) to safely navigate values that could be potentially null. A similar feature has been proposed (and then discarded) as part of the project Coin in Java 7.
Honestly I don't miss a null safe dereferencing operator in Java even because I can imagine that the majority of developers would start abusing it "just in case". Moreover, since the upcoming Java 8 will have lambda expressions, it will be straightforward to implement an Option monad that, as I hope to show in the remaining part of the post, is a far more powerful and flexible construct.
I don't want to delve in category theory and explain what a monad is, even because there are already tons of very goodarticlesdoing this. My purpose is to quickly implement an Option monad using the Java 8 lambda expression syntax and then show how to use it with a very practical example. In Scala, a monad M is any class having the following 3 methods:
def map[B](f: A => B): M[B] def flatMap[B](f: A => M[B]): M[B] def filter(p: A => Boolean): M[A]
In particular you can think to an Option monad as a wrapper around a, possibly absent, value. So an Option of a generic type A could be define as it follows:
import java.util.functions.Predicate; public abstract class Option<A> { public static final None NONE = new None(); public abstract <B> Option<B> map(Func1<A, B> f); public abstract <B> Option<B> flatMap(Func1<A, Option<B>> f); public abstract Option<A> filter(Predicate<? super A> predicate); public abstract A getOrElse(A def); public static <A> Some<A> some(A value) { return new Some(value); } public static <A> None<A> none() { return NONE; } public static <A> Option<A> asOption(A value) { if (value == null) return none(); else return some(value); } }
I also added some convenient factory methods for the Some and None concrete implementations of Option that I will implement later. Here Predicate is a single method interface defined in the new java.util.functions package:
public interface Predicate<T> { boolean test(T t); }
that is used to determine if the input object matches a given criteria, while Func1 is another single method interface:
public interface Func1<A1, R> { R apply(A1 arg1); }
that I defined to represent a more generic function of one argument of type A1 returning a result of type R.
The abstract class Option has then two concrete implementations, one representing the absence of a value (something that we are used to wrongly model with the infamous null reference):
The abstract class Option has then two concrete implementations, one representing the absence of a value (something that we are used to wrongly model with the infamous null reference):
public class None<A> extends Option<A> { None() { } @Override public <B> Option<B> map(Func1<A, B> f) { return NONE; } @Override public <B> Option<B> flatMap(Func1<A, Option<B>> f) { return NONE; } @Override public Option<A> filter(Predicate<? super A> predicate) { return NONE; } @Override public A getOrElse(A def) { return def; } }
and the other wrapping an actually existing value:
public class Some<A> extends Option<A> { private final A value; Some(A value) { this.value = value; } @Override public <B> Option<B> map(Func1<A, B> f) { return some(f.apply(value)); } @Override public <B> Option<B> flatMap(Func1<A, Option<B>> f) { return f.apply(value); } @Override public Option<A> filter(Predicate<? super A> predicate) { if (predicate.test(value)) return this; else return None.NONE; } @Override public A getOrElse(A def) { return value; } }
Now, to try to put the Option at work with a concrete example, let's suppose we have a Map<String, String> representing a set of named parameters with the corresponding values. We want to develop the method
int readPositiveIntParam(Map<String, String> params, String name) { // TODO ... }
that, if the value associated with a given key is a String representing a positive integer returns that integer, but returns zero in all other case. In other words we want the following test to pass:
@Test public void testMap() { Map<String, String> param = new HashMap<String, String>(); param.put("a", "5"); param.put("b", "true"); param.put("c", "-3"); // the value of the key "a" is a String representing a positive int so return it assertEquals(5, readPositiveIntParam(param, "a")); // returns zero since the value of the key "b" is not an int assertEquals(0, readPositiveIntParam(param, "b")); // returns zero since the value of the key "c" is an int but it is negative assertEquals(0, readPositiveIntParam(param, "c")); // returns zero since there is no key "d" in the map assertEquals(0, readPositiveIntParam(param, "d")); }
If we couldn't rely on our Option we should accomplish this task with something similar to this:
int readPositiveIntParam(Map<String, String> params, String name) { String value = params.get(name); if (value == null) return 0; int i = 0; try { i = Integer.parseInt(value); } catch (NumberFormatException nfe) { } if (i < 0) return 0; return i; }
too many conditional branches and returning points, isn't it? Using the Option monad we can achieve the same result with a single fluent statement:
int readPositiveIntParam(Map<String, String> params, String name) { return asOption(params.get(name)) .flatMap(FunctionUtils::stringToInt) .filter(i -> i > 0) .getOrElse(0); }
where we used an helper static method FunctionUtils.stringToInt() as a function literal, with the :: syntax also introduced in Java 8, defined as:
import static Option.*; public class FunctionUtils { public static Option<Integer> stringToInt(String s) { try { return some(Integer.parseInt(s)); } catch (NumberFormatException nfe) { return none(); } } }
This methods tries to convert a String in an int and, if it can't, it returns the None Option. Note that we could also define this behavior inline, while invoking the flatMap() method, using an anonymous lambda expression, but my advice is to develop a small library of utility functions, as I started doing here, in order to leverage the grater reusability allowed by functional programming. I think the comparison of the two readPositiveIntParam methods I provided illustrates well how the extensive use of the Option monad can finally allow us to write completely NullPointerException free software and, more in general, how a bigger employment of functional programming can dramatically reduce its cyclomatic complexity.
Java (programming language)
Opinions expressed by DZone contributors are their own.
Comments