The Beast
Thursday 23 February 2012 at 05:53 GMT
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.
Footnotes
If you enjoyed this post, you can subscribe to this blog using Atom.
Maybe you have something to say. You can email me or toot at me. I love feedback. I also love gigantic compliments, so please send those too.
Please feel free to share this on any and all good social networks.
This article is licensed under the Creative Commons Attribution 4.0 International Public License (CC-BY-4.0).