7 ms·
Why is Optional<> better then throwing an exception or returning a null? The way I see it it's like this: Optional<Integer> num = getSomeRiskyNumber();
by gravypod 10y ago
Why is Optional<> better then throwing an exception or returning a null?
The way I see it it's like this:
Optional<Integer> num = getSomeRiskyNumber();
if (!num.isPresent())
return ... code to bubble up a blank optional
vs
Integer num = getSomeRiskyNumber();
if (num == null)
... throw exception or return null
I get that the author has adopted a more functional programming methodology for dealing with their data but for some tasks this isn't acceptable. To just return 1 value you've allocated at least one object (Optional) and make at least 2 function calls on it (isPresent() and get()).
You get `if (value == null)` for free. Throwing exceptions is very heavy and I'd place Optional<> above that but there isn't any way to signify the error that you acctually got. You'd need to make an Optional<Maybe<T>> where Maybe<T> supports error/exceptions. Maybe Maybe<T> holds data and you can extend it with BadMaybe<T> who extends exception or something so you can put that in instead of your value to signal your exception.
I don't see the benifit. Maybe I'm just crazy but `if (v == null)` has all the features Optional has for less of an overhead and less cognitive load. (If you're afraid of NPEs then just document all the return states of your methods and use @nullable to show when you need to check. IIRC IntellJ catches that kind of mistake).
- chii 10y agoyou shouldn't really be running `optional.isPresent()`, but rather, use a map operation, or a flatMap operation, all the way until you need to interface an outside api (in which case, you use a `getOrElse(defaultValue)`). You're right about code that branches based on `isPresent()` is semantically the same as null checks - and if you only did that and didn't use any functional paradigm like mapping on the types, you don't really get any real benefit!
- mark242 10y agoIn Scala, you're encouraged to write more bulletproof code by avoiding exceptions at all time, simply because it's easier to realize that (in your case) getSomeRiskyNumber returns an option instead of an integer. getSomeRiskyNumber map { case Some(i) => Map("risk" -> i) case None => Map("error" -> "Error retrieving number.") } map { response => Json.toJson(response) } Reading this code you know you're always going to generate a json response to the user, and depending upon what getSomeRiskyNumber() returns, the json response will either look like { "risk": 23 } or { "error": "Error retrieving number." }. No exceptions needed.
- gravypod 10y agoThis is something that can be paralleled easily with @Nullable. Your IDE will even tell you, before compiling, that you should expect a null case. Also, currently, Java doesn't have a match {} expression that works with Some() and None. I belive that would completely change my opinion and the name of the game.
- mark242 10y agoThe match is just my personal preference to deal with option types, because I personally think it's cleaner to read. This reduces the number of flatMap chains in my code, but again, personal preference. Option(JavaClassFactory.staticMethodThatCanReturnNull()).map(i => i + 1).getOrElse(0) This is a very readable function, imo, and is how 99% of Scala programmers deal with legacy Java libraries. Scala even has the Try() object, where you can do something like this: Try(OldJavaClass.methodThatThrowsExceptions()).toOption.map(i => i + 1).getOrElse(0) It makes creating service-layer code so much simpler because you aren't caught in that rut of "well now I have to deal with this exception at the controller level". You can head off all that garbage very quickly. I suspect that a lot of Java Optional coders will start acting like this.
- pasta 10y agoBut then you will never know in your code if there was an error. What if you want to send an other HTTP response in case of an error? With an exception you can still generate a json response, but also handle errors in the way you like.
- mabbo 10y agoSimple answer: never return null. If something is wrong, throw an exception. If it's the kind of error that must be handled, throw a checked exception. If there's no value to return, return Optional.empty. The code receiving that Optional<Foo> is now free to do what the author is suggesting: map, filter, all without worrying if the value is actually there or not. The code is cleaner, easier to read, etc. But no one should ever have to check for null anymore.
- JustSomeNobody 10y agoOr use the null object pattern. Then you can write code that "just works"; kind of a higher level version of a nop.
- specialist 10y agoThe Null Object pattern is The Correct Answer (for Java like languages). Java's Optional is a bad idea implemented badly made even more bad by bad usage.
- mark242 10y agoOption is a takeoff of the Null Object pattern. Option(null).map(str => str + "!").foreach(str => println(str)) is a noop.
- aarongolliver 10y agoYeah, the Null Object Pattern is just optionals except you have to re-implement it each you need optional semantics.
- specialist 10y agoNo. They're opposites. "...an object which implements the expected interface, but whose method body is empty. " https://en.wikipedia.org/wiki/Null_Object_pattern https://en.wikipedia.org/wiki/Null_Object_pattern
- 10y ago
- dragandj 10y ago> To just return 1 value you've allocated at least one object (Optional) May be nitpicking, but in your other example, you've allocated at least one object (Integer) to just return 1 value, which means that supertight performance is not important in getSomeReskyNumber
- andrewjf 10y agoI think the most benefit of wrapping the result in an optional is so you can use compiler to enforce exhaustive pattern matching[1]. Optional<Integer> num = getSomeRiskyNumber(); match num { Some(x) => ... ; None => return error or whatever; } The compiler will verify you at least pretended to look at the None result instead of just ignoring it (but you could still swallow the none). 1: does java even have this?
- gravypod 10y agoTo my knowladge the Java library does not although it is fully capable of supporting this. I guess this is just an example of poor implementation. It could have been easily implemented like this Optional<X> x = ...; switch (status(x)) { case SOME: return x.get() * 10; case NONE: .... send error ... } Again I really like the matching functionality but as it stands, since it's not included, it's not worth much without it in my opinion. With match, that cleans up a LOT of code and if the compiler can guarantee no speed or memory hits for doing this it becomes very valuable very quick. It's one of the reasons I wanted to learn Rust.
- Sharlin 10y agoIncidentally, Scala discourages using pattern matching in cases like this (pun not intended), preferring the use of combinators (map/flatMap/etc).
- andrewaylett 10y agoThe closest equivalent would be something like: Optional<Integer> num = getSomeRiskyNumber(); return num.map(x -> ...).orElseGet(return error or whatever); Which is just as type-safe. There are a number of variants, including flatMap for when the map operation itself returns an Optional (the monadic bind operator) and a number of options for how to deal with the empty case.
- coldtea 10y ago>Why is Optional<> better then throwing an exception or returning a null? https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare https://www.infoq.com/presentations/Null-References-The-Bill...
- ahoka 10y agoOptional types shine the best when you can chain them: BufferedImage avatar = users.getUser(userName) .flatMap(User::getAvatarUri) .flatMap(ImgIO::read) .orElse(anonymousAvatar); I made this up, but I hope you see the point. Optional is just a fancy null pointer if you don't use it in a functional way. :-)
- Joeri 10y agoFirst of all, the use of isPresent is a red flag that optionals aren't used right. You'd typically use a sequence of map, flatMap and orElse calls. For example, this is some controller code I'm currently shipping: return Drawing.getForLocation(id) .map(d -> ok(Json.toJson(d))) .orElse(notFound("No drawing for location " + id)); That construct is more readable to me than an if(drawing != null) check. The cognitive load issue is a bit of a red herring. I'm used to optionals now and they're no more of a cognitive load than an if construct. But that's eye of the beholder stuff, so let's park the cognitive load issue for now. Getting down to the meat of your argument, why is optional better than an if not null check or an exception? I think anyone can see why it's better than throwing an exception. Optional makes it obvious to the caller that (a) there can be no response, and (b) what happens exactly when there's no response. With exceptions, either they're unchecked, in which case you don't know what exception to catch without peeking, and it's really easy to forget to add a try/catch handler at all; or it's checked, but even in that case the handling code is uglier than an equivalent optional construct. The more interesting question is why it is better than deliberately returning null. Again, it's about signaling. A library author can signal to the caller that they should expect not to get a value sometimes. Yes, it's possible to return null from a method that returns an optional, and this will cause an NPE, but in that case the error is the library's, not the callers. In making API's, you want to encourage correct use, and optionals help do that. Optionals in java suffer from the same problem as most API's in java do though. They're uglier than they need to be, and some common use patterns are awkward. For example, in my spark code in scala I'll use constructs like this one to convert a stream between types where the conversion might fail for some elements (and I don't care about the ones where it does): val someIntermediate = someStream.flatMap(convertMaybe) That works because Option can be handed to flatMap directly. But in Java streams, you can't do that, and you end up with constructs like this: someStream.map(convertMaybe).flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty()) Which is insane. In Java 9 they're kind of sort of fixing this and it's going to be someStream.map(convertMaybe).flatMap(Optional::stream) Which is still ugly though. Any way, I think optional has its uses in Java. It's definitely something that helps when building out robust API's. But where optionals would be most valuable, the combination with streams and futures, is where they're painful to work with. That's par for the course in Java though. Oracle's not actually all that good at designing API's. Usually the first attempt at something turns out so broken it has to be replaced wholesale later (like java.util.Date), or it's so ugly it feels bad to use it (CompletionStage? seriously?). But, to each their own, I'm sure my attempt at it would be even worse, and despite the warts I still like Java 8 overall.
- village-idiot 10y agoThe point of a static type system is to have the language help you out as much as possible to write type-error free code. It can't guarantee that you haven't messed up, but it's supposed to help. Null ruins that. It's a known source of runtime exceptions, and it is not a compilation error to return null or forget the null-check. It is a compilation error to pretend that an Optional<T> is just a T. The Java compiler is not smart enough to prevent you from doing something silly like calling get() on Optional<T> without checking isPresent() first, but at least it gives you something. Also Optional has some really nice creature comforts. It gives you the ability to map to compose an Optional<T> with functions that couldn't care less about the Optionality, and it lets you use flatMap to chain successive calls that might fail together, avoiding an arbitrary number of null checks (that you might forget!) in your code. Frankly arguing against Optional<T> is like arguing against map on lists/streams and saying that for is good enough for you. You can do it, but I think you'd be nuts to work against yourself like that.
- zigzigzag 10y agoKotlin does it more efficiently. But you wouldn't want it to be an error to use get without calling ifPresent. Sometimes you know that the type system is wrong and that in fact, although this thing is optional, it will in your case always be there. So being able to rapidly convert optionality into an error (we might call it a NullPointerException) is important. Kotlin has the !! operator for that.
- staticassertion 10y agoOptional forces the null check (at the cost of a pointer dereference :( but that's Java for you). That's the difference. It also provides some nice combinator syntax for handling that null beyond .isPresent, but if you were to just get .isPresent it would be a win. Documentation is optional (hah) and therefor shouldn't be relied on. Optional as a return type means there's no need to check for documentation, there's no need for @Nullable, and you get some pretty syntax to boot.
- wrsh07 10y agoA few things that need responding to. First, optional is the same as a maybe. They're different names for the same thing. If you want to return a status instead of nothing, you use an Either type (as mentioned in the post). In an Either, you populate left or right, but not both. You can let left be your status and right be your template type. You could codify this as StatusOr<T>. In a StatusOr land, you return if there's an error; otherwise you continue the computation. This gives a few advantages, but one is that you control the flow of control. (This is also done decently with exceptions in Java, iirc) However, all of these - maybes, either - they make your functions composable. There's a good talk... Railroad oriented programming... that dives into this.
- wrsh07 10y agoA few things that need responding to. First, optional is the same as a maybe. They're different names for the same thing. If you want to return a status instead of nothing, you use an Either type (as mentioned in the post). In an Either, you populate left or right, but not both. You can let left be your status and right be your template type. You could codify this as StatusOr<T>. In a StatusOr land, you return if there's an error; otherwise you continue the computation. This gives a few advantages, but one is that you control the flow of control. (This is also done decently with exceptions in Java, iirc) However, all of these - maybes, either - they make your functions composable. There's a good talk... Railroad oriented programming... that dives into this.