Null is not longer an option
The Invention of Null
„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.“
It became an API... it came to stay.
Null, so famous and so misunderstood
public String dup(final String foo)
{
String result = null;
if (foo != null)
{
result = foo + foo;
}
return result;
}
public String get(final String key)
{
// hashmap
return map.get(key);
}
Null is flexible but error-prone.
Let's extend the Vehicle
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;
}
}
Ok... plain and ugly code.
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;
}
}
null
representing that it has not been lent out?null
mean there is no relation to other vehicles?ArrayList
mean? Imagine removing a relation.And we have not used the code yet.
// 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());
}
NONE
There is always that one case...
Express nothing more explicitly
java.util.Optional<T>
null
does not havenull
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());
}
But that is not really better, isn't it?
// 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());
}
Yeah... not really convincing...
// 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);
}
Just more examples
What did we learn today?
Optional
makes clear that something is "optional"null
Optional
makes it clear that null
is a bugOptional
can help design better APIsOptional
does not express null
Optional
does not replace null
Optional<?> o = null
is sadly possibleOptional
and keep the notion the sameOptional
can make things better, but also might create less understandable codeOptional
cannot be serializedOptional is optional and hard to get and accept.