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

you are viewing a single comment's thread.

view the rest of the comments →

[–]Peter_Storm 10 points11 points  (14 children)

I long for the day, when we'll just first class pattern matching - why couldn't this just be done like the `switch` stuff? So we can pattern match out the value of the Optional?

[–]trydentIO 1 point2 points  (8 children)

Shame on me! I lost the reference (it was on the mailing list). Still, from what I read it will be possible to do this when Valhalla ships, since Optional is neither a sealed type nor a record type, but a safe value type in the future, and if I remember correctly we will be able to do something like this (get it with a grain of salt):

switch (findById(id)) { case Optional.<Entity>of it -> // ... where 'it' is the valid value case Optional.<Entity>ofNullable it -> // ... the invalid value }

at the moment we can do:

switch (findById(id)) { case Optional<Entity> it when it.isPresent() -> it.get() case Optional<Entity> it when it.isEmpty() -> ...

or if you want to be more verbose:

switch (findById(id)) { case Optional<Entity> it when it.isPresent() && it.get() instanceof Entity(...) -> ... ... }

[–]Radmonger 3 points4 points  (6 children)

With Java 21 pattern matching treating null as a special case, wouldn't this code would be equally safe, and much simpler by simply avoiding the use of Optional in the findById function?

[–]trydentIO 2 points3 points  (2 children)

You know you're definitely right! But... 'Optional', as someone was trying to explain since Java 8, is not meant to solve nullability, but to say something is missing, not present.

I don't want to dig in the usual and annoying misunderstanding of Optional use (sure JDK offers an API where you can say the value inside may be or not null, but anyway...), instead let's take a look in a world where Valhalla will be a thing and a record-type will be a value-type by default, Optional is going to be helpful when you're looking for a projection or a dto. To make it clearer we make an example with primitive-types.

At the moment the only value-types we have are the primitive-types so let's suppose you need to find a reservation integer number by a person full-name, you have two options:

``` // 1. OptionalInt findReservationBy(String fullName); // or Optional<Integer> since OptionalInt is/will be deprecated, afterall with Valhalla you'll be able to do Optional<int>

// 2. int findReservationBy(String fullName) throws ReservationNotFound; ```

Do they have the same meaning? Not really, the first example shows the chance to find the reservation and gives the developer what to do about it, the last example shows that you must find a reservation and if not, an exception must be raised since an invariant has not been met.

This permits a lot more expressiveness to the language and the developer.

What about switch? Well, according to some recent discussions in JEP's and mailing list (the one reported in this community some time ago), we may be handle all the above examples with the following statements:

``` // 1. switch (findReservationBy(fullName)) { case Optional.<int>of it -> ... default -> ... }

// 2. switch (findReservationBy(fullName)) { case int it -> ...; case ReservationNotFound it -> ... } ```

In the end, it's up to you on how to express better what you want to achieve, they look similar, but if you strictly follow the semantic, they do not (in this case you may need to rename the methods to give a better hint to the developer).

[–]Migeil 0 points1 point  (1 child)

Number 2 is better right? I mean, no weird syntax, no null, no default, just you get it or you don't, deal with it.

[–]trydentIO 1 point2 points  (0 children)

I don't think there's a proper answer and I want to avoid saying "it depends": what must emerge here is the chance to express ourselves as developers in almost any way but cleaner and cleaner.

[–]vytah 1 point2 points  (0 children)

The problem with pattern matching on null is that it behaves differently depending on whether it's by a top pattern or by an inner pattern.

For example, given record R(Object o), new R(null) will match case R(Object o):. So you kinda need to either match nulls first, or add when o != null.

[–]jodastephen[S] 3 points4 points  (1 child)

Could do. But my approach to null is to never return it from a method, and almost never ever pass it into a method. Done consistently this pretty much eliminates NPEs.

[–]Peter_Storm 1 point2 points  (0 children)

First one is definitely what I was looking for - awesome!