20

I have the following problem. Let's say you have 2 Optional variables

Optional<Contact> c1 = ...
Optional<Contact> c2 = ...

and a method which needs 2 variables of type Contact

void match(Contact c1, Contact c2) {...}

and you need to unwrap both c1 and c2 Optional vars and pass them into the match() method.

My question is "Which is the most elegant way to do that in Java 8?"

So far I've found 2 ways:

  1. by using isPresent

    if (c1.isPresent() && c2.isPresent()) {
        match(c1.get(), c2.get());
    }
    
  2. by using nested ifPresent

    c1.ifPresent((Contact _c1) -> {
        c2.ifPresent((Contact _c2) -> {
            match(_c1, _c2);
        });
    });
    

Both ways are terrible in my opinion. In Scala I can do this:

for {
    contact1 <- c1
    contact2 <- c2
} yield {
    match(contact1, contact2);
}

is there a way in Java 8 to do it neater than I outlined above?

Nico
  • 381
  • 1
  • 3
  • 6
  • What do you want to do if the Optional isn't present? Just ignore or throw an exception? – Tunaki Apr 06 '16 at 10:52
  • 1
    IMHO option 1) is clearer than the Scala code as it has less magic. – Peter Lawrey Apr 06 '16 at 10:52
  • 5
    Option 2 can be written `c1.ifPresent(_c1 -> c2.ifPresent(_c2 -> match(_c1, _c2)));` also, no need to create blocks. – Tunaki Apr 06 '16 at 10:56
  • @Tunaki, if the value is not set, then match(..) should not be called at all. Also, if you omit braces, the whole construction still remains embedded. I'm not saying it's too bad, but code readability is still suffering – Nico Apr 06 '16 at 12:10

3 Answers3

13

Solution you provided in scala is just syntax sugar for using flatMaps internally. You can use flatmaps in Java 8 too (but there are no syntax sugar for it).

c1.flatMap(contact1 -> c2.flatMap(contact2 -> match(contact1, contact2)));

it is almost the same thing as solution 2 you provided. You also can use applicative functor from https://github.com/aol/cyclops-react (I'm one of contributors) or any other functional java 8 library.

Applicative functor

Optional<String> o3 = Maybe.fromOptional(o1).ap2(String::concat).ap(o2).toOptional();

For-comprehension

Do.add(o1)
    .add(o2)
    .yield(a->b->a.concat(b));
riccardo.cardin
  • 7,971
  • 5
  • 57
  • 106
  • 1
    It has to be `ifPresent` since match has return type void, and if match will return f.e. boolean it could be `Optional result = c1.flatMap(contact1 -> c2.map(contact2 -> match(contact1 ,contact2)))` – klappvisor Apr 06 '16 at 11:35
  • it is a syntax sugar indeed, but it makes code readable. Regarding, cyclops-react lib, looks great, thanks for the hint! – Nico Apr 06 '16 at 12:15
  • Actually, a better way to do it in scala (and without syntax sugar) is something like `c1.zip(c2).map { case (a,b) => match(a,b) }`. Does java have `zip`? – Dima Apr 06 '16 at 13:56
  • It doesn't use syntax sugar but still use internal optional2Iterable conversion (as described here - http://stackoverflow.com/a/26346031/5619043). So to do same thing in scala you'd need to perform that conversion yourself that will lead to some boilerplate code. You can use cyclops-react to get zip operation - http://stackoverflow.com/a/35608063/5619043 – Nikita Sapozhnikov Apr 06 '16 at 14:52
7

You could desugar the Scala for-comprehension to map/flatMap in Java 8 with a function like:

public static <T,K,V> Optional<V> map2(Optional<T> opt1, Optional<K> opt2, BiFunction<T, K, V> f) {
    Optional<V> result = opt1.flatMap(t1 -> opt2.map(t2 -> f.apply(t1, t2)));
    return result;
}

And then pass your function match

  • The two optionals don't need to have the same type: you can write public static Optional map2(Optional opt1, Optional opt2, BiFunction f) { Optional result = opt1.flatMap(t1 -> opt2.map(t2 -> f.apply(t1, t2))); return result; } } – Luca Molteni Mar 15 '19 at 11:13
  • You are right, i am going to change the answer. Thank you! – Jose Antonio Jimenez Saez Jul 18 '19 at 13:55
0

If you consider the arguments not having values as an exception then you could handle them like:

try {
    match(c1.orElseThrow(NoVal::new), c2.orElseThrow(NoVal::new));
} catch (NoVal ex) {
    ...
}

If they aren't an exceptional case then I would go with your first option as being more explicit about your intent. In my view it's pretty clear and readable, and easy to change to use orElse if you wish to switch to using defaults if the optionals are empty.

sprinter
  • 27,148
  • 6
  • 47
  • 78