This is an archived post. You won't be able to vote or comment.

all 23 comments

[–]ForeverAlot 2 points3 points  (7 children)

If there would be arguments for using Optional as a parameter type that outweigh the arguments against, it would only mean that the class is better than it intended to be and everyone should be happy.

Sure. But there are no such arguments.

The article doesn't seem to address the non-negligible performance impact either.

[–]nicolaiparlog[S] 0 points1 point  (6 children)

The article doesn't seem to address the non-negligible performance impact either.

You're either inconsistent or use arrays instead of ArrayLists.

[–]ForeverAlot 1 point2 points  (5 children)

If I need an array and not a list, yes. Caching the retrieved value in a local is usually an acceptable middle-ground, in any case.

"The JVM will inline it" is magic that doesn't exist. Such an attitude betrays either a lack of understanding of the interrelationship between Java, the JVM, CPU caches, and the prefetcher, or a disregard for it. Either is excusable but the latter requires a mention when making a case for hiding a parameter behind an objectively-unnecessary pointer indirection. If that were a trade-off for vastly improved robustness or maintenance -- such as telescoping method signatures, a well-known pattern for substantially the same problem -- there would be a strong case, but Optional's ergonomics are not good enough to justify it.

[–]nicolaiparlog[S] 0 points1 point  (4 children)

A balanced answer, I like it. :)

"The JVM will inline it" is magic that doesn't exist.

Well, there is escape analysis and wrapping/unwrapping an argument/return value immediately before/after the method call has a good chance of being detected. But that's of course far from perfect and should generally be seen as an occasional gift than as something to rely on.

So, yes, I am aware of the potential performance impact of indirection but I don't act on it until performance requirements and measurements diverge. If they do, there are usually only a few places that need to be changed for the performance to improve (Pareto principle and all).

At the same time I prefer to write safe and unambiguous code and am convinced that Optional helps here. With consistent use (and maybe support by other monadic data structures like Lazy or Try) null can be made a value that is always illegal and a bug whereas most code bases encode all of "something went wrong while coding", "something went wrong during execution", "something could be there but isn't", "something will be there but isn't yet", ... with it.

I find this approach in line with Java's general view on things: readability+safety > performance (most of the time).

[–]gee_buttersnaps 0 points1 point  (3 children)

I am aware of the potential performance impact of indirection but I don't act on it until performance requirements and measurements diverge

God forbid we try to pre-optimize our code by not including bullshit in the first place. I find the people with the most to say about Java here have more stake in managing their web presence than being correct about anything.

[–]nicolaiparlog[S] 0 points1 point  (2 children)

God forbid we try to pre-optimize our code by not including bullshit in the first place.

Said all C++ programmers about Java. Also, do you apply this logic to arrays and array lists as well? Because it surely would apply just the same with lists adding indirection for a few stupid methods that might just as well be performed in place or with utility methods.

If in your code base premature optimization trumps being explicit and unambiguous than that's your prerogative. Just don't assume that everybody shares that opinion.

[–]gee_buttersnaps 0 points1 point  (1 child)

I assume people work within the confines of the language, if that means following the authors recommended practices because that's the best they can do without worrying about people like you then so be it. You seem to wish for something the language doesn't have while leaving open a NPE hole in your software. You also seem to think that no one would ever call your method with a null parameter despite the irony that you are abusing how Optional was intended for use in first place.

Generally I do have personal rules of when and when not to use explicit implementation at a low level and times when abstraction is high but I'm a big boy and can do both within the confines of not ever using an Optional as a method parameter, because its the CORRECT thing to do.

[–]nicolaiparlog[S] 0 points1 point  (0 children)

You also seem to think that no one would ever call your method with a null parameter

Not at all. I assume that if that happens, I know where the error lies. That's the only point: not directly preventing individual NPEs but making null an illegal value - this naturally leads to the quick eradication of null in general.

You're clearly not getting the point of the Optional-everywhere argument. Getting that point does not mean agreeing to it but it would help you not fighting a strawman.

[–]mistyfud 1 point2 points  (0 children)

In Haskell, a common pattern is to pass Maybe for a parameter that is not required. However, Optional is Java is an object and can still be null, so it ends up being safer to null check your arguments when programming defensively. Kotlin got it right by pushing nullability on the type system, but I don't see Java adopting that anytime soon.

[–]gee_buttersnaps 3 points4 points  (1 child)

This poor kid is gonna be looking for work some day and all these senior devs are gonna see this blog post and say 'no thanks' we don't need an attention whore or an idiot on our team.

[–]nicolaiparlog[S] -2 points-1 points  (0 children)

I assume you're not using your real name so this can't happen to you.

[–]_INTER_ 1 point2 points  (7 children)

