Having been using Java 8 now for 6+ months or so, I\'m pretty happy with the new API changes. One area I\'m still not confident in is when to use Optional
. I se
Personally, I prefer to use IntelliJ's Code Inspection Tool to use @NotNull
and @Nullable
checks as these are largely compile time (can have some runtime checks) This has lower overhead in terms of code readability and runtime performance. It is not as rigorous as using Optional, however this lack of rigour should be backed by decent unit tests.
public @Nullable Foo findFoo(@NotNull String id);
public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);
public class Book {
private List<Pages> pages;
private @Nullable Index index;
}
List<@Nullable Foo> list = ..
This works with Java 5 and no need to wrap and unwrap values. (or create wrapper objects)
Here are some of the methods that you can perform on an instance of Optional<T>
:
map
flatMap
orElse
orElseThrow
ifPresentOrElse
get
Here are all the methods that you can perform on null
:
This is really an apples to oranges comparison: Optional<T>
is an actual instance of an object (unless it is null
… but that would probably be a bug) while null
is an aborted object. All you can do with null
is check whether it is in fact null
, or not. So if you like to use methods on objects, Optional<T>
is for you; if you like to branch on special literals, null
is for you.
null
does not compose. You simply can’t compose a value which you can only branch on. But Optional<T>
does compose.
You can, for instance, make arbitrary long chains of “apply this function if non-empty” by using map
. Or you can effectively make an imperative block of code which consumes the optional if it is non-empty by using ifPresent
. Or you can make an “if/else” by using ifPresentOrElse
, which consumes the non-empty optional if it is non-empty or else executes some other code.
…And it is at this point that we run into the true limitations of the language in my opinion: for very imperative code you have to wrap them in lambdas and pass them to methods:
opt.ifPresentOrElse(
string -> { // if present...
// ...
}, () -> { // or else...
// ...
}
);
That might not be good enough for some people, style-wise.
It would be more seamless if Optional<T>
was an algebraic data type that we could pattern match on (this is obviously pseudo-code:
match (opt) {
Present(str) => {
// ...
}
Empty =>{
// ...
}
}
But anyway, in summary: Optional<T>
is a pretty robust empty-or-present object. null
is just a sentinel value.
There seems to be a few people who effectively argue that efficiency should determine whether one should use Optional<T>
or branch on the null
sentinel value. That seems a bit like making hard and fast rules on when to make objects rather than primitives in the general case. I think it’s a bit ridiculous to use that as the starting point for this discussion when you’re already working in a language where it’s idiomatic to make objects left-and-right, top to bottom, all the time (in my opinion).