在 Java8中链接可选项

寻找链接可选项的方法,以便返回出现的第一个可选项。如果不存在 Optional.empty(),则应返回。

假设我有几个这样的方法:

Optional<String> find1()

我想把他们锁起来:

Optional<String> result = find1().orElse( this::find2 ).orElse( this::find3 );

但是这当然不起作用,因为 orElse期望一个值,而 orElseGet期望一个 Supplier

68564 次浏览

You could do it like this:

Optional<String> resultOpt = Optional.of(find1()
.orElseGet(() -> find2()
.orElseGet(() -> find3()
.orElseThrow(() -> new WhatEverException()))));

Though I'm not sure it improves readability IMO. Guava provides a way to chain Optionals:

import com.google.common.base.Optional;


Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3());

It could be another alternative for your problem but does not use the standard Optional class in the JDK.

If you want to keep the standard API, you could write a simple utility method:

static <T> Optional<T> or(Optional<T> first, Optional<T> second) {
return first.isPresent() ? first : second;
}

and then:

Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3()));

If you have a lot of optionals to chains, maybe it's better to use the Stream approach as other mentionned already.

Use a Stream:

Stream.of(find1(), find2(), find3())
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

If you need to evaluate the find methods lazily, use supplier functions:

Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

Maybe one of

    public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) {
if (first.isPresent()) return first;
for (Supplier<Optional <? extends T>> sup : supp) {
Optional<? extends T> opt = sup.get();
if (opt.isPresent()) {
return opt;
}
}
return Optional.empty();
}


public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) {
if (first.isPresent()) return first;
Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent);
return present.findFirst().orElseGet(Optional::empty);
}

will do.

The first one iterates over an array of suppliers. The first non-empty Optional<> is returned. If we don't find one, we return an empty Optional.

The second one does the same with a Stream of Suppliers which is traversed, each one asked (lazily) for their value, which is then filtered for empty Optionals. The first non-empty one is returned, or if no such exists, an empty one.

Inspired by Sauli's answer, it is possible to use the flatMap() method.

Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.findFirst();

Converting an Optional into a Stream is cumbersome. Apparently, this is going to be fixed with JDK9. So this could be written as

Stream.of(this::find1, this::find2, this::find3)
.map(Supplier::get)
.flatMap(Optional::stream)
.findFirst();

Update after Java 9 was released

Although the original question was about Java 8, Optional::or was introduced in Java 9. With it, the problem could be solved as follows

Optional<String> result = find1()
.or(this::find2)
.or(this::find3);

To perform Optional Chaining First convert Stream to Optional Using either of the two methods

  1. findAny() or findFirst()
  2. min() / max()

Once optional is obtained optional has two more instance method which are also present in Stream class i.e filter and map(). use these on methods and to check output use ifPresent(System.out :: Println)

ex:

Stream s = Stream.of(1,2,3,4);

s.findFirst().filter((a)->a+1).ifPresent(System.out :: Println)

Output is : 2

Based on Alexis C's answer, but without the nesting of orElses

String result = find1()
.map(Optional::of)
.orElseGet(Foo::find2())
.map(Optional::of)
.orElseGet(Foo::find3())
.orElseThrow(() -> new WhatEverException())

Remove the orElseThrow if you want an Optional<String> as the result instead.

The trick is to wrap each optional returned by the findX to another optional before each orElseGet.

for cascading chaining You could use ifPresentOrElse

find1().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
find2().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
find3().ifPresentOrElse( System.out::println, new Runnable() {
public void run() {
System.err.println( "nothing found…" );
}
} );
}
} );
}
} );

to do something with the value of the Optional You had to replace the System.out::println with Your Consumer
(different Consumers would also be possible in this solution)

// Java 9+
find1().or(() -> find2()).or(() -> find3());




// Java 8
Optional.ofNullable(
find1().orElse(
find2().orElse(
find3().orElse( null )
)));

My common generic solution for all those issues:

public static <T> T firstMatch(final Predicate<T> matcher, final T orElse, final T... values) {
for (T t : values) {
if (matcher.test(t)) {
return t;
}
}
return orElse;
}

Then you can do:

public static <T> Optional<T> firstPresent(final Optional<T>... values) {
return firstMatch(Optional::isPresent, Optional.empty(), values);
}

Since Java 9

most likely case the readers are looking for (today)

result = find1()
.or(this::find2)
.or(this::find3);

Java 8

result = Optional.ofNullable(find1()
.orElse(find2()
.orElse(find3()
.orElse(null))));