I've read on many Web sites Optional should be used as a return type only, and not used in method arguments. I'm struggling to find a logical reason why. For example I have a piece of logic which has 2 optional parameters. Therefore I think it would make sense to write my method signature like this (solution 1):
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
// my logic
}
Many web pages specify Optional should not be used as method arguments. With this in mind, I could use the following method signature and add a clear Javadoc comment to specify that the arguments may be null, hoping future maintainers will read the Javadoc and therefore always carry out null checks prior to using the arguments (solution 2):
public int calculateSomething(String p1, BigDecimal p2) {
// my logic
}
Alternatively I could replace my method with four public methods to provide a nicer interface and make it more obvious p1 and p2 are optional (solution 3):
public int calculateSomething() {
calculateSomething(null, null);
}
public int calculateSomething(String p1) {
calculateSomething(p1, null);
}
public int calculateSomething(BigDecimal p2) {
calculateSomething(null, p2);
}
public int calculateSomething(String p1, BigDecimal p2) {
// my logic
}
Now I try writing the code of the class which invokes this piece of logic for each approach. I first retrieve the two input parameters from another object which returns Optional
s and then, I invoke calculateSomething
. Therefore, if solution 1 is used the calling code would look like this:
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);
if solution 2 is used, the calling code would look like this:
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));
if solution 3 is applied, I could use the code above or I could use the following (but it's significantly more code):
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
if (p2.isPresent()) {
result = myObject.calculateSomething(p1, p2);
} else {
result = myObject.calculateSomething(p1);
}
} else {
if (p2.isPresent()) {
result = myObject.calculateSomething(p2);
} else {
result = myObject.calculateSomething();
}
}
So my question is: Why is it considered bad practice to use Optional
s as method arguments (see solution 1)? It looks like the most readable solution to me and makes it most obvious that the parameters could be empty/null to future maintainers. (I'm aware the designers of Optional
intended it to only be used as a return type, but I can't find any logical reasons not to use it in this scenario).
I think that is because you usually write your functions to manipulate data, and then lift it to
Optional
usingmap
and similar functions. This adds the defaultOptional
behavior to it. Of course, there might be cases, when it is necessary to write your own auxilary function that works onOptional
.First of all, if you're using method 3, you can replace those last 14 lines of code with this:
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));
The four variations you wrote are convenience methods. You should only use them when they're more convenient. That's also the best approach. That way, the API is very clear which members are necessary and which aren't. If you don't want to write four methods, you can clarify things by how you name your parameters:
public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)
This way, it's clear that null values are allowed.
Your use of
p1.orElse(null)
illustrates how verbose our code gets when using Optional, which is part of why I avoid it. Optional was written for functional programming. Streams need it. Your methods should probably never return Optional unless it's necessary to use them in functional programming. There are methods, likeOptional.flatMap()
method, that requires a reference to a function that returns Optional. Here's its signature:public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
So that's usually the only good reason for writing a method that returns Optional. But even there, it can be avoided. You can pass a getter that doesn't return Optional to a method like flatMap(), by wrapping it in a another method that converts the function to the right type. The wrapper method looks like this:
So suppose you have a getter like this:
String getName()
You can't pass it to flatMap like this:
opt.flatMap(Widget::getName) // Won't work!
But you can pass it like this:
opt.flatMap(optFun(Widget::getName)) // Works great!
Outside of functional programming, Optionals should be avoided.
Brian Goetz said it best when he said this:
The reason Optional was added to Java is because this:
is cleaner than this:
This advice is a variant of the "be as unspecific as possible regarding inputs and as specific as possible regarding outputs" rule of thumb.
Usually if you have a method that takes a plain non-null value, you can map it over the
Optional
, so the plain version is strictly more unspecific regarding inputs. However there are a bunch of possible reasons why you would want to require anOptional
argument nonetheless:Optional
Optional
if the given value is emptyYou thinkOptional
is so awesome that whoever uses your API should be required to learn about it ;-)My take is that Optional should be a Monad and these are not conceivable in Java.
In functional programming you deal with pure and higher order functions that take and compose their arguments only based on their "business domain type". Composing functions that feed on, or whose computation should be reported to, the real-world (so called side effects) requires the application of functions that take care of automatically unpacking the values out of the monads representing the outside world (State, Configuration, Futures, Maybe, Either, Writer, etc...); this is called lifting. You can think of it as a kind of separation of concerns.
Mixing these two levels of abstraction doesn't facilitate legibility so you're better off just avoiding it.
I know that this question is more about opinion rather than hard facts. But I recently moved from being a .net developer to a java one, so I have only recently joined the Optional party. Also, I'd prefer to state this as a comment, but since my point level does not allow me to comment, I am forced to put this as an answer instead.
What I have been doing, which has served me well as a rule of thumb. Is to use Optionals for return types, and only use Optionals as parameters, if I require both the value of the Optional, and weather or not the Optional had a value within the method.
If I only care about the value, I check isPresent before calling the method, if I have some kind of logging or different logic within the method that depends on if the value exists, then I will happily pass in the Optional.
Optionals aren't designed for this purpose, as explained nicely by Brian Goetz.
You can always use @Nullable to denote that a method argument can be null. Using an optional does not really enable you to write your method logic more neatly.