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 h
Check out the JavaDoc in JDK10, https://docs.oracle.com/javase/10/docs/api/java/util/Optional.html, an API note is added:
API Note: Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors.
Maybe I will provoke a bunch of down-votes and negative comments, but... I cannot stand.
Disclaimer: what I write below is not really an answer to the original question, but rather my thoughts on the topic. And the only source for it is my thoughts and my experience (with Java and other languages).
First let's check, why would anyone like to use Optional at all?
For me the reason is simple: unlike other languages java does not have built-in capability to define variable (or type) as nullable or not. All "object"-variables are nullable and all primitive-types are not. For the sake of simplicity let't not consider primitive types in further discussion, so I will claim simply that all variables are nullable.
Why would one need to declare variables as nullable/non-nullable? Well, the reason for me is: explicit is always better, than implicit. Besides having explicit decoration (e.g. annotation or type) could help static analyzer (or compiler) to catch some null-pointer related issues.
Many people argue in the comments above, that functions do not need to have nullable arguments. Instead overloads should be used. But such statement is only good in a school-book. In real life there are different situations. Consider class, which represents settings of some system, or personal data of some user, or in fact any composite data-structure, which contains lots of fields - many of those with repeated types, and some of the fields are mandatory while others are optional. In such cases inheritance/constructor overloads do not really help.
Random example: Let's say, we need to collect data about people. But some people don't want to provide all the data. And of course this is POD, so basically type with value-semantics, so I want it to be more or less immutable (no setters).
class PersonalData {
private final String name; // mandatory
private final int age; // mandatory
private final Address homeAddress; // optional
private final PhoneNumber phoneNumber; // optional. Dedicated class to handle constraints
private final BigDecimal income; // optional.
// ... further fields
// How many constructor- (or factory-) overloads do we need to handle all cases
// without nullable arguments? If I am not mistaken, 8. And what if we have more optional
// fields?
// ...
}
So, IMO discussion above shows, that even though mostly we can survive without nullable arguments, but sometimes it is not really feasible.
Now we come to the problem: if some of the arguments are nullable and others are not, how do we know, which one?
Approach 1: All arguments are nullable (according to java standrd, except primitive types). So we check all of them.
Result: code explodes with checks, which are mostly unneeded, because as we discussed above almost all of the time we can go ahead with nullable variables, and only in some rare cases "nullables" are needed.
Approach 2: Use documentation and/or comments to describe, which arguments/fields are nullable and which not.
Result: It does not really work. People are lazy to write and read the docs. Besides lately the trend is, that we should avoid writing documentation in favor of making the code itself self-describing. Besides all the reasoning about modifying the code and forgeting to modify the documentation is still valid.
Approach 3: @Nullable @NonNull etc... I personally find them to be nice. But there are certain disadvantages : (e.g. they are only respected by external tools, not the compiler), the worst of which is that they are not standard, which means, that 1. I would need to add external dependency to my project to benefit from them, and 2. The way they are treated by different systems are not uniform. As far as I know, they were voted out of official Java standard (and I don't know if there are any plans to try again).
Approach 4: Optional<>. The disadvantages are already mentioned in other comments, the worst of which is (IMO) performance penalty. Also it adds a bit of boilerplate, even thoough I personally find, use of Optional.empty() and Optional.of() to be not so bad. The advantages are obvious:
So in my point, there is no black-and-white in regard of any methodology including this one. I personally ended up with the following guidelines and conventions (which are still not strict rules):
There are still grey areas, where these conventions do not work:
By the way, the last two cases can also be the source of need in the optional fields/arguments. I.e. when the structure of the data is not developed by ourselves, but is imposed by some external interfaces, db-schemas etc...
At the end, I think, that one should think about the problem, which is being solved, and try to find the appropriate tools. If Optional<> is appropriate, then I see no reason not to use it.
I think that is because you usually write your functions to manipulate data, and then lift it to Optional
using map
and similar functions. This adds the default Optional
behavior to it.
Of course, there might be cases, when it is necessary to write your own auxilary function that works on Optional
.
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, like Optional.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:
public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
return t -> Optional.ofNullable(function.apply(t));
}
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:
return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.stream()
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
.filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.findFirst()
.getOrThrow(() -> new InternalError(...));
is cleaner than this:
Method matching =
Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
.stream()
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
.filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.getFirst();
if (matching == null)
throw new InternalError("Enclosing method not found");
return matching;
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 an Optional
argument nonetheless:
Optional
Optional
if the given value is emptyOptional
is so awesome that whoever uses your API should be required to learn about it ;-)The pattern with Optional
is for one to avoid returning null
. It's still perfectly possible to pass in null
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.
public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}