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).
At first, I also preferred to pass Optionals as parameter, but if you switch from an API-Designer perspective to a API-User perspective, you see the disadvantages.
For your example, where each parameter is optional, I would suggest to change the calculation method into an own class like follows:
Accepting Optional as parameters causes unnecessary wrapping at caller level.
For example in the case of:
Suppose you have two not-null strings (ie. returned from some other method):
You're forced to wrap them in Optional even if you know they are not Empty.
This get even worse when you have to compose with other "mappable" structures, ie. Eithers:
ref:
ref. https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749
Using
Optional
as method arguments also needs ofnull
check on itself. Unnecessary duplicate logic. For example:-Imagine if, number of
Optional
parameter grows. If forget null check, possibly NPE will be thrown at runtime.Furthermore if your method is public, client will be forced to wrap its parameters in Optionals, which will be a burden especially if number of arguments grow.
The pattern with
Optional
is for one to avoid returningnull
. It's still perfectly possible to pass innull
to a method.While these aren't really official yet, you can use JSR-308 style annotations to indicate whether or not you accept
null
values into the function. Note that you'd have to have the right tooling to actually identify it, and it'd provide more of a static check than an enforceable runtime policy, but it would help.The best post I've seen on the topic was written by Daniel Olszewski:
This is because we have different requirements to an API user and an API developer.
A developer is responsible for providing a precise specification and a correct implementation. Therefore if the developer is already aware that an argument is optional the implementation must deal with it correctly, whether it being a null or an Optional. The API should be as simple as possible to the user, and null is the simplest.
On the other hand, the result is passed from the API developer to the user. However the specification is complete and verbose, there is still a chance that the user is either unaware of it or just lazy to deal with it. In this case, the Optional result forces the user to write some extra code to deal with a possible empty result.