Zhro
Zhro

Reputation: 2614

Is there a clearer way to deal with short-lived optionals?

I love Optional in Java. It has, in one simple class, allowed me to clearly identify return types and arguments which may or may not be available.

One thing that I struggle with is the necessity of assigning it to a short-lived variable which is then inherited into every subsequent scope.

I like to use the simple variable name opt when using optionals like this:

   Optional<ThingA> opt = maybeGetThing();

   if (opt.isPresent()) {
      ThingA usefulVariableName = opt.get();
      ...

But when I then need a variable name to use in this scope...

void method() {
   Optional<ThingA> opt = maybeGetThing();

   if (opt.isPresent()) {
      ThingA usefulVariableName = opt.get();

      usefulVariableName.doA();
      usefulVariableName.doB();
      usefulVariableName.doC();

      // Duplicate local variable opt
      Optional<ThingB> opt = usefulVariableName.maybeAnotherThing();
   }
}

I can use things like optA and optB and so on. But I wonder if there is another way to write this code without having to enumerate my temporary variables. This just smacks of lazy variable names like a aaaa aaaaaabbb or something.

I don't want to name all of my optionals explicitly like this:

   Optional<ThingA> optUsefulVariableName = maybeGetThing();

   if (optUsefulVariableName.isPresent()) {
      ThingA usefulVariableName = optUsefulVariableName.get();
      ...

While accurate, it is extremely verbose. I also try to use throwaway names like opt and i to indicate that these are in fact only temporary and should serve no purpose beyond their immediate scope (even though they will be inherited).


UPDATE:

I have seen suggestions for using ifPresent() but I don't see how I can use this for instances where I also need to perform an action if the optional is empty:

void method() {
   Optional<ThingA> opt = maybeGetThing();

   if (!opt.isPresent()) {
      doSomethingOnlyHere();

      return;
   }

   if (opt.isPresent()) {
      ThingA usefulVariableName = opt.get();

      usefulVariableName.doA();
      usefulVariableName.doB();
      usefulVariableName.doC();

      // Duplicate local variable opt
      Optional<ThingB> opt = usefulVariableName.maybeAnotherThing();
   }
}

When I try to refactor with ifPresent():

void method() {
   // Doesn't handle instance where I need side effects on an empty optional
   maybeGetThing().ifPresent(usefulVariableName -> {
      ...
   }
}

Upvotes: 1

Views: 479

Answers (2)

Paul Rooney
Paul Rooney

Reputation: 21619

The most basic way to eliminate the variable and the need to call Optional#get is to use Optional.ifPresent which calls a function if the Optional has a value.

maybeGetThing().ifPresent(val -> {
    // do stuff with side effects here
});

This is still quite a limited way to use Optional, as one of Optionals key purposes is to facilitate programming in a functional style. If you are a beginner this may be a little lost on you, but the idea is to have functions that return something and not functions that rely on side effects. Functions relying on side effects cannot be chained together and are generally harder to reason about.

Technically Optional is something called a Functor (from category theory). It is a wrapper around a value (Whatever T is) and it allows the value to be passed through a series of operations to operate on it and pass it to the next operation until we have what we want, then the chain of operations ends with a terminal (i.e. final) operation. The terminal operation may return the unwrapped value if it exists or it could throw or return some default value if it doesn't.

For Optional it will skip any subsequent operations if the value becomes not present.

There are common operations like map, filter, flatMap (ok that's a Monad operation) and other more java specific operations like Optional#orElse and Optional#orElseThrow.

To refactor your example code you could do this.

void method() {
   return maybeGetThing().flatMap(val -> {

       // eek side effects
       val.doA();
       val.doB();
       val.doC();

       return val.maybeAnotherThing();
   });  
}

flatMap is a way of converting an Optional of one type to an Optional of another type. If the return value weren't Optional you would use map.

You can see we have eliminated the need for names of return values in favour of naming the parameters of lambda functions. The lambda functions are scoped so you can reuse the names if that's what you want to.

I generally like to provide runnable code, so here is a contrived example of what I mean which is runnable.

import java.util.Optional;

class DummyClass {

    private int val = 0;

    public void doA(){ val += 1; }

    public void doB(){ val += 2; }

    public void doC(){ val += 3; }

    public Optional<String> maybeAnotherThing(){
        return Optional.of(Integer.toString(val));
    }
}

public class UseOptional5 {   

    Optional<DummyClass> maybeGetThing(){
        return Optional.of(new DummyClass());
    }

    String method() {
        return maybeGetThing()
               // you can put other operations here
               .flatMap(val -> {

                    // eek side effects
                    val.doA();
                    val.doB();
                    val.doC();

                    return val.maybeAnotherThing();
                })
                // you can put other operations here too
                .orElseThrow(() -> new IllegalArgumentException("fail!!"));
    }    

    public static void main(String args[]) {

        UseOptional5 x = new UseOptional5();

        System.out.println(x.method());
    }
}

Upvotes: 1

Anonymous
Anonymous

Reputation: 86343

Since Java 9 I’d do

void method() {
   maybeGetThing().ifPresentOrElse(
           usefulVariableName -> {
               usefulVariableName.doA();
               usefulVariableName.doB();
               usefulVariableName.doC();

               // No duplicate local variable opt
               Optional<ThingB> opt = usefulVariableName.maybeAnotherThing();
           },
           this::doSomethingOnlyHere
   );
}

My rule of thumb is you seldom need or want to use isPresent and/or get, they are low-level. For basic things ifPresent (with f) and ifPresetnOrElse are fine. Others are correct that map and flatMap are very useful too.

Upvotes: 0

Related Questions