Monday, December 7, 2015

Avoiding nulls using Optional


Let me show you guys this code:

String audioSystemVersion = "UNKNOWN";
if (myCar != null) {
AudioSystem audioSystem = myCar.getAudioSystem();
if (audioSystem != null) { // the card has audio system.
audioSystemVersion = audioSystem.getVersion();
}
}
Is this boilerplate code familiar for you? Null checking everywhere.

To give some historical context, Tony Hoare - one of the giants of computer science - wrote, "I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn't resist the temptation to put in a null reference, simple because it was so easy to implement."

What can you do to prevent unintended null pointer exceptions ? You can be defensive and add checks to prevent null references like you see in the above code, or you can use one of the amazing stuffs of Java 8 like the Optional pattern. We are going directly to the action, let me show you some code!

  1. Creating Optional objects

    An empty Optional.

    Optional<AudioSystem> sc = Optional.empty();

    A non null Optional. If audioSystem were null, a NullPointerException would be thrown. Take it into account.

    AudioSystem audioSystem = new AudioSystem();
    Optional<AudioSystem> as = Optional.of(audioSystem);

    Finally, you can create an Optional from a null reference. If audioSystem were null, the resulting Optional would be an empty Optional.

    Optional<AudioSystem> as = Optional.ofNullable(audioSystem);
  2. Do something if value present

    Instead of this

    AudioSystem as = ....;
    if (as != null) {
    Sysmte.out.println(as);
    }

    better this (functional style).

    Optional<AudioSystem> as = ...;
    as.ifPresent(System.out::println);

    or you can use it the imperative way (not recommended)

    Optional<AudioSystem> audioSystem = ...;
    if(audioSystem.isPresent()){
    System.out.println(audioSystem.get());
    }
  3. Filter values using filter method

    Instead of this

    AudioSystem as = ...;
    if(as != null && "Pionner".equals(as.getBrand())){
    System.out.println(as);
    }
    better this (using the class Predicate).

    Optional<AudioSystem> maybeAudioSystem = ...;
    maybeAudioSystem.filter(as -> "Pionner".equals(as.getBrand())
    .ifPresent(as -> System.out.println(as));
  4. Extracting and transforming values using the map method

    Instead of this

    if(car != null){
    AudioSystem as = car.getAudioSystem();
    if(as != null && "Pionner".equals(as.getBrand()){
    System.out.println("ok");
    }
    }
    better this

    maybeCar.map(Car::getAudioSystem)
    .filter(as -> "Pionner".equals(as.getBrand())
    .ifPresent(() -> System.out.println("ok"));

The purpose of Optional is not to replace every single null reference in your codebase but rather to help design better APIs in which—just by reading the signature of a method—users can tell whether to expect an optional value. In addition, Optional forces you to actively unwrap an Optional to deal with the absence of a value; as a result, you protect your code against unintended null pointer exceptions.

Killing the nulls!

2 comments: