web-dev-qa-db-ja.com

Java 8のゲッターはオプションの型を返す必要がありますか?

Java 8で導入されたOptional型は、多くの開発者にとって新しいものです。

古典的なFooの代わりにOptional<Foo>型を返すゲッターメソッドは良い習慣ですか?値はnullにできると仮定します。

243
leonprou

もちろん、人々は自分が望むことをするでしょう。しかし、この機能を追加する際には明確な意図がありました。多くの人がそうすることを望んでいたのと同じくらい、notが汎用タイプかもしれません。私たちの意図は、「結果なし」を表す明確な方法が必要なライブラリメソッドの戻り値の型に制限されたメカニズムを提供することでした。そして、そのようなnullを使用すると、エラーが発生する可能性が非常に高くなります。

たとえば、おそらく結果の配列や結果のリストを返すものには決して使用すべきではありません。代わりに、空の配列またはリストを返します。何かのフィールドやメソッドのパラメーターとして使用することはほとんどありません。

ゲッターの戻り値として日常的に使用することは間違いなく使いすぎになると思います。

wrongオプションを使用して回避する必要はありません。多くの人が望んでいたことではないため、熱心な使い過ぎのリスクをかなり心配していました。

(公共サービスのアナウンス:NEVER nullにならないことを証明できない場合はOptional.getを呼び出し、代わりにorElseまたはifPresentなどの安全なメソッドのいずれかを使用します。 getのようなもの、またはgetOrElseThrowNoSuchElementExceptionを呼び出す必要がありました。または、これが最初にOptionalの目的全体を損なう非常に危険な方法であることを明確にしたものです。更新:Java 10にはOptional.orElseThrow()があります。これは、get()とセマンティック上は同等ですが、名前の方が適切です。)

424
Brian Goetz

私自身の研究を少し行った後、私はこれが適切な場合を示唆するかもしれない多くの事に出くわしました。最も信頼できるのは、Oracleの記事からの次の引用です。

"Optionalクラスの意図はすべてのnull参照を置き換えないであることに注意することが重要です。代わりに、その目的は設計を支援することです詳細-包括的なAPIので、メソッドのシグネチャを読み取るだけで、オプションの値を期待できるかどうかを判断できます。これにより、値の欠如を処理するためにオプションを積極的にアンラップする必要があります。」 - ヌルポインター例外にうんざりしていませんか?Java SE 8のオプションの使用を検討してください!

Java 8オプション:使用方法 からの抜粋も見つかりました

」オプションは、これらのコンテキストで使用することを意図していません。何も購入しないためです:

  • ドメインモデル層(シリアル化不可)
  • DTOで(同じ理由)
  • メソッドの入力パラメータで
  • コンストラクタパラメータ」

また、いくつかの有効なポイントを上げるようです。

Optionalを避けることを示唆する否定的な意味合いや赤旗を見つけることができませんでした。一般的な考えは、APIの有用性や使いやすさが向上する場合は、それを使用することだと思います。

67
Justin

一般に、null許容の戻り値にオプションの型を使用することをお勧めします。ただし、w.r.t。フレームワークへ古典的なゲッターをオプションの型に置き換えると、ゲッターとセッターのコーディング規約に依存するフレームワーク(Hibernateなど)で作業するときに多くの問題が発生すると思います.

17
Claas Wilke

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;

私のポイントは、Optionalは関数型プログラミングをサポートするために書かれており、同時にJavaに追加されたということです。 (この例は Brian Goetzのブログ の好意によるものです。このコードはとにかく例外をスローするので、より良い例ではorElse()メソッドを使用しますが、画像は得られます。)

しかし、現在、人々はまったく異なる理由でOptionalを使用しています。彼らはそれを使用して、言語設計の欠陥に対処しています。欠点は次のとおりです。APIのどのパラメーターと戻り値がnullを許可されるかを指定する方法はありません。 javadocsで言及されている場合もありますが、ほとんどの開発者は自分のコード用にjavadocsを作成することすらしていないため、作成中にjavadocsをチェックする開発者は多くありません。そのため、コールスタックで既に9〜10回繰り返し検証されているため、値がnullになることはほとんどありませんが、使用する前に常にnull値をチェックする多くのコードにつながります。

新しいOptionalクラスを見た多くの人々がその目的がAPIに明快さを加えることだと思ったので、この欠陥を解決するための本当の渇望があったと思います。 「ゲッターはオプショナルを返すべきですか?」いいえ、ゲッターが関数型プログラミングで使用されることを期待しない限り、おそらくそうすべきではありません。実際、Java APIでOptionalが使用されている場所を見ると、主に関数型プログラミングの中核であるStreamクラスにあります。 (私はあまり徹底的にチェックしていませんが、Streamクラスはonly使用される場所かもしれません。)

