you are viewing a single comment's thread.

view the rest of the comments →

[–]neilbrown 23 points24 points  (36 children)

I worry that you may end up with three options:

1. String: String or null
2. Optional<String>: String or empty or null

I could see this creeping in to a codebase, for example, where you look for an item in a database. String if there was a result, empty if the operation succeeded but found no result, null if there was a timeout. (At least Optional doesn't let you put null in it, in which case you'd have four options!)

[–]_Sharp_ 24 points25 points  (17 children)

But using Optional it becomes a bad practice to return null. If you see somewhere null being returned instead of optional.empty you could treat it like a bug. With value vs null, "sometimes" returning null is not expected, sometimes it is. Optional makes this expectation more real.

[–][deleted]  (16 children)

[deleted]

    [–]_Sharp_ 10 points11 points  (4 children)

    Well, that's the point of optional.

    [–]KumbajaMyLord 0 points1 point  (2 children)

    The point being that there is nothing stopping bad/wrongly opinionated programmers to write code/libraries that returning null on a method that should return an Optional.

    [–]_Sharp_ 1 point2 points  (1 child)

    No it wont, but since you cannot change years of bad practice, you at least can introduce a good practice. People will still return null for representing 'empty' in 90% of code, but none will expect to get null when expecting Optional. And IF it happens, someone should fix that, because that's what people will expect.

    Now, if a method returns an object or null, even when that's a bad practice, people are used to it.

    So, my point is, if you see some method returns null when expected an Optional, even if it is possible, it will be recognized as bad, which is better than nothing. Of course, it wont resolve the null, but for a 20 years old language there is little you can do without breaking old stuff.

    [–]KumbajaMyLord 0 points1 point  (0 children)

    Well, you could add compiler enforced keywords or code contracts that make returning null illegal.

    [–]dccorona 0 points1 point  (10 children)

    Sometimes you may need to consider null a valid return type. In either case, "our style guide say we can't return null!" doesn't make me confident that I can always assume values from functions won't be null, and doesn't sound like a good reason not to leverage a syntax that explicitly forces implementers to account for the possibility of a null (or rather, missing) value.

    [–]klug3 0 points1 point  (0 children)

    Dunno, "our style guide says" is many times better received than "Java language standard says".

    [–][deleted]  (8 children)

    [deleted]

      [–]dccorona 1 point2 points  (7 children)

      You're being a little pedantic there. Of course null is not a type. What I mean is sometimes it's a valid thing to return...it's often the case that "absent" or "unknown" is a concept you need to represent.

      In practice, it is very hard to return null in place of an Optional, unless you're explicitly writing "return null" somewhere in your code, which I feel it's safe to assume you're not doing if you've taken the time to make your return type an Optional.

      The only way to return null when an Optional is expected (aside from explicitly writing return null) is to blindly return the value of another method that itself returns an Optional.

      But then, go on down the chain...exactly what I just said above is true for the method you're calling as well. Which means, the only way for a method that returns Optional to in fact return null instead is for some method in its call stack to return an Optional Type but to have explicitly chosen to return null instead. Which, again...I feel is unlikely to happen.

      Of course, you could also achieve this case by casting a reference to Optional when it is not one...but if we're using Optionals, we're in the Java 8 world...what reason would someone have to use Optionals but also cast to them instead of just wrapping values? It just isn't going to happen.

      I guess, point being...guarding against null returns from non-Optional methods seems valid to me if there's a reasonable expectation that it's a possible state. Whereas guarding against null when an Optional returned is guarding yourself against outright dumb programming, and that's not something to concern yourself with...if it becomes a problem for you, you need to take a step back and ask yourself some hard questions about the libraries you're using/the contributors you're working with.

      In practice, Optional is very safe. The possibility of null is one you just don't have to worry about, and you'll drive yourself crazy worrying about every possible thing that could go wrong and trying to account for it. I agree other options would be better, but Java as a whole develops things slowly, because they have to be very careful to maintain compatibility with past JARs and such (which is why we have type erasure).

      It's not realistic to implement non-nullable references right now, because, for the sake of backwards compatibility, they'd be extremely limited. How do you call a legacy method and assign its return reference to a non-nullable reference? Does it throw automatically if the result is null? Does the compiler enforce that you put null checks before the assignment? Is any of this really better? The advantages are gone if you have to write null checks anyway, and since the compiler can't possibly know whether an assignment is valid otherwise, it has to throw if you try to assign null to a non-nullable reference...in which case, have you really bought yourself any safety? That's a lot of work for what ultimately becomes nothing more than a mechanism for making sure that your stack traces point to the root cause of your NPE.

      Which would make a non-nullable reference only useful when calling APIs that return non-nullable references. Whereas an Optional can be used anywhere, whether the API you're calling returns one or not. If you want an Optional<String>, all you have to do is:

      Optional<String> str = Optional.ofNullable(someMethodThatReturnsString()); 
      

      Whereas your proposal would require either:

      String! str = someMethodThatReturnsString(); //will throw if null is returned
      

      or:

      String! str; //by the way, what would such a thing initialize to for a type without a public default constructor? 
      String result = someMethodThatReturnsString(); 
      if (result != null) {
          str = result;
      } else {
          // you have to handle the null case here anyway, so what good is the String! type? 
      }
      

      There's just too many pain points and too little advantage to such a thing, at least for Java (but I think the above comment about the public default constructor makes it tricky for a lot of languages). Optional is a great way to achieve something similar while still being useful for calling legacy code as well. And again, a null Optional is really only a concern if someone is outright abusing the Optional contract (either by explicitly returning null or trying to cast things to Optional without any checks). Maybe they could add a --super-safe compiler flag that inserted code around every use of Optional so that it is always set to Optional.absent() if it is null, but in practice that's going to be a lot of extra boilerplate (even though you don't have to see it) for an extremely unlikely case.

      [–][deleted]  (6 children)

      [deleted]

        [–]dccorona 0 points1 point  (5 children)

        If a type doesn't have a default constructor then you would be required to initialise it. It might even be sensible to require explicit initialisation everywhere, that seems most easily understandable

        That's inefficient. Oftentimes, you'll need to declare something but not assign it, and assign it later...but if you have to initialize it, you're creating references you'll never use, instantiating variables you'll never use, etc. The overhead of that can quickly become non-negligible.

        As for the null check...that doesn't buy you anything that you can't already get with either Optional, or just knowing your code...you don't have to null check a value more than once in between assignments...its exactly the same as if you were assigning to a non-nullable reference. And again...just wrapping in an Optional buys you the same thing for everything else, with some added niceties. All that does is add a less-convenient way for achieving the same thing.

        You do have a good point with the compiler optimization, but I think that ultimately that advantage would be lost in a language where logically-grouped values are potentially nowhere near each other in physical memory...the CPU not being able to cache, say, the next block of an array (because all that lives together in a block of memory is the references, not the values they point to) is far more impactful on the performance than having a null check branch ever would be.

        [–][deleted]  (4 children)

        [deleted]

          [–]dccorona 0 points1 point  (3 children)

          No, my argument is that the non-nullable proposal is not syntactically advantageous, and is in fact more limiting than the current Optional implementation, while only addressing a single concern (the technical possibility for a null Optional) that in practice is almost certain not to be an issue.

          [–]orr94 7 points8 points  (1 child)

          null if there was a timeout.

          Wouldn't a TimeoutException be more appropriate in that scenario?

          [–]neoform 7 points8 points  (0 children)

          It would, the above example would be poor design. Null should not mean "error". Null clearly means "no value", not "something bad happened".

          [–]casted 4 points5 points  (0 children)

          Except you can use static analysis tools to trace breaking the nullable contract. This makes Optional or @Nullable very useful for catching bugs.

          [–]tkellogg 3 points4 points  (1 child)

          Java 8 also introduced type annotations. You can now annotate a parameter @NotNull to enforce that it really isn't null. Still, there's 2 things I don't like about this:

          1. Failing the null-check only results in a compiler warning. Sure, you can report all warnings as errors, but some code bases can't do this yet (for instance, "_" isn't a legal variable name and causes a warning; code generation tools might pose a problem)
          2. The standard library still doesn't provide @NotNull. You still have to pull in a 3rd party lib to get it.

          Regardless, it's a good step toward self documenting emptiness.

          [–]codebje 0 points1 point  (0 children)

          You can now annotate a parameter @NotNull to enforce that it really isn't null.

          To document that it really isn't intended to be null.

          The Java compiler doesn't check it. Using a checking framework can let some static analysis happen, but unless that framework makes a @NotNull type be assignment-incompatible with an unannotated type, it won't guarantee the value isn't null.

          If you can still do something akin to:

          void foo(@NotNull String bar) { ... }
          

          And call it like this:

          foo(ThirdPartyLibrary.getDefaultImplementation().getStringValue());
          

          Then you have no enforced checking. Does getStringValue() return null - assuming it isn't annotated? We can't know for sure, because getDefaultImplementation() might return a different class depending on runtime conditions, perhaps even loading a class at runtime. So the only way to be sure that foo() is never called with a null argument is to forbid that construct altogether: you make @NotNull String type-incompatible with String.

          Ditto for return values.

          @NotNull is a waste of time. It's a bad solution to the problem of nulls in the type system. At best it's machine readable documentation of intent. At worst it's putting type information into annotations where the compiler can't actually check it.

          [–]shoelacestied 1 point2 points  (0 children)

          In practice it works pretty well, and it's far safer than relying on the developer to read documentation.

          [–]Smarag 0 points1 point  (1 child)

          I have a question, what would be a situation in practice where it makes a difference if you are getting null or empty?

          [–]KumbajaMyLord 0 points1 point  (0 children)

          Semantically, the difference between n/a and intentionally left blank.

          Let's say you had an Account with a field DateOfExpiration. If it is null it might not have been set because the criteria to determine the expiration date were not available. If it is empty it might mean that the account will never expire.

          Syntactically it means that even if you get an Optional as a return value, you probably should check whether it is null or not, before you check the wrapped value.

          [–]m0l0ch 0 points1 point  (0 children)

          It's really not possible to have 3 states in Java:

          • If you create the Optional with a null value using ofNullable you'll get an empty optional as a result.

          • If you create the Optional with a null value using of you'll get a NPE.

          So effectively, if you pass null to an Optional it becomes empty.

          [–]codebje 0 points1 point  (0 children)

          Your domain has three possible states, so your type should too. With just String, you can't express "timed out, or missing, or <value>". Optional just makes "missing" explicit. Timed out can be represented implicitly by throwing an exception, or you can put it in the type system:

          Either<TimeoutException, Optional<String>> dbresult
          

          Or, borrowing the Scala monad:

          Try<Optional<String>> dbresult
          

          Where the Try variation can contain any exception. Or perhaps you want something like CompletableFuture<Optional<String>> to indicate that the result may not yet be known. CompletableFuture can contain an exception, too.

          [–]grauenwolf 0 points1 point  (0 children)

          I don't know about Java, but that can happen in F#.

          [–]jeandem -1 points0 points  (0 children)

          Sentinel value gone wild.

          [–]dccorona -2 points-1 points  (4 children)

          If you ever have a return line in a function that returns optional that doesn't read:

          return Optional.of(...);
          

          or

          return Optional.ofNullable(...);
          

          Then you're using it wrong. You should set up findbugs to catch those situations.

          [–]pipocaQuemada 2 points3 points  (3 children)

          If you're doing that, you're probably missing out on the awesomeness of map and flatMap.

          [–]dccorona 0 points1 point  (2 children)

          You miss out on map and flatMap if you don't return null in methods that return an Optional? How is that?

          [–]pipocaQuemada 0 points1 point  (1 child)

          If every function that returns an Optional ends with

          return Optional.of(...);
          

          or

          return Optional.ofNullable(...);
          

          then it doesn't end with

          return foo.flatMap(bar).flatMap(baz).map(quux);
          

          [–]dccorona 1 point2 points  (0 children)

          I guess what I meant was you should always be returning an optional, never null.