JavaではオプションのorElse


137

私はJava 8で新しいOptional型を使用していて、機能的にサポートされていない一般的な操作のように思われるものに遭遇しました:「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のかなりの省略のように思えます。


13
この問題を参照してください。明確にするために、これはすでにJava 9に含まれる予定です
。– Obicere

そうです!ありがとう、私の検索では見つかりませんでした。
Yona Appletree、2015年

2
@Obicereこれは空のOptionalでの動作に関する問題であり、代替結果に関するものではないため、ここでは該当しません。オプションにはorElseGet()、OPに必要なものがすでにありますが、優れたカスケード構文は生成されません。
Marko Topolnik、2015


回答:


86

これは、の形式でJDK 9の一部であるor取り、Supplier<Optional<T>>。あなたの例は次のようになります:

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

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


いいね。この追加は1年前のものであることに気づかなかった。ブログの質問に関しては、バイトコード呼び出し命令が戻り型を含む完全な署名を参照するため、戻り型を変更するとバイナリ互換性が損なわれるため、戻り型を変更することはできませんifPresent。とにかく、名前ifPresentは良いものではないと思います。名前(のように「他」の軸受ではない他のすべてのメソッドの場合はmapfilterflatMap)、なぜべきで、値が存在しない場合、彼らは何もしないことを暗示しているifPresent...
ホルガー

追加するので、Optional<T> perform(Consumer<T> c)チェーン接続できるようにする方法perform(x).orElseDo(y)orElseDoあなたの提案に代わるものとしてifEmpty持っているでは一貫して、else不在の値のために何かをするかもしれないすべてのメソッドの名前にします)。あなたはそれperformをJavaの9で模倣することができますが、それstream().peek(x).findFirst()はAPIの乱用であり、同時にRunnableを指定せずにを実行する方法はまだありませんConsumer...
Holger

65

現在の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


13
プロジェクトでそれを使用しただけですが、コードレビューは行わないでください。
Ilya Smagin

3
「orElseGet」のチェーンよりもずっとクリーンですが、読みにくくなっています。
slartidan 2015

4
これは確かに正しいですが、正直なところ、解析に1秒かかり、正しいことを確信できません。心に留めておいてください、この例はそうだと思いますが、私はそれが遅延評価されないようにする、または一見区別がつかない他のエラーが発生するような小さな変更を想像できます。私には、これはユーティリティ関数としての適切なカテゴリに分類されます。
Yona Appletree、2015年

3
で切り替える.map(Optional::get).findFirst()、「読み取り」が簡単になるのではないかと思います。たとえば、.filter(Optional::isPresent).findFirst().map(Optional::get)「ストリーム内でOptional :: isPresentがtrueである最初の要素を見つけ、次にOptional :: getを適用してフラット化する」のように「読み取る」ことができますか?
schatten 2016

3
面白いことに、私は数か月前に同じような質問に対して非常に似た解決策を投稿しました。私がこれに遭遇したのはこれが初めてです。
shmosel

34

それはきれいではありませんが、これはうまくいきます:

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)

1
ええと、私がこの戦略を使用すると、eclipseは次のように述べています。文字列でオプションの有効な戦略を返すことを検討するには?
chrismarx 2016

@chrismarx () -> {}はを返しませんOptional。あなたは何を成し遂げようとしているのですか?
ミーシャ2016

1
私はただ例に従おうとしています。マップ呼び出しの連鎖は機能していません。私のサービスは文字列を返しますが、もちろん.map()オプションは利用できません
chrismarx

1
次期or(Supplier<Optional<T>>)Java 9の優れた読みやすい代替案
David M.

1
@Sheepyあなたは間違っています。 .map()空のOptional場合、空が生成されますOptional
ミーシャ

27

おそらくこれはあなたが求めているものです: あるオプションから別のオプションから値を取得します

それ以外の場合は、をご覧くださいOptional.orElseGet。ここで私は何の例だと思うあなたは後にしていることは:

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

2
これは天才が通常行うことです。オプションをnull可能として評価し、それをラップするofNullableことは私が今まで見た中で最もクールなことです。
ジンクォン

5

まだ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 or()guava のOptionalは適切な操作(JDK9と同様)をサポートします。例:

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

(ここでは、各サービスがcom.google.common.base.Optionalではなくを返しますjava.util.Optional)。


Optional<T>.or(Supplier<Optional<T>>)グアバのドキュメントでは見つかりませんでした。そのためのリンクはありますか?
Tamas Hegedus

.orElseGetはTを返しますが、Optional :: emptyはOptional <T>を返します。Optional.ofNullable(........ orElse(null))は、@ aioobeで説明されているように望ましい効果をもたらします。
ミゲルペレイラ

@TamasHegedusオプション1のことですか?それは、その上の省略されているカスタム実装です。ps:返信が遅くなってすみません
Sheepy

この場合@MiguelPereira .orElseGetはOptional <T>で動作しているため、Optional <T>を返します
Sheepy

2

これは、パターンマッチングに適しており、Some and None実装(JavaslangFunctionalJavaの実装など)を使用したより伝統的なOptionインターフェース、またはcyclops-reactの遅延多分実装のようです。私はこのライブラリの作成者です。

サイクロプス反応するあなたも、構造を使用することができ、パターンマッチングを JDKタイプに。オプションの場合、ビジターパターンを介して現在のケースと不在のケースを照合できます。それはこのようなものになります-

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

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.