Optional - Bye, bye NPE

Null is not longer an option

All his fault

The Invention of Null

  • Tony Hoare invented null
  • Ever since he regrets it

„It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.“

The Challenge

Null, so famous and so misunderstood

What is Null?

  • Keyword that expresses special state
  • Keyword that expresses special value
  • Indicates that a variable has not been initialized
  • Indicates that no instance exists
public String dup(final String foo)
{
    String result = null;
    
    if (foo != null)
    {
        result = foo + foo;
    }
    
    return result;
}

When is it used?

  • Initial state
  • Missing value
  • Removed value
  • Ambiguous return value
public String get(final String key)
{
    // hashmap
    return map.get(key);
}

Back to the Museum

Let's extend the Vehicle

  • Add ability to lend it
    • A car can be lent to zero or one other museum
  • Add related cars
    • A car can relate to zero, one, or many other cars
public class Vehicle
{
    private String brand;
    private int age;
    private int value;
    private boolean restored; 
    
    private List<Vehicle> related;
    private Museum lentTo;
    
    public Vehicle(String brand, int age, int value)
    {
        this.brand = brand;
        this.age = age;
        this.value = value;
        this.restored = false;
    }
}

The Challenges

public class Vehicle
{
    // ...
    
    private List<Vehicle> related;
    private Museum lentTo;
    
    public boolean isLent()
    {
        return lentTo != null;
    }
    
    public Museum lentTo()
    {
        return lentTo;
    }
    
    public Museum lentTo(Museum museum)
    {
        Museum old = lentTo;
        this.lentTo = museum;
        
        return old;
    }
    
    public List<Vehicle> isRelatedTo()
    {
        return related;
    }
}
  • Is null representing that it has not been lent out?
  • Does null mean there is no relation to other vehicles?
  • What does an empty relatedTo ArrayList mean? Imagine removing a relation.

Try the code

// First Attempt
// Find all cars that have been lent to museum X
public List<Vehicle> findLentTo(Museum from, Museum to)
{
    return from.vehicles.stream()
                    .filter(v -> v.isLent() && v.lentTo().equals(to))
//                  .filter(v -> {
//                      final Museum m = v.lentTo();
//                      return m != null && m.equals(to);
//                  })
                    .collect(Collectors.toList());
}
// cheap fix
public class Museum
{
    public static final Museum NONE = new Museum("");
    
    public final String name;
    public List<Vehicle> vehicles = new ArrayList<>();
    
    public Museum(String name)
    {
        this.name = name;
    }
}

public class Vehicle
{
    private Museum lentTo = Museum.NONE;
    
    ...
}
// second attempt
public List<Vehicle> findLentTo(final Museum from, final Museum to)
{
    return from.vehicles.stream()
                    .filter(v -> v.lentTo().equals(to))
                    .collect(Collectors.toList());
}
// Now imagine we would list all museums
// that have vehicles lent from X
public Set<Museum> allLentTo(final Museum from)
{
    // More boilerplate, just to deal with the special state
    return from.vehicles.stream()
                    .map(v -> v.lentTo())
                    .filter(m -> m != Museum.NONE)
                    .collect(Collectors.toSet());
}
  • There is always a case not covered that needs special handling somewhere.
  • We waste a little memory for our NONE
  • We need something that expresses none or empty.

Optional to the Rescue

Express nothing more explicitly

  • java.util.Optional<T>
  • Gives us the state that null does not have
  • Wraps objects or when empty, expresses the missing value without using null
  • Has only two states: empty or non-empty
  • null cannot be held
// Create an Optional from a non-null value
// Complains, when museum is null and hence indicates a programming mistake
Optional<Museum> lentTo = Optional.of(museum);

// Create an Optional and permit null BUT will treat it as empty when you ask.
// You will never get the null back!
Optional<Museum> lentTo = Optional.ofNullable(museum);
// Use the Optional and access the value
final Optional<Museum> museum = vehicle.lentTo();
if (museum.isPresent())
{
    // Optional.get() will protest with NoSuchElementException
    // when called on an empty Optional
    System.out.println(museum.get().getName());
}

Getting back to the Examples

