Reputation: 2614
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
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
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