Why should Java 8's Optional not be used in arguments

前端 未结 20 1638
我寻月下人不归
我寻月下人不归 2020-11-22 11:33

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

相关标签:
20条回答
  • 2020-11-22 12:11

    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.

    0 讨论(0)
  • 2020-11-22 12:13

    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:

    1. It is part of the Java standard.
    2. It makes obvious to the reader of the code (or to the user of API), that these arguments may be null. Moreover, it forces both: user of the API and developer of the method to aknolage this fact by explicitly wrapping/unwrapping the values (which is not the case, when annotations like @Nullable etc. are used).

    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):

    1. Inside my own code all the variables must be not-null (but probably Optional<>).
    2. If I have a method with one or two optional arguments I try to redesign it using overloads, inheritance etc.
    3. If I cannot find the solution in reasonable time, I start thinking, if the performance is critical (i.e. if there are millions of the objects to be processed). Usually it is not the case.
    4. If not, I use Optional as argument types and/or field types.

    There are still grey areas, where these conventions do not work:

    • We need high performance (e.g. processing of huge amounts of data, so that total execution time is very large, or situations when throughput is critical). In this cases performance penalty introduced by Optional may be really unwanted.
    • We are on the boundary of the code, which we write ourselves, e.g.: we read from the DB, Rest Endpoint, parse file etc.
    • Or we just use some external libraries, which do not follow our conventions, so again, we should be careful...

    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.

    0 讨论(0)
  • 2020-11-22 12:17

    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.

    0 讨论(0)
  • 2020-11-22 12:19

    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;
    
    0 讨论(0)
  • 2020-11-22 12:20

    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:

    • you want your function to be used in conjunction with another API that returns an Optional
    • Your function should return something other than an empty Optional if the given value is empty
    • You think Optional is so awesome that whoever uses your API should be required to learn about it ;-)
    0 讨论(0)
  • 2020-11-22 12:20

    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) {}
    
    0 讨论(0)
提交回复
热议问题