// Find all vehicles lent to X
// First non-Optional attempt
public List<Vehicle> findLentTo(Museum from, Museum to)
{
    return from.vehicles.stream()
                    .filter(v -> v.isLent() && v.lentTo().equals(to))
                    .collect(Collectors.toList());
}
// The same with Optional
public List<Vehicle> findLentTo(Museum from, Museum to)
{
    return from.vehicles.stream()
                    .filter(v -> v.lentTo().filter(m -> m.equals(to)).isPresent())
                    .collect(Collectors.toList());
}
// Java 9 to the rescue
public List<Vehicle> findLentTo(Museum from, Museum to)
{
    // Java 9 gets streams at Optionals to flatten them easily
    return from.vehicles.stream()
                    .flatMap(v -> v.lentTo().filter(m -> m.equals(to)).stream())
                    .collect(Collectors.toList());
}
// Get us all museums
// the version with the placeholder for null
public Set<Museum> allLentTo(final Museum from)
{
    // More boilerplate, just to deal with the special state
    Set<Museum> museums = from.vehicles.stream()
                    .map(v -> v.lentTo())
                    .filter(m -> m != Museum.NONE)
                    .collect(Collectors.toSet());
    ...                        
}
// The Optional versions
public Set<Museum> allLentTo(final Museum from)
{
    final Set<Museum> museums1 = from.vehicles.stream()
                    .map(v -> v.lentTo())
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .collect(Collectors.toSet());
    
    final Set<Museum> museums2 = from.vehicles.stream()
                    .flatMap(
                        v -> v.lentTo().isPresent() ? 
                            Stream.of(v.lentTo().get()) : Stream.empty())
                    .collect(Collectors.toSet());

    final Set<Museum> museums3 = from.vehicles.stream()
                    .flatMap(
                        v -> v.lentTo().map(Stream::of).orElseGet(Stream::empty))
                    .collect(Collectors.toSet());

    // Java 9
    final Set<Museum> museums4 = from.vehicles.stream()
                    .flatMap(v -> v.lentTo().stream())
                    .collect(Collectors.toSet());
}

More Examples

// Creation
Optional<String> s1 = Optional.empty();
Optional<String> s2 = Optional.of("foo");
Optional<String> s3 = Optional.of(null); // NPE!!
Optional<String> s4 = Optional.ofNullable("foo");
Optional<String> s5 = Optional.ofNullable(null); // same as empty()
// Get a value
// This API is against the basic concept and might be deprecated again.
Optional<String> s1 = Optional.empty();
Optional<String> s2 = Optional.of("foo");

Assert.assertEquals(null, s1.get()); // NoSuchElementException
Assert.assertEquals("foo", s2.get());
// Filter and Map
public boolean isValidAge(final Vehicle vehicle) 
{
    return Optional.ofNullable(vehicle)
        .map(Vehicle::getAge)
        .filter(p -> p >= 0)
        .filter(p -> p <= 100)
        .isPresent();
}
// flatMap
public int vehiclesAtLentToMuseum(final Vehicle vehicle) 
{
    return Optional.ofNullable(vehicle)
        .flatMap(Vehicle::lentTo)
        .map(m -> m.vehicles.size())
        .orElse(0);
}
// Do when not null
public void test(String name)
{
    // Old
    if (name != null)
    {
        System.out.println(name.length());
    }    

    // New fancy way
    // Obviously name should be passed as Optional :) in the first place
    Optional.of(name).ifPresent(s -> System.out.println(s.length()));
}
// Do or else
public String test(String name)
{
    // Old
    if (name != null)
    {
        return name;
    }   
    else
    {
        return ""; 
    }
    
    // New fancy way
    return Optional.ofNullable(name).orElse("");
    
    // Or extra efficient with a supplier
    return Optional.ofNullable(name).orElseGet(String::new);
}

Conclusion

What did we learn today?

  • Optional makes clear that something is "optional"
  • Enforces compiler support
  • Forces you to use a second API to ask the "exists" or "contains" questions instead of reading the bones of null
  • Optional makes it clear that null is a bug
  • Optional can help design better APIs
  • Optional does not express null
  • Optional does not replace null
  • Optional<?> o = null is sadly possible
  • You cannot rewrite an API easily with Optional and keep the notion the same
  • Optional can make things better, but also might create less understandable code
  • Optional cannot be serialized