少し機能的なコードでゲッターを使用する予定がある場合は、標準のゲッターと、オプションのゲッターを使用することをお勧めします。

ああ、クラスをシリアル化する必要がある場合は、Optionalを絶対に使用しないでください。

オプションは、a)非常に冗長であり、b)そもそもその問題を解決することを意図していないため、APIの欠陥に対する非常に悪いソリューションです。

APIの欠陥に対するはるかに優れたソリューションは、 Nullness Checker です。これは、@ Nullableで注釈を付けることで、nullを許可するパラメーターと戻り値を指定できる注釈プロセッサーです。このようにして、コンパイラはコードをスキャンし、実際にnullにできる値がnullが許可されていない値に渡されているかどうかを判断できます。デフォルトでは、注釈を付けない限り、nullを許可しないものと想定します。この方法では、null値について心配する必要はありません。パラメータにヌル値を渡すと、コンパイラエラーが発生します。 nullにはできないnullのオブジェクトをテストすると、コンパイラの警告が生成されます。これの効果は、NullPointerExceptionを実行時エラーからコンパイル時エラーに変更することです。

これはすべてを変えます。

ゲッターについては、Optionalを使用しないでください。また、どのメンバーもnullにならないようにクラスを設計してください。そして、Nullness Checkerをプロジェクトに追加し、必要に応じてゲッターとセッターパラメーター@Nullableを宣言してみてください。これは新しいプロジェクトでのみ行いました。 nullに対する余分なテストを多数記述した既存のプロジェクトでは、おそらく多くの警告が生成されるため、改造するのは難しいかもしれません。しかし、多くのバグもキャッチします。大好きです。そのため、私のコードはずっとクリーンで信頼性が高いです。

(これに対処する新しい言語もあります。JavaバイトコードにコンパイルするKotlinを使用すると、オブジェクトを宣言するときにnullになる可能性があるかどうかを指定できます。これはよりクリーンなアプローチです。)

元の投稿への補遺(バージョン2)

多くのことを考えた後、私は渋々、1つの条件でOptionalを返すことは受け入れられるという結論に達しました。取得された値は実際にはnullかもしれないということです。 nullを返すことができないゲッターからOptionalを定期的に返すコードをたくさん見ました。これは、コードに複雑さを追加するだけの非常に悪いコーディング手法であり、バグが発生する可能性が高いと考えています。しかし、返された値が実際にnullである可能性がある場合は、先に進んでそれをOptionalにラップします。

関数型プログラミングのために設計され、関数参照を必要とするメソッドは、2つの形式で記述され(またそうされるべきです)、そのうちの1つはOptionalを使用することに注意してください。たとえば、Optional.map()Optional.flatMap()は両方とも関数参照を取ります。最初は通常のゲッターへの参照を取り、2番目はOptionalを返すものを取ります。そのため、値をnullにできないOptionalを返すことで、だれにも恩恵を与えません。

とはいえ、NullPointerExceptionsをランタイムバグからコンパイル時エラーに変換するため、 Nullness Checker が使用するアプローチがnullを処理する最良の方法であることがわかります。

9
MiguelMunoz

IfあなたがOptionalを理解する最新のシリアライザーと他のフレームワークを使用している場合、私はこれらのguidelinesEntity Beanおよびドメインレイヤーを記述する場合は、うまく機能します。

  1. シリアル化レイヤー(通常はDB)が、テーブルnullの列BARのセルにFOO値を許可する場合、ゲッターFoo.getBar()は、この値がnullであることが合理的に予想されることを開発者に示すOptionalを返し、これを処理する必要があります。 DBが値がnullでないことを保証する場合、ゲッターはnotこれをOptionalでラップする必要があります。
  2. Foo.barprivateで、notOptionalである必要があります。 Optionalである場合、privateである理由は実際にはありません。
  3. セッターFoo.setBar(String bar)は、barおよびnotOptionalのタイプを取る必要があります。 null引数を使用してもかまわない場合は、JavaDocコメントでこれを明記してください。 nullまたはIllegalArgumentExceptionまたは何らかの適切なビジネスロジックを使用しても問題ない場合は、IMHOがより適切です。
  4. コンストラクターはOptional引数を必要としません(ポイント3と同様の理由で)。一般に、シリアライズデータベースではmust null以外の引数のみをコンストラクタに含めます。

上記をより効率的にするには、IDEテンプレートを編集して、toString()equals(Obj o)などのゲッターと対応するテンプレートを生成するか、それらのフィールドを直接使用します(ほとんどのIDEジェネレーターはすでにnullを処理します) 。

3
Mark