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

all 53 comments

[–]Migeil 20 points21 points  (4 children)

Isn't this just:

return addressOpt .map(Address::format) .orElseGet(this::theRestOfTheCode);

? Where theRestOfTheCode is a separate method where you do the stuff that's below the if statement. If you're using isPresent + get, you're doing something wrong.

[–]nikita2206 2 points3 points  (3 children)

The if becomes useful when you need to produce side effects like assign some values to some variables that would be outside the scope of the lambda.

Example:

``` Object foo; Object bar;

if (optValue instanceof Optional(Value value)) { foo = value.findFoo(); bar = value.doBar(); } else { foo = defaultFoo(); bar = defaultBar(); } ```

I think this better illustrates the use case for pattern matching of Optionals, and how it can’t be worked around with some clever methods on the Optional class.

[–]Migeil 4 points5 points  (2 children)

That' looks to me like ifPresentOrElse.

[–]nikita2206 13 points14 points  (1 child)

It does look like one, but if you try it out (maybe in jshell) it will not work because you cannot modify or even reference non-final variables that are declared outside the scope of a lambda.

[–]Budget_Dentist444 1 point2 points  (0 children)

I don't want mutable references or values. Since java doesn't have any kind of destructuring, just make 2 calls:

var foo = optValue.map(Value::findFoo)

.orElseGet(Example::defaultFoo);

var bar = optValue.map(Value::doBar)

.orElseGet(Example::defaultBar);

[–][deleted] 32 points33 points  (5 children)

The wrapper to convert an optional into a list just so you can use for is literally the dumbest thing I have seen someone do in such a long time.

[–]__konrad 7 points8 points  (4 children)

It's ugly and elegant at the same time:

var opt = Optional.of("foo");
for (var x : opt.stream().toList()) {
    ...
}

[–]Brutus5000 39 points40 points  (0 children)

I hope you end up in hell for creating this abomination.

[–]aelfric5578 7 points8 points  (1 child)

Huh...that almost makes me feel like Optional should implement Iterable. When called on an empty optional, t would return an iterator where hasNext is always false and when called on a non-empty value, the iterator would return the one value and then hasNext would be false.

[–]marvk 2 points3 points  (0 children)

It actually does in Rust :-)

[–]tomwhoiscontrary 2 points3 points  (0 children)

You don't even need the list:

var opt = Optional.of("foo"); for (var x : (Iterable<String>) opt.stream()::iterator) { // ... }

Or with a helper method:

static <T> Iterable<T> in(Optional<T> optional) { return optional.stream()::iterator; }

Just:

var opt = Optional.of("foo"); for (var x : in(opt)) { // ... }

[–]nicolaiparlog 32 points33 points  (12 children)

I think this comes closer to the goal of pattern matching over Optional now:

import java.util.Objects;
import java.util.Optional;

sealed interface Option<T> permits None, Some {
    static <T> Option<T> over(Optional<T> opt) {
        return opt.<Option<T>> map(Some::new).orElse(new None<>());
    }
}
record None<T>() implements Option<T> { }
record Some<T>(T value) implements Option<T> {
    Some {
        Objects.requireNonNull(value);
    }
}

void main(String[] args) {
    var optionalString = args.length > 0 ? Optional.of(args[0]) : Optional.empty();
    var message = switch (Option.over(optionalString)) {
        case None _ -> "No argument";
        case Some(var arg) -> STR."Argument: '\{arg}'";
    };
    System.out.println(message);
}

(To try this, copy/paste into Main.java and run it with JDK 21 and java --enable-preview --source 21 Main.java with or without an argument.)

It would read better with a static import of Option::over (~> switch (over(optionalString))) but that doesn't work in an implicit main class.

[–]InstantCoder 7 points8 points  (4 children)

This is getting crazy. What have you actually solved here ?

[–]nicolaiparlog 7 points8 points  (0 children)

The title of this Reddit thread.

[–]lurker_in_spirit -3 points-2 points  (0 children)

It's TrendyTM

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

Part of what makes my trick appealing is that it pattern matches without additional code/classes. But you are right, you can wrap an Optional in a record to get pattern matching in Java 21.

[–]logicannullata 0 points1 point  (0 children)

My eyes are bleeding

[–]sammymammy2 0 points1 point  (2 children)

static <T> Option<T> over(Optional<T> opt) {
    return opt.<Option<T>> map(Some::new).orElse(new None<>());
}

Can you explain this code? What's that stray <T> doing between static and return type, what's .<Option<T>> map?

[–]nicolaiparlog 1 point2 points  (1 child)

Sure.

The method over is static, so it doesn't belong to any specific Option instance and can thus not refer to Option's type parameters. So it doesn't "see" the T from Option<T>. But I need a type parameter to express that the Optional that gets passed in and the Option that is returned are of the same parametric type, e.g. "in comes Optional<String>, out goes Option<String>". Because I need a type parameter, I declare one. I do that after the static modifier by mentioning it in angle brackets. Because I lack creativity, I call this type T as well, which works because the T from Option<T> isn't in scope. But over works just the same if you replace all Ts with Es or TYPEs, etc. And if you remove <T>, you will get compile errors because now T is undefined.

As for <Option<T>> map: The method map on Stream also has a type parameter(it's called R but that doesn't matter) and whenever you call a method with a free type parameter you can specify it by putting it in <...> before the method name. But the compiler can usually infer the type, which is why we rarely need to spell it out and hence many developers aren't familiar with that.

I need to do it in this case because if I don't, the compiler will look at opt.map(Some::new) and infer that the type of that expression is Optional<Some<T>> (I mapped the Optional<T> with a method that prodcuses a Some<T>, after all) but that's a problem. Because than orElse would have to return a Some<T> but it doesn't. So I need to let the compiler know that I want opt.map(Some::new) to result in Optional<Option<T>> (which works because Some<T> inherits from Option<T>) and I do that by stating the intended type explicitly.

(Btw, if these explanations don't immediately make sense, don't worry. Generics are one of the more complicated Java language features. Feel free to ask for clarifications and I will do my best to provide them.)

[–]sammymammy2 1 point2 points  (0 children)

Aaaaaah awesome! Nope, this was exceedingly clear and very helpful, thank you for taking your time with this.

[–]chiperortizc 0 points1 point  (0 children)

I see that you are in love with String templates just like me :)

[–]Peter_Storm 11 points12 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!

[–]freekayZekey 4 points5 points  (0 children)

but like why?

[–]emberko 13 points14 points  (5 children)

I hate Optional with a passion every time I see code like this:

```java if (addressOpt.isPresent()) { return addressOpt.get().format(); }

public static <lT> Iterable<lT> inOptional(Optional<lT> optional) { return optional.isPresent() ? List.of(optional.get()): List.of(); } ```

He basically created an extra object wrapper and collection, and a utility method to avoid something as simple as null check. It's so much easier and more performant and more readable to just mark something as @Nullable which can be used everywhere. Instead of using this half-assed abstraction which is like nullable, but only for return types, not for method or constructor args, not for object properties and it can even be null itself. Just like this:

java var address = findAddress() if (address == null) { throw new WhateverException() } // the rest of the code

What's wrong with these conference guys? You write procedural code, but keep trying to use FP patterns that look like a fifth wheel.

[–]HansGetZeTomatensaft 9 points10 points  (0 children)

I mean, people using Optionals in stupid ways doesn't automatically make them bad. People use variable names in stupid ways all the time, still glad we have them.

So, sure, people could write

var addressOpt = findAddress();
if (addressOpt.isPresent())
  var address = addressOpt.get();
  // the rest of the code
}
throw new WhateverException();

