A scala style Option class for Java null idioms (Improved)

2011-08-02

Most of us are well aware that null is a "bad" thing. And we probably all read the 2009 "apology" from the null-Inventor Tony Hoare (see Null References: The Billion Dollar Mistake).

In Java we (still?) have null. But you as a programmer should not program with null-logic. In other words don't make developers pass null to your methods or have your methods return null.

It is obviously very much influenced by the Option and Maybe monads (from Scala and Haskell respectively). The class below is not a Monad, but handles just as many of the null idioms:

  • When value is null, use a default , otherwise get value
  • When value is null, throw an exception, otherwise you the value
  • When value is null, throw an exception, otherwise map the value to another value
  • When value is null, do nothing, otherwise map the value to another value< Well, as far as you can get without higher order functions and other goodies...
package util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Option<T> {

  private T value;

  protected Option(T value) {
    this.value = value;
  }

  protected Option() {
  }

  private boolean hasValue() {
    return value != null;
  }

  private T getValue() {
    return value;
  }

  public <f extends T> T getValueOr(F fallBackValue) {
    return hasValue() ? value : fallBackValue;
  }

  public T assertAndGet() {
    return assertAndGet("expected a value");
  }

  public T assertAndGet(String message) {
    if (value == null) throw new IllegalStateException(message);
    return value;
  }


  public <m , E extends Exception> M mapValueOrThrowException(Mapper<t , M> mapper, Class<e> e, String message) throws E {
    if (!hasValue())
      throwException(e, message);
    return mapper.map(value);
  }

  public <m> Option</m><m> map(Mapper<t , M> mapper) {
    if (!hasValue())
      return new Option<m>();
    return new Option</m><m>(mapper.map(value));
  }

  public void doWith(DoWith<t> doWith) {
    if (hasValue())
      doWith.run(value);
  }


  public interface Mapper<f , T> {
    T map(F f);
  }

  public interface DoWith<t> {
    void run(T value);
  }

  public <e extends Exception> T getValueOrThrowException(Class</e><e> e, String message) throws E {
    if (hasValue())
      return value;
    return throwException(e, message);
  }

  private </e><e extends Exception> T throwException(Class</e><e> e, String message) throws E {
    final E exception;
    try {
      final Constructor</e><e> constructor = e.getConstructor(String.class);
      exception = constructor.newInstance(message);
      throw exception;
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e1) {
      throw new RuntimeException(e1);

    } catch (NoSuchMethodException e1) {
      throw new RuntimeException(new StringBuilder().append("Class ").append(e.getName()).append(" does not have a constructor taking a String").toString(), e1);

    }
  }
}

There are two two other helpful classes None a Some

public class None<t> extends Option</t><t> {

    private static None instance = new None();

    public static None none() {
        return instance;
    }


    protected None() {
        super();
    }
}



public class Some</t><t> extends Option</t><t> {
    public Some(T value) {
        super(value);
    }
}

Here is some code using the classes from above:

final Option<course> courseOption= repository.getCourseById(courseCode);
    courseOption.getValueOrThrowException(CourseRegistrationException.class,"Unknown course " + courseCode)

And an example that uses the map function:

public CourseBookingInfo getCourseBookingInfo(String code) throws CourseRegistrationException {
    final Option</course><course> courseOption = repository.getCourseByCode(code);
    return courseOption.mapValueOrThrowException(new Option.Mapper</course><course , CourseBookingInfo>() {
      @Override
      public CourseBookingInfo map(Course course) {
        return new CourseBookingInfo(course.getCode(), course.getNumberOfAvailableSeats());
      }
    },CourseRegistrationException.class, "Unknown course " + code );
}
This article does not necessarily reflect the technical opinion of EDC4IT, but purely of the writer. If you want to discuss about this content, please use thecontact ussection of the site