Frage

Take the two code examples:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

As far as I can tell the most obvious difference is that the Optional requires creating an additional object.

However, many people have started rapidly adopting Optionals. What is the advantage of using optionals versus a null check?

War es hilfreich?

Lösung

Optional harnesses the type system for doing work that you'd otherwise have to do all in your head: remembering whether or not a given reference may be null. This is good. It's always smart to let the compiler handle boring drugework, and reserve human thought for creative, interesting work.

Without Optional, every reference in your code is like an unexploded bomb. Accessing it may do something useful, or else it may terminate your program wth an exception.

With Optional and without null, every access to a normal reference succeeds, and every reference to an Optional succeeds unless it's unset and you failed to check for that. That is a huge win in maintainability.

Unfortunately, most languages that now offer Optional haven't abolished null, so you can only profit from the concept by instituting a strict policy of "absolutely no null, ever". Therefore, Optional in e.g. Java is not as compelling as it should ideally be.

Andere Tipps

An Optional brings stronger typing into operations that may fail, as the other answers have covered, but that is far from the most interesting or valuable thing Optionals bring to the table. Much more useful is the ability to delay or avoid checking for failure, and to easily compose many operations that may fail.

Consider if you had your optional variable from your example code, then you had to perform two additional steps that each might potentially fail. If any step along the way fails, you want to return a default value instead. Using Optionals correctly, you end up with something like this:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

With null I would have had to check three times for null before proceeding, which adds a lot of complexity and maintenance headaches to the code. Optionals have that check built in to the flatMap and orElse functions.

Note I didn't call isPresent once, which you should think of as a code smell when using Optionals. That doesn't necessarily mean you should never use isPresent, just that you should heavily scrutinize any code that does, to see if there is a better way. Otherwise, you're right, you're only getting a marginal type safety benefit over using null.

Also note that I'm not as worried about encapsulating this all into one function, in order to protect other parts of my code from null pointers from intermediate results. If it makes more sense to have my .orElse(defaultValue) in another function for example, I have much fewer qualms about putting it there, and it's much easier to compose the operations between different functions as needed.

It highlights the possibility of null as a valid response, which people often assume (rightly or wrongly) is not going to be returned. Highlighting the few times when null is valid allows omitting littering your code with huge numbers of pointless null checks.

Ideally setting a variable to null would be a compile time error anywhere but in an optional; eliminating runtime null pointer exceptions. Obviously backwards compatibility precludes this, sadly

Let me give you an example:

class UserRepository {
     User findById(long id);
}

It's quite clear what is this method intended for. But what happens if the repository doesn't contain user with given id? It could throw an exception or maybe it returns null?

Second example:

class UserRepository {
     Optional<User> findById(long id);
}

Now, what happens here if there's no user with given id? Right, you get Optional.absent() instance and you can check it with Optional.isPresent(). Method signature with Optional is more explicit about its contract. It's immediatelly clear, how the method is supposed to behave.

It also has a benefit when reading the client code:

User user = userRepository.findById(userId).get();

I have trained my eye to see .get() as an immediate code smell. It means that client purposefully ignores the contract of the invoked method. It's much easier to see this than without Optional, because in that case you're not immediatelly aware what's the contract of called method (whether it allows null or not in its return) so you need to check it first.

Optional is used with the convention that methods with return type Optional never return null and usually also with the (less strong) convention that method without Optional return type never return null (but it's better to annotate it with @Nonnull, @NotNull or similar).

In addition to the other answers, the other major advantage of Optionals when it comes to writing clean code is that you can use a Lambda expression in the event that the value is present, as shown below:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

An Optional<T> allows you to have "failure" or "no result" as a valid response/return value for your methods (think, e.g., of a database lookup). Using an Optional instead of using null to indicate failure/no result has some advantages:

  • It clearly communicates that "failure" is an option. The user of your method does not have to guess whether nullmight be returned.
  • The value null should, at least in my opinion, not be used to indicate anything as the semantics might not be totally clear. It should be used to tell the garbage collector that the previously referred to object may be collected.
  • The Optional class provides many nice methods to conditionally work with the values (e.g., ifPresent()) or define default values in a transparent way (orElse()).

Unfortunately, it does not completely remove the need for null checks in the code as the Optional object itself still might be null.

Consider the following method:

void dance(Person partner)

Now let's look at some calling code:

dance(null);

This causes a potentially difficult to track crash somewhere else, because dance was not expecting a null.

dance(optionalPerson)

This causes a compile error, because dance was expecting a Person not an Optional<Person>

dance(optionalPerson.get())

If I didn't have a person, this causes an exception on this line, at the point where I illegitimately tried to convert the Optional<Person> into a Person. The key is that unlike passing a null, the except traces to here, not some other location in the program.

To put it all together: misusing optional produces easier to track down problems, misusing null causes difficult to track down problems.

In Objective-C, all object references are optional. Because of this, code like you posted is silly.

if (variable != nil) {
    [variable doThing];
}

Code like the above is unheard of in Objective-C. Instead, the programmer would just:

[variable doThing];

If variable contains a value, then doThing will be called on it. If not, no harm. The program will treat it as a no-op and continue running.

This is the real benefit of optionals. You don't have to litter your code with if (obj != nil).

if(optional.isPresent()) {

The obvious problem here is that if "optional" really is missing (i.e. is null) then your code is going to blow up with a NullReferenceException (or similar) trying to call any method on a null object reference!

You might want to write a static, Helper-class method that determines the null-ness of any given object type, something like this:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

or, perhaps, more usefully:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

But are either of these really any more readable/ understandable/ maintainable than the original?

if ( null == optional ) ... 
if ( null != optional ) ... 
Lizenziert unter: CC-BY-SA mit Zuschreibung
scroll top