web-dev-qa-db-ja.com

なぜJava 8のOptionalが引数に使われないのか

私は多くのWebサイトで読んだことがあるOptionalはreturn型としてのみ使用されるべきで、メソッドの引数では使用されません。論理的な理由を見つけるのに苦労しています。たとえば、2つのオプションパラメータを持つロジックがあります。したがって、私はこのように私のメソッドシグネチャを書くことが理にかなっていると思います(解決策1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}

Optionalを指定する多くのWebページでは、メソッドの引数として使用しないでください。これを念頭において、次のメソッドシグネチャを使用して、引数がnullである可能性があることを明示するために明確なJavadocコメントを追加することができます。 :

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

別の方法として、私のメソッドを4つのパブリックメソッドに置き換えて、より良いインターフェイスを提供し、p1とp2をより明確にすることができます(解決策3)。

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

それでは、それぞれのアプローチに対してこのロジックを呼び出すクラスのコードを書いてみましょう。最初にOptionalsを返す別のオブジェクトから2つの入力パラメータを取得し、次にcalculateSomethingを呼び出します。したがって、解決策1を使用すると、呼び出しコードは次のようになります。

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

解決策2を使用すると、呼び出し元のコードは次のようになります。

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

解決策3を適用する場合は、上記のコードを使用するか、次のコードを使用します(ただし、これよりかなり多くのコードを使用します)。

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

それで、私の質問は、なぜメソッド引数としてOptionalsを使うのは悪い習慣と考えられているのですか(解決策1を参照)。 それは私にとって最も読みやすい解決策のように見え、将来のメンテナにとってパラメータが空/ nullになる可能性があることを最も明白にしています。 (Optionalの設計者が戻り値の型としてのみ使用されることを意図していたことは知っていますが、このシナリオでは使用しない論理的な理由を見つけることはできません)。

289
Neil Stevens

ああ、それらのコーディングスタイルはちょっとした塩と一緒に使われるべきです。

  1. (+)意味解析を行わずにOptionalの結果を他のメソッドに渡す。それをメソッドに任せても大丈夫です。
  2. ( - )メソッド内で条件付きロジックを発生させるオプションのパラメータを使用することは、文字通り反対になります。
  3. ( - )Optionalに引数をパックする必要があるのは、コンパイラにとっては最適ではなく、不要なラッピングを行います。
  4. ( - )NULL可能パラメーターと比較して、オプションのほうがコストがかかります。

一般に:Optionalは2つの状態を統一します。そのため、データフローが複雑になるため、入力よりも結果に適しています。

148
Joop Eggen

このトピックで私が見た best post は、 Daniel Olszewski によって書かれました。

必須ではないメソッドパラメータに対してOptionalを検討することは魅力的かもしれませんが、そのようなソリューションは他の可能な代替案と比較して薄いです。問題を説明するために、次のコンストラクタ宣言を調べます。

public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}

一見すると、それは正しい設計上の決定として見えるかもしれません。結局、添付ファイルのパラメータをオプションとして明示的にマークしました。しかし、コンストラクタを呼び出すことに関しては、クライアントコードは少し不器用になることがあります。

SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

明確さを提供する代わりに、Optionalクラスのファクトリメソッドは読者をそらすだけです。オプションのパラメータは1つしかありませんが、2つまたは3つ持つことを想像してください。ボブ叔父は間違いなくそのようなコードを誇りに思うだろう????

メソッドがオプションのパラメータを受け入れることができる場合は、実績のあるアプローチを採用し、メソッドオーバーロードを使用してそのようなケースを設計することをお勧めします。 SystemMessageクラスの例では、2つの別々のコンストラクターを宣言することがOptionalの使用より優れています。

public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}

この変更により、クライアントコードがはるかに簡単になり、読みやすくなります。

SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);
130
Gili

Optionalをパラメータとして使用しないことには、ほとんど正当な理由はありません。これに対する議論は権威からの議論(Brian Goetz - 彼の議論は我々が非nullのオプションを強制することはできないということを参照)に頼るか、Optionalの引数がnullであるかもしれない(本質的に同じ議論)。もちろん、Javaでの参照はすべてnullになる可能性があります。プログラマのメモリではなく、コンパイラによってルールが強制されることを推奨する必要があります(これは問題があり、拡張できません)。

関数型プログラミング言語はOptionalパラメータを奨励します。これを使用する最良の方法の1つは、複数のオプション・パラメーターを用意し、そのパラメーターが空ではないと想定してオプションを返す関数を使用するためにliftM2を使用することです( http://www.functionaljava.org/javadoc/4.4/functionaljavaを参照) /fj/data/Option.html#liftM2-fj.F- )。 Java 8は残念ながらoptionalをサポートする非常に限られたライブラリを実装しました。

Javaプログラマーとして、私たちはレガシーライブラリと対話するためにnullを使うべきです。

69
Mark Perry

このアドバイスは、「入力に関してはできるだけ非特異的で、出力に関してはできるだけ具体的にする」という経験則の変形です。

通常、プレーンなnull以外の値をとるメソッドがある場合は、それをOptionalにマッピングすることができます。そのため、プレーンバージョンは厳密に入力に関しては特定できません。 しかし それでもOptional引数が必要になる理由はたくさんあります:

  • あなたの関数をOptionalを返す他のAPIと組み合わせて使用​​したいのです。
  • 与えられた値が空の場合、関数は空のOptional以外の何かを返すべきです。
  • あなたはOptionalがとても素晴らしいと思うのであなたのAPIを使う人は誰でもそれについて学ぶために必要とされるべきです;-)
