0

In functional languages it is common to use pattern matching on optional types:

let result = match myOptional with 
             | Some x -> x * 2
             | None -> 0

This is very helpful to the programmer, since the compiler checks that the pattern matches are complete.

However, in the examples for Java's Optional that I have seen, isPresent and get are used instead:

Integer result;
if (myOptional.isPresent()) {
    result = myOptional.get() * 2;
} else {
    result = 0;
}

To me, this defeats the purpose of Optional. The compiler does no checking to ensure that the both branches of the if are implemented correctly, and the resulting code has no extra guarantees than the equivalent using null.

This design choice discourages safety, so why does the standard library provide a get function instead of just match?

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • What "correctness" do you want the compiler to check for? Definite assignment ensures you've assigned `result` before you use it; I can't think what else you want. (I may just be failing in imagination here...) – Andy Turner Jan 09 '17 at 14:44
  • Yes, it helps the programmer avoid NPEs. – sdgfsdh Jan 09 '17 at 14:45
  • "it helps the programmer avoid NPEs" But what NPEs would you get in this case? – Andy Turner Jan 09 '17 at 14:46
  • This is just an example. – sdgfsdh Jan 09 '17 at 14:47
  • Maybe http://stackoverflow.com/questions/30860584/avoid-ispresent-and-get-in-control-logic – Sotirios Delimanolis Jan 09 '17 at 14:49
  • Or http://stackoverflow.com/questions/40778248/null-check-vs-optional-is-present-check – Sotirios Delimanolis Jan 09 '17 at 14:50
  • Those questions are related, but what I want to understand is *why* the API was designed this way. I assume that the Java designers know what they are doing, so there is probably a good reason for it! – sdgfsdh Jan 09 '17 at 14:56
  • "This design choice discourages safety" What alternative design do you think would be better? What is *actually* unsafe about this? Please give a specific example which illustrates the problems you envisage. – Andy Turner Jan 09 '17 at 14:57
  • @AndyTurner the F# snippet above is safer because it ensures that `result` is always initialized. Leaving off either of the two cases is a compile-time error. – sdgfsdh Jan 09 '17 at 15:01
  • @sdgfsdh [definite assignment](https://docs.oracle.com/javase/specs/jls/se8/html/jls-16.html) ensures the same thing. [Try it](http://ideone.com/O0MkYJ): leave either of the branches out, see if it still compiles if you use `result` somewhere. This is not specifically to do with `Optional`, though. – Andy Turner Jan 09 '17 at 15:02
  • @AndyTurner So it does! Post that as an answer and I will accept it. – sdgfsdh Jan 09 '17 at 15:07

3 Answers3

7

This is done with the Optional::map method. Your example could be written:

Integer result = myOptional.map(i -> i * 2).orElse(0);

With regards to the get method, there has been a discussion to deprecate it - I'm not sure if a decision has been reached yet.

assylias
  • 321,522
  • 82
  • 660
  • 783
4

the F# snippet above is safer because it ensures that result is always initialized. Leaving off either of the two cases is a compile-time error.

It would be in Java too: Java uses definite assignment to ensure that local and final variables are initialized.

Note that this has nothing to do with Optional, specifically: any time the compiler can determine that such a variable may not have been initialized before it is used, it will raise an error.

For example:

int result;
if (someCondition) {
  result = 0;
}
System.out.println(result);  // result might not have been initialized.

Ideone demo

Ideone demo, using OP's code

So there isn't really anything "unsafe" about this design (with regard to assignment of local or final variables).

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • 1
    Good job trying to understand what the OP meant! – assylias Jan 09 '17 at 15:38
  • Don't agree. Initialization is only a part of the picture. The compiler cannot make sure that the programmer didn't accidentally mix up the `if` branches. The author could write `if (!opt.isPresent()) { result = opt.get() * 2; }` and this would fail at runtime. – ZhekaKozlov Jan 21 '17 at 07:46
1

Yes, generally you should avoid calling get(). However, sometimes this is the only satisfactory solution because capabilities of lambda-expressions are rather limited. Here is the list of things that lambdas can not do:

  • Throw checked exceptions if a method does not have a corresponding throws declaration
  • Modify mutable variables in an outer scope
  • Break from a cycle (or continue)
  • Return from a method

Consider this example:

if (opt.isPresent()) {
    int value = opt.get();
    if (value == 0) {
        throw new Exception("Catastrophical error!");
    } else {
        // ...
    }
}

Since Consumer does not support checked exceptions, you cannot rewrite this code snippet with lambdas in a simple way.

Other disadvantages of lambdas:

  • Longer stack traces
  • Harder to debug
  • Require memory allocation (important for performance-critical applications)

So, the final answer is: try to write correct and functional code. Since get() can potentially affect the correctness of your code, avoid using it as much as possible. But, if your run to limitations of lambdas, don't rack your brain and just use get(). Your colleagues will thank you when they see a readable snippet of code instead of an ugly workaround which modifies outer mutable variables or throw a checked exception inside a lambda.

ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155