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, simply because it was so easy to implement.
In reality, 1 billion dollars is a huge understatement of the financial damage caused by null references.
Most developers spend a comically large amount of time chasing null pointers.
…but you don’t have to!
This one secret will save you time and money–more than the cost of this course!
Many will tell you that null pointers aren’t a problem if you’re a good programmer.
final User user = getUser(10);
final String name;
if(user !=null){
name = user.getName();
}else{
name = "Not Found";
}
Problem: Every method could, possibly, return a null value, so to be safe you need to check the result of every method.
The “good programmers” don’t do this, though. They “know” which methods need to be tested…which is why they, too, get null pointer exceptions.
Problem: If you don’t want to test every method, you need to read the documentation for methods to determine which ones can return null.
The “good programmers” don’t do this either…which is why they, too, get null pointer exceptions.
Problem: If you do read the documentation, it is imperative that the documentation is correct and complete.
The “good programmers” don’t write and update documentation sufficiently, though…which is why they, too, get null pointer exceptions.
Of course, the “good programmers” always have excuses for why their null pointer problems are not their fault and continue to have null pointer problems.
Meanwhile, the “incompetent” programmer acknowledges his or her inability to cope with complex systems and asks the compiler for help.
public Optional<User> getUser(int id){
if(userExists(id)){
return Optional.of(new User(id));
}else{
return Optional.empty();
}
}
Now, this won’t even compile!
final User user = getUser();
The compiler complains, because the return value is not a User. But we need an User…
final Optional<User> maybeUser = getUser(10);
final String name;
if(maybeUser.isPresent()){
name = maybeUser.get().getName();
}else{
name = "Not Found";
}
Did we just do the same thing as the good programmer?
Sort of. There is one immediate difference, though: Now we are forced by the compiler to address the possibility of no value being returned.
This is already a big gain with only slightly more code needed than the good programmer’s approach.
If your team is on board with never returning null values (which is significantly easier than avoiding null values returned from methods unexpectedly), then you can use Optional values to eliminate null pointer exceptions from your code.
Java, unfortunately, does not prevent you from returning a null value even if the return type is an Optional value.
You should have used Haskell.
Another bonus to this approach is that the return type itself documents the fact that the method may not return a value. Even better, if this documentation changes, the compiler will force you to rethink your use of the method!
Of course, you could just assume that the method returns a value and write:
final String name = getUser(10).get().getName();
Which isn’t any better than
final String name = getUser(10).getName()
Using the old code.
The big difference, though, is psychology. If you know the meaning of an Optional return type, and you decide that, even though this method is guaranteed to sometimes not return a value, you are going to roll the dice, then there is literally no programming language feature that can help you, because you willfully do bad things.
On the other hand, the developer who doesn’t check for a null is often just being careless. There is no red flag being waived in his or her face about the certain danger.
So, Optional return types flip the responsibility around.
With null values, you have to be responsible enough to make sure you don’t screw up.
With Optional values, you are almost forced to write good, safe code and have to male a conscious decision to do the bad thing.
(Some even argue that get() should not exist in the Optional class)
Problem: Why are we returning a default value of “Not Found”? If the User does not exist,
then why should the name exist? We could just return a null
, but that’s bad!
Instead, we should just return an Optional name!
final Optional<User> maybeUser = getUser(10);
if(maybeUser.isPresent()){
final User u = maybeUser.get();
return Optional.of(u.getName());
}else{
return Optional.empty();
}
At this point you should be ready to revolt and assume I am trolling you.
Are you really, supposed to litter Optional wrappers throughout every line of code?
No! Higher order functions will rescue us and make life beautiful.
The Optional class defines a method called map which accepts a function as its argument.
More specifically, for the type Optional of type T, it accepts a function that takes an object of type T and returns some other type of object.
Using this, we can write the following instead:
return getUser(10).map(u->u.getName());
If the Optional contains a value, the function is applied to it, resulting in an Optional containing an String. Otherwise, the whole thing is just an empty Optional.
Now, we have obtained safety and simpler code. Good programmers and their null checks have been thoroughly defeated at this point.
What if the getName() method itself returns an *Optional
final Optional<String> result =
getUser(10).map(u->u.getName());
This doesn’t compile. Why?
The Optional type also provides a method flatMap that takes a function which returns an Optional value and “flattens” the Optional layers so that there are no nested Optionals
Now, we can write safe code in the face of missing values quite simply:
return getUser(10)
.flatMap(u -> u.getName())
.map(n -> n.toUpperCase())
.filter(n -> n.equals("PHILLIP"))
.map(n->"User: " + n)
We can do a little cleanup using method references
return getUser(10)
.flatMap(User::getName)
.map(String::toUpperCase)
.filter(n -> n.equals("PHILLIP"))
.map(n->"User: " + n)