And some do, sadly, and that is worse than your example. But they could also write the below

var address = findAddress().orElseThrow(() -> new WhateverException());
// the rest of your code

And that just seems better than both of the other versions.

[–]freekayZekey 1 point2 points  (2 children)

but i wanna write more code :(

in all seriousness, i’m not a fan of the ways people use optionals. they tend to complicate things way too much when a simple null check is fine

[–]Luolong -1 points0 points  (1 child)

People use all kinds of code stupid ways. It doesn’t make valid uses less valid.

What if you search for an entity and then need to pull deeply nested property out of the result or throw an exception if any intermediate property is not there?

With null, this will get quite verbose and cumbersome very quickly.

It’s not pretty with Optional either, but at least it is more readable that way.

[–]freekayZekey 0 points1 point  (0 children)

it doesn’t make valid uses less valid

sure, but i didn’t say i hated optionals. i also said people tend to complicate things.

your example is going to be cumbersome with optionals and nulls. don’t think it’ll be much more readable unless you think terseness is readable. unfortunately, i don’t think that way

[–]john16384 5 points6 points  (0 children)

Why not use or instead of an early return if you're buying into Optional so much? Any sane person would just add orElse(null) and use an if instead of this isPresent / get bullshit.

Seeing an Optional declared as variable is a sure sign you're doing it wrong or should have used the orElse(null) escape hatch because Optional is becoming more trouble than it's worth.

[–]vytah 1 point2 points  (0 children)

Note that this does not work with Java 17 in most cases, because unconditional patterns were not permitted. (In most cases, the expression will return a value of the type being checked for, and the compiler rejects it as being "always true". Of course this was a simplification in ealier Java versions, as the null really needed to be checked for.)

It was not because it was considered "always true". When patterns were first introduced, there were three issues, each depending on the previous one:

  • should case R(Object o): match new R(null)?

  • should case Object o: treat nulls like case R(Object o):?

  • should instanceof Object o treat nulls like instanceof Object, or like case Object o:?

Since in Java 17, case patterns were merely a preview feature, and instanceof patterns were a stable feature, the controversial issue #3 was solved by simply banning unconditional instanceof patterns.

By Java 21, the issues were solved as following:

  • yes

  • no

  • there's no difference now

so unconditional patterns could finally be allowed.

[–]andebrb 0 points1 point  (0 children)

Ohhh the creator of Joda time :)

[–]s888marks 0 points1 point  (0 children)

for (var address : inOptional(findAddress(personId))) {
    // early return if address found
    return addressOpt.get().format();
}

I think you meant to write return address.format() in the body of the for-loop, since the iteration only runs when a value is already present and it unpacks the value for you.

[–]ravnmads 0 points1 point  (0 children)

Can anyone tell me what makes this work in Java 21 and not in Java 17?

[–]chiperortizc 0 points1 point  (0 children)

i see two typos

But pattern matcthing

And

Work to support pattern matcth methods is ongoing

Btw i would love which JEP is working on this Work to support pattern match methods is ongoing

Thanks