Optionals in Java have been around for some time now, basically since the release of version 8. My other language of choice, C++, has received this feature in version C++17. Since I am currently in the process of writing some C++ code, I was curious how they were implemented there. Optionals are trying to solve a problem that is likely to plague any language. What shall a method or function return if there is no value? Or shall it not return anything but instead start crying like a petulant child and throw an exception?
As an introduction, let me dive a little bit into why we need
optionals (or do we?) and compare two different implementations of
this concept, one being java.util.Optional
and the other C++
std::optional
. I chose to compare these two language for several
reasons:
- I work with Java in my day job, so I have a good idea of how it works there.
- As mentioned, C++ is one of the languages I know quite well too.
- The main reason: both optional implementations are add-on classes rather than language features. More on that later.
If you’re not interested in a story or what optionals are used for then feel free to skip directly to the code samples and the comparison.
Note: I will always use the term "function" throughout this post to be consistent. You may replace it with "method" in your head where applicable.
Why do we need optionals?
Return values can be a tricky thing. If your API is 100% crystal
clear and there’s never the case that none of your methods never have
to return "nothing" as a result and all 3rd party APIs you are using
are perfect in this regard as well then consider yourself lucky. The
more likely scenario, however, is that you’ve had to deal with null
or -1
before. Both of which are common indicators that there is no
result.
Without optionals, the way you can handle this situation depends on the return type. In some cases, the return type can be used to transport two types of results, one being the actual value and the other the information that there is no value. This means that the value returned from a function is subject to a specification. As a result, you will not be able to tell what the valid range of values and what the "there is no value"-value is, simply by reading the function’s definition. You must consult the API documentation in order to acquire that information. Now, I’m a big proponent of a good API documentation. However, history is my witness that many API’s are either poorly documented or not at all. It all depends on the popularity of the library that you are using and even popular libraries are victims to poor documentation. Another option is to write tests to find that out, but in my opinion this is time that should not be spent. It’s not your job to figure out an API’s return values.
Let’s use the following function as a contrived example:
int get_next_value()
{
if (have_next()) {
return next();
}
return -1;
}
This function is used with an endless list of numbers and returns a
number as long as the end of the list hasn’t been reached. The way
this function is implemented, it uses -1
as an indicator that the
end has been reached. In fact, many socket or file access APIs work
like this. In this example, that immediately leads to a problem: the
list cannot contain negative numbers or otherwise a value will
conflict with the "there is no value"-value -1
.
How about something that involves null
? Take the following Java
snippet for example.
Car findByModel(String model) {
var found = database.executeQuery(...);
if (found.hasResult()) {
return Car.fromRow(found);
}
return null;
}
This is an imaginary database access function that executes a query
and converts the row that was found into a POJO or returns null
.
Granted, in this case a seasoned programmer knows that Java objects
can be null
and null
is a valid "value" for a reference to
indicate no-value. It is perfectly legitimate to use it here.
How could this example be implemented with C++? C++ developers have
the option to place complex objects on the stack instead of using
pointers (basically what Java calls a reference). But such an object
can only be a valid object, there is no nullptr
in this case. As a
workaround, you could add something like an is_null()
function and
evaluate the internal state. Yuck. Qt’s XML DOM implementation works
this way, for example. Or its QDate
and other date and time related
classes. This way you can avoid using pointers and the nullptr
value that comes with it. I’m sure we all were taught to shy away
from pointers when possible. How about using a smart pointer like
std::unique_ptr
?
(Did I seriously ask that question?)
NO! Don’t do that!
We have two issues here:
- C++ objects on the stack cannot be
null
. - Even if they could or if you chose to use pointers, a
null
able return value is prone to result in a null-pointer access error. That is the case for C++ and Java.
The way I see it we are facing a main problem and a sub-problem.
The main problem is this: Just from reading the function definition you don’t know how the no-value case is handled.
The sub-problem is a result of that fact: You cannot force the proper handling of no-value by the compiler.
And if the user doesn’t know how no-value is treated it might likely cause one of those rare and pesky null-pointer exceptions or access violations that only happen once in a while, depending on the input, the day of the week, the sun’s intensity and a sack of rice tipping over on the other side of the planet. I’m sure we’ve all been there.
And this is where optionals come in. Java and C++ don’t have native support for optionals like C# or Kotlin do. Java and C++ use class wrappers to achieve a similar goal. Direct language support by the compiler can result in much more concise code and likely also has other benefits I’m too stupid come up with and too lazy to research right now. The fact that both languages I chose to examine here utilize classes as an optional implementation means that the compiler can only check for proper use of the optional class API.
Consider this little Java snippet:
Optional<String> getString() {
return null;
}
That’s how you can still break the power of optionals in Java. Since
java.util.Optional
is merely a class instance it can also become
null
. The general consensus is to NOT do that, of course. But
it’s an obvious drawback compared to an optional built into the
language itself. C++ is safe in that regard because std::optional
is supposed to be used as an object on the stack and as I have
mentioned earlier, this cannot be null
. You could, of course, do
something like this:
std::optional<std::string>* get_string() {
return nullptr;
}
If you do that though, you don’t deserve anything other than an endless tirade of illegal access errors.
With that "little" lecture on why optionals make sense out of the way (which of course nobody else other than me has ever done before), let’s get into comparing both implementations.
java.util.Optional vs. std::optional
All the sample code can be found on Github.
As outlined above, optionals shall be used as return values to indicate that there is no value and also to get rid of the tedious unhandled null-pointer exceptions. Let’s take the Java database example and first look at code that uses this function without optionals.
final var foundCar = findByModel("i30 N");
if (null == foundCar) {
throw new NotFoundException();
}
foundCar.drive();
Pretty standard stuff. Shouldn’t be too hard to write, one would think. Keep in mind that this is a really simple example. There are use cases out there, where in literally 99% of all function invocations a value will be returned. It’s the edge cases where things blow up. Or, when time is scarce because deadlines a looming over a developer’s shoulders like Death himself, sharpening his scythe. Then programmers tend to omit error handling because, you know, it is expected to be fine all the time. Which it’s not and those errors will reveal themselves eventually.
Back to the topic. Let’s rewrite it with java.util.Optional
.
Optional<Car> findByModel(String model) {
var found = database.executeQuery(...);
if (found.hasResult()) {
return Optional.of(Car.fromRow(found));
}
return Optional.empty();
}
// Somewhere else...
final var foundCar = findByModel("i30 N");
if (foundCar.isEmpty()) {
throw new NotFoundException();
}
foundCar.get().drive();
As you can see, the calling code changes by forcing you to handle the
optional. Of course, you can still ignore it and try to access the
value using get()
. But by doing so, you are still reminded that
there could potentially be no value. IDEs like IntelliJ highlight
this by default. On first glance, this looks more cumbersome to use.
For that, Java has some nice helper functions. In this example we
throw an exception and that can be handled very elegantly.
final var foundCar = findByModel("i30 N").orElseThrow(NotFoundException::new);
foundCar.drive();
BAM! That’s even much nicer than the non-optional version.
orElseThrow
either returns the unwrapped value or throws an
exception, all in one line. What about C++? Well, this is where
optional doesn’t shine as bright. It is more limited in its
functionality. But before I get ahead of myself, here’s the code.
std::optional<car> find_by_model(std::string model) {
const auto found = database.execute_query(...);
if (found.has_result()) {
return std::make_optional(car.from_row(found));
}
return std::nullopt;
}
// Somewhere else...
const auto found_car = find_by_model("i30 N");
if (!found_car.has_value()) {
throw not_found_exception{};
}
found_car.value().drive();
// Or, much nicer
found_car->drive();
This is basically everthing you can do with std::optional
. It gives
you the possibility of returning no value from a function without
resorting to nullptr
or any other tricks. The usage is not as
convenient as Java though. You can check if there is a value with
has_value()
and retrieve this value with – you guessed it –
value()
. There is one more interesting function, value_or()
,
which I’ll get into later. Unlike Java though, C++ provides
dereferencing operators like ->
to get to the value without calling
the value()
function, as is also the case with iterators for
example. But other than that, std::optional
does not have the power
of java.util.Optional
. Just look at the documentation and
see how many, or rather, how little functions there are. Java, on the
other hand, has a plethora of functions, some of which I’ll
be showing next.
Take a look at the C++ samples in the Github project to see how optionals can be created and also several use cases.
I’ve already covered the most simple and maybe most common case: test for a value and throw if none is available. What if you don’t want to throw but maybe use a default value? Well, both languages also got you covered.
final var defaultCar = new Car(...);
final var foundCar = findByModel("i30 N").orElse(defaultCar);
const auto default_car = car{...};
const auto found_car = find_by_model("i30 N").value_or(default_car);
This may not be the best example use case, but it shows how easy you
can test for no value and replace it with a default value instead.
This is much shorter than the traditional way with an if (null != foundCar)
statement.
Java doesn’t stop there. If your logic depends on having an optional, you can also produce an optional instead of a "real" value. This is helpful in functions that don’t consume but produce data.
final var defaultCar = new Car(...);
final var foundCar = findByModel("i30 N").or(Optional.of(defaultCar));
Do you maybe want to perform some action if there is a value and if
not, then just continue? Sometimes data is optional and you only
execute code if it’s there. ifPresent()
is your friend and only
gets executed if the optional has a value. Otherwise it’s doing
nothing.
findByModel("i30 N").ifPresent(this::tuneCar);
The equivalent of that would be the following.
final var foundCar = findByModel("i30 N");
if (foundCar.isPresent()) {
tuneCar(foundCar.get());
}
And to throw one more C++ snippet in here: assuming the signature of
the tuning function looks like this tune_car(car& pimp_my_ride)
we
can use the *
operator as a shorthand to get to the value. Nicer
than Java’s get()
– although that is more explicit as to what is
happening. But C++ coders should also easily understand the use of
*
.
const auto found_car = find_by_model("i30 N");
if (found_car.has_value()) {
tune_car(*found_car);
}
Additionally, java.util.Optional
also has a filter()
function
with which you can perfom conditional tests before committing to the
value.
final var foundCar = findByModel("i30 N")
.filter(car -> car.getProductionYear() == 2000)
.ifPresent(this::tuneCar);
In that case only cars from 2000 will be tuned. You can also convert
to another type by using the map()
function. In my example this is
useful if you want to convert from a database entity to a data
transfer object.
final var foundCar = findByModel("i30 N")
.filter(car -> car.getProductionYear() == 2000)
.map(CarDto::fromEntity)
.orElseThrow(NotFoundException::new);
See how you can chain functions to create a nice functional pipeline? Since this is not available in C++, how would that look with what we have right now?
const auto found_car = find_by_model("i30 N");
if (!found_car.has_value() || 2000 != found_car->production_year()) {
throw not_found_exception{};
}
return make_dto(found_car);
I mean, it gets the point across and has worked for decades. But I
have to admit, conditional statements like that have the potential to
trip me up, force me to think harder about what it does.
Unfortunately, I am notoriously bad deciphering more complex if
statements and that is where I honestly welcome Java’s functional
style.
When I switched jobs roughly a year ago, I was still in the camp that didn’t really like Java all that much. It was a nice enough language, but I still preferred C++. That has changed somehwat now. Java may have its flaws. However, through some important changes in version 8 and through libraries like Lombok it is actually a nice experience to write Java code nowadays. I still like C++, but a lot of that is a sentimental thinking because I’ve started my career on that language. It’s not bad, I’m not saying that. It excels in it own ways, but as I’ve written some C++ code recently, I have to say that collection handling is way more convenient using Java’s streams.
Anyway. I think this has been a pretty long piece and the only thing that makes it remotely original is the comparison to C++. Other than that, Java’s optionals are old news by now. It’s not the first time that I’ve compared several language features, so I think this fits right in. Maybe I’ll extend this to Kotlin in a future post, who knows.