10
llogiq

Optionalを使ったパターンはreturnnullを避けるためのものです。 nullをメソッドに渡すことはまだ完全に可能です。

これらはまだ正式なものではありませんが、 JSR-308スタイル 注釈を使用して、関数にnull値を受け入れるかどうかを指定できます。実際にそれを識別するためには正しいツールが必要で、実行可能な実行時ポリシーよりも静的チェックの方が多くなるはずですが、それは助けになるでしょう。

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}
8
Makoto

これは私にはちょっとばかげているように思えますが、私が考えることができる唯一の理由はメソッドパラメータのオブジェクト引数がすでにある意味でオプションであるということです - それらはnullになることができます。したがって、既存のオブジェクトを選択してオプションでラップするように強制するのは無意味です。

そうは言っても、オプショナルを受け取る/返すメソッドをチェーニングすることは合理的なことです。たぶんモナド。

6
Steve B.

私の考えでは、OptionalはMonadにすべきであり、これらはJavaでは考えられません。

関数型プログラミングでは、「ビジネスドメインタイプ」に基づいてのみ引数を取り、構成する純粋で高次の関数を扱います。現実の世界に影響を与える関数、または計算結果を報告する関数(いわゆる副作用)を作成するには、外部の世界を表すモナドから値を自動的にアンパックする関数を適用する必要があります。先物、たぶん、どちらか、作家、など...);これはリフティングと呼ばれます。あなたはそれを一種の懸念の分離として考えることができます。

これら2つの抽象化レベルを混在させても読みやすさは向上しませんので、避けたほうがよいでしょう。

3
Eddy

Optionalをパラメータとして渡すときに注意するもう1つの理由は、メソッドが1つのことを実行する必要があるということです。Optionalパラメータを渡す場合は、複数の操作を行うことをお勧めします。

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }
2
Pau

Brian Goetz でうまく説明されているように、オプションはこの目的のためには設計されていません。

メソッド引数がnullになる可能性があることを示すには、常に @Nullable を使用できます。オプションを使用しても、メソッドロジックをもっときれいに書くことができるわけではありません。

1
Kieran

これは、通常、データを操作するための関数を作成してから、Optionalおよび同様の関数を使用してそれをmapに変換するためです。これはデフォルトのOptionalの振る舞いをそれに追加します。もちろん、Optionalに対して動作する独自の補助関数を書く必要がある場合もあります。

1
Danil Gaponov

存在の共鳴は、Optionalがそれ自体nullであるかどうかを最初にチェックし、次にそれがラップする値を評価しようとする必要があるということです。不要な検証が多すぎます。

1
Macchiatow

JDK10のJavaDocをチェックしてください。 https://docs.Oracle.com/javase/10/docs/api/Java/util/Optional.html 、APIに関するメモが追加されています。

API注:オプションは、主に「結果なし」を表す必要がある場合、およびnullを使用するとエラーが発生する可能性がある場合に、メソッドの戻り値の型として使用することを目的としています。

1
Xiaolong

パラメータとしてOptionalを受け入れると、呼び出し側レベルで不要なラッピングが発生します。

例えば、

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

2つのnullでない文字列(つまり、他のメソッドから返された文字列)があるとします。

String p1 = "p1"; 
String p2 = "p2";

空でないことがわかっていても、あなたはそれらをOptionalでラップすることを余儀なくされています。

他の「マッピング可能な」構造で構成しなければならない場合、これはさらに悪化します。 どちらか

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

参照:

メソッドはOptionをパラメータとして期待すべきではありません。これはほとんどの場合、呼び出し元から呼び出し先への制御フローの漏洩を示すコードのにおいです。Optionの内容を確認するのは呼び出し元の責任です

参考文献 https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749

1
gpilotino

まず第3に、方法3を使用している場合は、最後の14行のコードをこれで置き換えることができます。

int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

あなたが書いた4つのバリエーションは 便利さ methodsです。それらがより便利であるときだけあなたはそれらを使うべきです。これも最善の方法です。そのようにして、APIはどのメンバーが必要でどのメンバーが不要であるかを明確にしています。 4つのメソッドを書きたくない場合は、パラメータに名前を付ける方法で物事を明確にすることができます。

public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)

このように、null値が許可されていることは明らかです。

p1.orElse(null)を使用すると、Optionalを使用したときにコードがどの程度冗長になるかがわかります。これは、これを避ける理由の一部です。オプションは関数型プログラミング用に書かれています。ストリームはそれを必要とします。関数型プログラミングでそれらを使用する必要がない限り、あなたのメソッドはおそらくOptionalを返すべきではありません。 Optional.flatMap()メソッドのように、Optionalを返す関数への参照を必要とするメソッドがあります。これがそのシグネチャです。

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