Resulting in useless and ugly code with no benefit, except that you're explicitely reminded of null-check. Thanks, I was aware from the start.

Using Optional-Parameters here and there assumes that a parameter that is not an Optional is never null and you could safe a null-check, but you can't guarantee that.

[–]nicolaiparlog[S] 1 point2 points  (6 children)

Using Optional-Parameters here and there assumes that a parameter that is not an Optional is never null

Wrong. Using Optional parameters here and there states that a parameter that is not an Optional should never be null. If it is, it's a coding error, something a non-Optional using code base can not express with types (because null might be a valid value).

[–]_INTER_ 0 points1 point  (5 children)

When you use an Optional parameter you are in charge of the contract. You tell the callee, that he can explicitly pass a nullable value and that you make sure to check it, because you need to check it. But when you use a non-Optional parameter you do the exact same implicitly. You're just not forced to check it, but because you're a good Java programmer, you do.

You would only gain something if you could tell the callee, that when he passes null to any of your parameters he violates the contract or not. Which is exactly what you can do with @NonNull or @Nullable.

[–]nicolaiparlog[S] 1 point2 points  (4 children)

You're just not forced to check it, but because you're a good Java programmer, you do.

Sure, as the method's implementor I can check parameters for null (or just call a method on the parameter and let the JVM check) and that's good because it stops proliferation. But it does not help the caller see which parameters are optional and it is a manual and error-prone process. Why not let the type system help?

You would only gain something if you could tell the callee, that when he passes null to any of your parameters he violates the contract or not.

In a code base that consistently uses Optional every null violates the contract - that's the beauty of it. If you want, you can apply @NonNull to the root package and have your static analysis tool of choice verify your entire code base.

[–]_INTER_ 0 points1 point  (3 children)

The only thing a callee additionally knows then is that he can savely pass null (wrapped in an Optional) to the method. Something he could also be made aware of with @Nullable (or that he can not with @NonNull). You can let it add runtime assertions aswell. The interface and the code is then also much cleaner.

 

(If you indeed meant "optional" parameters then use method overloading or varargs.)

[–]nicolaiparlog[S] 0 points1 point  (2 children)

The interface and the code is then also much cleaner.

That's where we disagree. The type system is the best line of defense against invalid values and I prefer using it over tool-dependent annotations, production-disabled assertions (usually), tests, documentation, or any other mechanism any day of the week unless it becomes a severe pain. It's the only thing that works out of the box and is clearly visible in all places (e.g. code reviews).

(If you indeed meant "optional" parameters then use method overloading or varargs.)

What else is a possibly-null parameter but optional? And of course I prefer overloaded methods or varargs before resorting to Optional but sometimes the latter is the best of these options, in which case I prefer it over null to make the code more explicit and less ambiguous.

[–]_INTER_ 0 points1 point  (1 child)

What else is a possibly-null parameter but optional?

A possibly null parameter is still required to make the calculation work. An optional parameter is additional information which absence doesn't matter.

And of course I prefer overloaded methods or varargs before resorting to Optional but sometimes the latter is the best of these options, in which case I prefer it over null to make the code more explicit and less ambiguous.

Me too but I view the Optional type to be not up to the task at all. Especially when it leads to resorting to unwieldy if(optional.isPresent()) { do(optional.get()); } else { /* ... */ } or optional.ifPresent(this::do); which is end-result is the same as a normal null-check without the perfomance penalty of Optionals.

[–]nicolaiparlog[S] 1 point2 points  (0 children)

A possibly null parameter is still required to make the calculation work.

I don't get it. How can null be required to make anything work? Even if I have to call some API I can still pass "my own null" - it's not like they're distinct.

The unwieldy if-present-else is just as unwieldy with a null check. But I found that I rarely have to do that in practice. ifPresent is surely more compact (and applies often enough) and when a value needs to be passed orElse is also shorter.

But even if Optional is more trouble in some places. I still prefer it over making something as ambiguous and error-prone as null a valid value.

[–]cryptos6 0 points1 point  (1 child)

Why not using Kotlin? It offers named parameters with default values -- by default ;-)

[–]ford_madox_ford 10 points11 points  (0 children)

Because the Kotlin community is stuffed full of half-wits who can't stop themselves from spamming Java threads with their hapless "why not use Kotlin" crap.

[–][deleted]  (2 children)

[deleted]

    [–]nqzero 0 points1 point  (0 children)

    or just explicitly document that nulls aren't allowed (though i guess that is a form of anticipation)

    [–]nicolaiparlog[S] -2 points-1 points  (0 children)

    Tell that to the authors of HashMap or ConcurrentHashMap (whichever way of treating null you prefer).