web-dev-qa-db-ja.com

JavaのオプションorElseオプション

私は新しい Java 8のオプションタイプ で作業しており、機能的にサポートされていない一般的な操作のように見えるものに出くわしました:「orElseOptional」

次のパターンを検討してください。

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else {
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);
}

このパターンには多くの形式がありますが、現在のオプションが存在しない場合にのみ呼び出される、新しいオプションを生成する関数を取るオプションの「orElse」が必要になります。

実装は次のようになります。

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) {
    return value != null ? this : other.get();
}

そのような方法が存在しない理由があるのか​​、意図しない方法でOptionalを使用しているのか、そしてこのケースに対処するために人々が思いついた他の方法があるのか​​、興味があります。

私は、カスタムユーティリティクラス/メソッドを含むソリューションはエレガントではないと思うべきです。なぜなら、私のコードで作業している人は、それらが存在することを必ずしも知らないからです。

また、誰かが知っている場合、そのようなメソッドはJDK 9に含まれますか?そのようなメソッドはどこで提案できますか?これは、私にとってAPIのかなり目立たないように思えます。

124
Yona Appletree

これは、orの形式のJDK 9の一部であり、Supplier<Optional<T>>を取ります。その場合の例は次のとおりです。

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

詳細については、 Javadoc または この投稿 を参照してください。

74
Nicolai

現在のAPIを前提とした最もクリーンな「サービスの試行」アプローチは次のとおりです。

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

重要な側面は、一度書く必要がある(一定の)操作の連鎖ではなく、別のサービスを追加する(またはサービスのリストを変更するのが一般的)ことの簡単さです。ここでは、単一の()->serviceX(args)を追加または削除するだけで十分です。

ストリームの遅延評価のため、先行するサービスが空でないOptionalを返した場合、サービスは呼び出されません。

58
Holger

それはきれいではありませんが、これは動作します:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup)は、Optionalで使用するのに非常に便利なパターンです。これは、「このOptionalに値vが含まれる場合、func(v)を提供し、そうでない場合はsup.get()を提供する」ことを意味します。

この場合、serviceA(args)を呼び出してOptional<Result>を取得します。そのOptionalに値vが含まれている場合、Optional.of(v)を取得しますが、空の場合は、serviceB(args)を取得します。より多くの選択肢でリンスを繰り返します。

このパターンの他の用途は次のとおりです。

  • .map(Stream::of).orElseGet(Stream::empty)
  • .map(Collections::singleton).orElseGet(Collections::emptySet)
30
Misha

おそらくこれはあなたが望んでいることです: 1つのオプションまたは別のものから値を取得する

それ以外の場合は、 Optional.orElseGet をご覧ください。ここに、私があなたが望んでいることを考える例を示します

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));
25
aioobe

まだJDK8を使用していると仮定すると、いくつかのオプションがあります。

オプション#1:独自のヘルパーメソッドを作成する

例えば。:

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

あなたができるように:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

オプション#2:ライブラリを使用する

例えば。 google guavaのOptionalは適切なor()操作をサポートします(JDK9と同様)。例:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(各サービスがcom.google.common.base.OptionalではなくJava.util.Optionalを返す場所)。

4
Sheepy

これは、パターンマッチングと、SomeおよびNone実装( JavaslangFunctionalJava )または怠zyな Maybe cyclopsの実装 -react 。このライブラリの作成者です。

cyclops-react を使用すると、JDKタイプで構造的 パターンマッチング を使用することもできます。 Optionalでは、 visitor pattern を使用して、現在のケースと不在のケースを照合できます。これは次のようになります-

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
2
John McClean