だから、これがOptionalを返すメソッドを書く唯一の正当な理由です。しかしそこでも、それは避けることができます。関数を正しい型に変換する別のメソッドにラップすることで、Optionalを返さないゲッターをflatMap()のようなメソッドに渡すことができます。ラッパーメソッドは次のようになります。

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

だから、あなたはこのようなゲッターを持っているとしましょう:String getName()

このようにflatMapに渡すことはできません。

opt.flatMap(Widget::getName) // Won't work!

しかし、あなたはこのようにそれを渡すことができます:

opt.flatMap(optFun(Widget::getName)) // Works great!

関数型プログラミング以外では、オプションは避けなければなりません。

ブライアン・ゲッツは、彼がこれを言ったとき、それを最もよく言いました:

OptionalがJavaに追加された理由は、次のとおりです。

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

これよりきれいです:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;
0
MiguelMunoz

最初はOptionalsをパラメータとして渡すことも好みましたが、API-Designerの観点からAPI-Userの観点に切り替えると、不利な点があります。

あなたの例では、各パラメータがオプションであるところで、私は計算方法を次のようにそれ自身のクラスに変えることをお勧めします:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();
0
Torsten

もう1つのアプローチは、あなたができることです

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

関数(この場合はサプライヤ)を構築すると、他の変数としてこれを渡すことができ、次のようにして呼び出すことができます。

calculatedValueSupplier.apply();

ここで考えているのは、あなたがオプションの値を持っているかどうかということは、あなたの関数の内部の詳細であり、パラメータには含まれないということです。パラメータとしてオプションを考えるときの機能を考えることは、私が見つけた実際には非常に役に立つテクニックです。

あなたが実際にそれをするべきかどうかあなたの質問に関してあなたの好みに基づいています、しかし他の人が言ったようにそれはあなたのAPIを控えめに言って醜くします。

0
Swaraj Yadav

メソッドの引数としてOptionalを使用する場合も、nullをチェックする必要があります。不要な重複ロジック例えば:-

void doSomething(Optional<ISOCode> isoOpt, Optional<Product> prodOpt){

    if(isoOpt != null){
       isoOpt.orElse(ISOCode.UNKNOWN);
    }

    if(prodOpt != null){
       prodOpt.orElseThrow(NullPointerException::new);
    }

    //more instructions
}

Optionalパラメータの数が増えると想像してみてください。 nullチェックを忘れると、実行時にNPEがスローされる可能性があります。

さらに、あなたのメソッドが公開されている場合、クライアントはそのパラメータをOptionalsにラップすることを余儀なくされます。これは特に引数の数が増えると負担になります。

0
Awan Biru

これは、APIユーザーとAPI開発者に対して異なる要件があるためです。

開発者は正確な仕様と正しい実装を提供する責任があります。したがって、開発者がすでに引数がオプションであることを認識している場合、実装はそれがnullであるかOptionalであるかにかかわらず、それを正しく処理する必要があります。 APIはユーザーにとってできるだけ単純でなければならず、nullが最も単純です。

一方、結果はAPI開発者からユーザーに渡されます。仕様が完全で冗長であるにもかかわらず、ユーザーがそれに気付いていないか、単にそれに対処するのが面倒である可能性がまだあります。この場合、Optionalの結果は、空の結果を処理するためにユーザーに追加のコードを書くように強制します。

0
speedogoo

私はこの質問が難しい事実よりもむしろ意見についてのものであることを知っています。しかし、私は最近.NET開発者からJava開発者に移行したので、最近Optionalパーティに加わったばかりです。 また、コメントとしてこれを述べることを好むが、私のポイントレベルは私がコメントすることを可能にしないので私は代わりにこれを答えとして置くことを余儀なくされている。

私がしてきたことは、経験則としても役立ちました。オプションの値と天気の両方が必要な場合、またはオプションの値がメソッド内に含まれていない場合は、戻り値の型にオプションを使用し、パラメータとしてのみオプションを使用します。

値だけが気になる場合は、メソッドを呼び出す前にisPresentをチェックします。値が存在するかどうかに応じて、メソッド内に何らかのロギングや異なるロジックがある場合は、Optionalに渡します。

0
Blair

そのトピックは古いですが、今は興味があります。

個人的に私はソリューション3を好みますが、メソッドへの引数を提供することは常にnull以外と見なされます(lomboks @NonNullを追加したい)。あなたが本当にあなたの4つのケースのすべてを受け入れたいのであれば、なぜパラメータオブジェクトではないのですか?多分あなたのotherObjectは満足するでしょうか?一般に、それらの(オプションの)値を受け入れるAPIを本当に提供したいのですか、またはオプションのパラメータを回避するために再設計できますか?あなたが本当にオプションのパラメータが必要だと思うなら、私はあなたがSRPを破ると仮定します。

0