The Beast
About a week ago (give or take a few months1), we killed a couple of nulls. If you want to know why, go ahead and read the first of this three-part series, Pinocchio, and then the second, Fairy Godmother. However, there’s one of those evil little buggers left, and I want to slay it with extreme prejudice. So here he is, in all his terrifying glory.
Books books = (Books) cache.get("books", new Supplier<Object>() {
@Override public Object get() {
Books defaultBooks = readListOfBooks();
downloadBookIsbnsFor(defaultBooks);
return defaultBooks;
}
});
cache.set("books", books);
for (Book book : books) {
String isbn = book.getIsbn();
if (isbn == null) {
isbn = "[not found]";
}
System.out.println(isbn);
}
Fine, not so terrifying. Still, I wouldn’t mind putting him on the express train to deletionville.2 To facilitate this, I want to introduce my friend, Optional.
Optional goes by many names. Some call him by his nickname, “Option”. In some cultures, he’s indistinguishable from his cousin, List. In the far north, up near Glasgow, he’s known in hushed tones as “Maybe”. One thing we all agree on, though, is that in the right place, at the right time, he can be the only thing stopping wanton type destruction.3
We often need to represent optionality in our code. Sometimes this thing
is there, sometimes it’s not. In the example up above, we might have an
ISBN, or we might need to fill it in with that "[not found]"
sign.
Rewriting the null check in a more fluent style, it could look something
like this:
String isbn = book.getIsbn().or("[not found]");
System.out.println(isbn);
Of course, we can’t just go and call or
on null
—we’ll get an NPE.
And it doesn’t exist on String
. We need to have a type on which to
implement or
, and here’s where Optional
comes in. It looks something
like this:
public interface Optional<T> {
T or(T defaultValue);
}
Perfect. But how do we get one of those Optional things, anyway? Well,
it comes from the downloadBookIsbnsFor
function, so let’s look at that
now. Here’s what I’m imagining it looks like:
public void downloadBookIsbnsFor(Books books) {
for (Book book : books) {
String isbn = null;
try {
isbn = downloadIsbn(book.getName());
} catch (DownloadException e) {
log(e);
}
book.setIsbn(isbn);
}
}
All we need to do is make the ISBN in Book
an Optional<String>
, and
then, assuming it works like a regular Java bean, we can make this use
Optional
too.
public void downloadBookIsbnsFor(Books books) {
for (Book book : books) {
Optional<String> isbn;
try {
isbn = Optional.of(downloadIsbn(book.getName()));
} catch (DownloadException e) {
isbn = Optional.absent();
log(e);
}
book.setIsbn(isbn);
}
}
Take a close look at the two occasions where isbn
is assigned. We
assign an optional value of the actual ISBN, which we download from the
Interwebs. If this fails, we construct an “absent” value. This tells us
that nobody’s home. I’ve also seen it called “Nothing” and “None”. So
we’re using this optional value and we’re producing it. Now we just need
to implement it.
It turns out it’s actually very simple. All you have to do implement each type using an implementation of the interface.
class OptionalOf<T> implements Optional<T> {
private final T value;
OptionalOf(T value) {
this.value = value;
}
T or(T defaultValue) {
return value;
}
}
OptionalOf
returns its own value. Absent
, on the other hand, returns
the one you give it.
class Absent<T> implements Optional<T> {
T or(T defaultValue) {
return defaultValue;
}
}
Simple, right? Now all we need to do is provide the helper methods for making them. Java doesn’t allow static methods on interfaces, so we’ll make it an abstract class instead.
abstract class Optional<T> {
static <T> Optional<T> of(T value) {
return new OptionalOf<T>(value);
}
static <T> Optional<T> absent() {
return new Absent<T>();
}
abstract T or(T defaultValue);
}
And with that, we have a type-safe way of building optional values. The best part is that this class is already built into Guava, and contains all sorts of additional methods to make your life easier. There’s also one in Functional Java, as well as a number of other libraries. We even have one on our company GitHub page.
If you’re using Scala or another functional programming language, you’ll
probably find it’s built in—though it might be called Option
or
Maybe
. And if you’re not, well, it’s fairly trivial to write. In fact,
I just threw one together in Ruby.
So what’s the end result? It looks something like this:
Books books = (Books) cache.get("books", new Supplier<Object>() {
@Override public Object get() {
Books defaultBooks = readListOfBooks();
downloadBookIsbnsFor(defaultBooks);
return defaultBooks;
}
});
cache.set("books", books);
for (Book book : books) {
String isbn = book.getIsbn().or("[not found]");
System.out.println(isbn);
}
I don’t know about you, but to me, that’s a whole lot prettier.
If you enjoyed this post, you can subscribe to this blog using RSS. I personally use Feedly; you can subscribe here.
Maybe you have something to say. You can comment below, email me, or toot at me. I love feedback. I also love gigantic compliments, so please send those too.