No More Excuses to Use Null References in Java 8
Join the DZone community and get the full member experience.
Join For FreeUnfortunately 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]
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); } }
public interface Predicate<T> { boolean test(T t); }
public interface Func1<A1, R> { R apply(A1 arg1); }
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; } }
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; } }
int readPositiveIntParam(Map<String, String> params, String name) { // TODO ... }
@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")); }
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; }
int readPositiveIntParam(Map<String, String> params, String name) { return asOption(params.get(name)) .flatMap(FunctionUtils::stringToInt) .filter(i -> i > 0) .getOrElse(0); }
import static Option.*; public class FunctionUtils { public static Option<Integer> stringToInt(String s) { try { return some(Integer.parseInt(s)); } catch (NumberFormatException nfe) { return none(); } } }
Opinions expressed by DZone contributors are their own.
Comments