web-dev-qa-db-ja.com

オプションの用途

6ヶ月以上前からJava 8を使用してきたので、新しいAPIの変更に非常に満足しています。まだ確信が持てない分野の1つは、いつOptionalを使用するかです。私は、どこかでそれを使いたいと思うこととnullがあることとの間で揺れ動くようです。

私がそれを使うことができる状況はたくさんあるように思われます、そしてそれが利点(読みやすさ/ nullの安全性)を追加するか、あるいは単に追加のオーバーヘッドを引き起こすかどうか私にはわかりません。

だから、私はいくつかの例があります、そして私はOptionalが有益であるかどうかについてのコミュニティの考えに興味があります。

1 - メソッドがnullを返すことができる場合のパブリックメソッドの戻り型として。

public Optional<Foo> findFoo(String id);

2 - パラメータがnullの場合のメソッドパラメータとして:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 - Beanのオプションのメンバーとして:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4 - Collections内:

一般的に私は考えていません:

List<Optional<Foo>>

何かを追加します - 特にnullの値などを削除するためにfilter()を使うことができますが、コレクションの中でOptionalを使う方法はありますか?

見逃したケースはありますか?

224
Will

Optionalの主なポイントは、戻り値が存在しないことを示す値を返す関数の手段を提供することです。 この説明 を参照してください。これにより、呼び出し元は流なメソッド呼び出しのチェーンを継続できます。

これは、OPの質問のユースケース#1に最も近いものです。ただし、値の欠如は、nullよりも正確な定式化です。なぜなら、IntStream.findFirstのようなものは決してnullを返すことができないからです。

ユースケース#2の場合、オプションの引数をメソッドに渡すと、これを機能させることができますが、かなり不器用です。文字列の後にオプションの2番目の文字列が続くメソッドがあるとします。 Optionalを2番目の引数として受け入れると、次のようなコードになります。

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

Nullを受け入れた方がいいです:

foo("bar", "baz");
foo("bar", null);

おそらく最良の方法は、単一の文字列引数を受け入れ、2番目のデフォルトを提供するオーバーロードメソッドを用意することです。

foo("bar", "baz");
foo("bar");

これには制限がありますが、上記のいずれよりもはるかに優れています。

ユースケース#3および#4、クラスにOptionalを含むフィールドまたはデータ構造内で、APIの誤用と見なされます。最初に、上部で述べたように、Optionalの主な設計目標に反します。第二に、値を追加しません。

Optionalに値がないことを処理する方法には、代替値を提供する方法、代替値を提供する関数を呼び出す方法、または例外をスローする方法の3つがあります。フィールドに保存する場合は、初期化または割り当て時にこれを行います。 OPが述べたように、リストに値を追加する場合、値を単に追加しないという追加の選択肢があり、それにより、存在しない値を「フラット化」します。

Optionalを実際にフィールドまたはコレクションに格納したいという人が思いつくケースを誰かが思い付くと思いますが、一般的にはこれを避けるのが最善です。

182
Stuart Marks

私はゲームに遅れています、しかしそれが価値があるもののために、私は私の2セントを加えたいです。彼らは Optionalの設計目標 に反していますが、これは Stuart Marksの答え でよくまとめられていますが、その妥当性を私は確信しています(明らかに)。

どこでもオプションを使用

一般に

私は全体を書きました Optionalの使い方についてのブログ記事 しかし、それは基本的にこれに帰着します:

  • 可能な限り選択性を回避するようにクラスを設計する
  • それ以外の場合はすべて、デフォルトでOptionalではなくnullを使用するようにします。
  • おそらく次のものについては例外を設けてください。
    • ローカル変数
    • プライベートメソッドに値と引数を返す
    • パフォーマンスが重要なコードブロック(推測なし、プロファイラーを使用)

最初の2つの例外は、Optionalで参照をラップおよびラップ解除することで認識されるオーバーヘッドを減らすことができます。それらは、nullが合法的にあるインスタンスから別のインスタンスへ境界を通過できないように選択されます。

これはコレクションでOptionalsを許可することはほとんどなく、nullsとほぼ同じくらい悪いことに注意してください。しないでください。 ;)

あなたの質問について

  1. はい。
  2. 過負荷がオプションではない場合、はい。
  3. 他の方法(サブクラス化、装飾など)が選択肢にならない場合は、はい。
  4. いいえ、どうぞ!

利点

これを行うと、コードベースにnullが含まれることが少なくなりますが、根絶はされません。しかしそれは重要なことでさえありません。他にも重要な利点があります。

意図を明確にする

Optionalを使用すると、変数がオプションであることを明確に表します。あなたのコードを読んだり、あなたのAPIを消費したりする人は、何もないかもしれないし、値にアクセスする前にチェックが必要であるという事実で頭を殴られます。

不確実性を取り除く

Optionalがないと、nullオカレンスの意味が明確になりません。状態の正しい表現( Map.get を参照)または初期化の欠落や失敗などの実装エラーが考えられます。

これはOptionalを永続的に使用することで劇的に変化します。ここで、すでにnullが出現しているということは、バグが存在することを意味します。 (値が欠落していることが許されている場合は、Optionalが使用されていたはずです。)これは、このnullの意味の質問にすでに答えられているので、NULLポインター例外のデバッグをはるかに容易にします。

その他のNullチェック

これでnullはできなくなりました。これはどこでも実施できます。注釈、アサーション、プレーンチェックのいずれを使用しても、この引数またはその戻り型がnullになる可能性があるかどうかを考える必要はありません。できません。

デメリット

もちろん、銀の弾丸はありません...

パフォーマンス

値(特にプリミティブ)を余分なインスタンスにラップすると、パフォーマンスが低下する可能性があります。タイトなループでは、これは目立つようになるかさらにはさらに悪くなるかもしれません。

コンパイラは、寿命の短いOptionalsについての追加の参照を回避できる可能性があることに注意してください。 Java 10では、 value types はさらに不利益を減らすか、または取り除くかもしれません。

直列化

Optionalは直列化できません しかし 回避策 はそれほど複雑ではありません。

不変性

Javaではジェネリック型が不変であるため、実際の値型がジェネリック型の引数にプッシュされると、特定の操作が面倒になります。例を示します ここで(「パラメトリック多態性」を参照)

65
Nicolai

個人的には、@NotNull@Nullableのチェックはコンパイル時に実行されるため(一部のランタイムチェックを行うことができます)、 IntelliJのコード検査ツール を使用することをお勧めします。 Optionalの使用ほど厳密ではありませんが、この厳密さの欠如は、適切な単体テストによって裏付けられる必要があります。

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

これはJava 5で動作し、値をラップしたりアンラップしたりする必要はありません。 (またはラッパーオブジェクトを作成する)

24
Peter Lawrey

私はGuava Optionalと彼らのWikiページがそれを非常にうまくいっていると思います。

Nullに名前を付けることによる可読性の向上に加えて、Optionalの最大の利点は、そのばかな証拠です。あなたは、あなたがプログラムをまったくコンパイルしたいのなら、欠けているケースについて積極的に考えるようにあなたに強制します。 Nullを使用すると、単に物事を忘れることが厄介になりやすくなります。FindBugsが役に立ちますが、それでも問題が解決されるとは限りません。

これは、存在しているかどうかにかかわらず値を返す場合に特に重要です。あなた(および他の人)は、other.methodを実装するときにaがnullである可能性があることを忘れている可能性があるよりも、other.method(a、b)がnull値を返す可能性があることを忘れがちです。 Optionalを返すと、呼び出し側がそのケースを忘れることは不可能になります。コードをコンパイルするには、オブジェクトをラップ解除する必要があるからです。 情報源:---(Guava Wiki - nullを使用し、回避する - 要点は何ですか?

Optionalname__はオーバーヘッドを増やしますが、その明らかな利点はexplicitにすることでオブジェクトが存在しない可能性があり、プログラマが状況を処理できるようになることです。誰かが最愛の!= nullチェックを忘れるのを防ぎます。

2 を例にとると、もっと明示的に書くコードだと思います。

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

より

if(soundcard != null){
  System.out.println(soundcard);
}

私にとってはOptionalname__はサウンドカードが存在しないという事実をよりよく捉えています。

私の2¢あなたのポイントについて:

  1. public Optional<Foo> findFoo(String id); - これについてはよくわかりません。たぶん私はemptyであるかもしれないResult<Foo>を返すか、またはFooname__を含むでしょう。これは似たような概念ですが、実際はOptionalname__ではありません。
  2. public Foo doSomething(String id, Optional<Bar> barOptional); - Peter Lawrey's answer - 同様に@Nullableとfindbugsチェックをお勧めします この議論 も参照してください。
  3. あなたの本の例 - 私がOptionalを内部で使用するかどうかはわかりません。本の「API」では、その本に索引がない可能性があることを明示的に示すためにOptional<Index> getIndex()を使用します。
  4. コレクションでは使用しないで、コレクションでnull値を許可しないでください

一般的に、私はnullname__sを回避することを最小限に抑えるようにします。 (一度焼けて…)適切な抽象化を見つけて、特定の戻り値が実際に何を表しているのかを他のプログラマに知らせることは価値があると思います。

22
Behe

Oracleチュートリアルから

Optionalの目的は、コードベース内のすべてのnull参照を置き換えるのではなく、メソッドのシグニチャを読むだけで、ユーザがオプションの値を期待できるかどうかを判断できる、より優れたAPIの設計を支援することです。さらに、Optionalは、値の欠如に対処するためにOptionalの積極的な展開を強制します。その結果、意図しないNULLポインタ例外からコードを保護できます。

12
ctomek

1 - メソッドがnullを返す可能性がある場合のパブリックメソッドの戻り値の型として。

これが、ユースケース#1の有用性を示す 良い記事 です。このコードがあります

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

これに変換されます

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

それぞれのgetterメソッドの戻り値としてOptionalを使用します。

4
artifex
3
gontard

これは興味深い使用法です(私は信じています)...テスト。

私は自分のプロジェクトの1つを徹底的にテストするつもりであり、それゆえアサーションを構築します。確認しなければならないことと、そうでないものだけがあります。

したがって、私は主張するものを構築し、それを検証するために主張を使用します。

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

NodeAssertクラスでは、これを行います。

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

これは、ラベルを確認したい場合にのみアサートが実際にトリガーされることを意味します。

2
fge

OptionalはOptionalの型Tがintlongcharなどのようなプリミティブ型の場合にのみ役に立ちます。 "real"クラスの場合は、null値を使用できるので意味がありません。

私はそれがここから(または他の同様の言語概念から)取られたと思います。

Nullable<T>

C#では、このNullable<T>は値型をラップするためにずっと前に導入されました。

1
peter.petrov

Optionalクラスを使用すると、nullを使用せずに、より良い代替手段を提供できます。

  • これにより、開発者は、存在しないNullPointerExceptionを回避するためにプレゼンスをチェックするようになります。

  • 存在しない可能性がある値をどこで見ればよいかを確認することができるため、APIはより文書化されやすくなります。

Optionalは、オブジェクトをさらに操作するための便利なAPIを提供します。 isPresent() ; get() ; orElse() ; orElseGet() ; orElseThrow() ; map() ; filter() ; flatmap()

さらに、多くのフレームワークはこのデータ型を積極的に使用してAPIから返します。

1
Aleksey Bykov

Optionalがnull値を返す可能性があるメソッドの一般的な代替品であるとは思いません。

基本的な考え方は次のとおりです。値が存在しなくても、将来その値が使用可能になるとは限りません。それはfindById(-1)とfindById(67)の違いです。

呼び出し側にとってのオプションの主な情報は、彼が与えられた値を当てにしないかもしれないということですが、それはいつか利用可能かもしれません。多分それは再び消えて後でもう一度戻ってくるでしょう。それはオン/オフスイッチのようなものです。あなたはライトをオンまたはオフに切り替えるための「オプション」があります。あなたがオンにするためのライトを持っていない場合しかし、あなたは選択肢がありません。

そのため、以前nullが返された可能性がある場所にOptionalsを導入するのは面倒です。私はまだnullを使いますが、それは木の根、遅延初期化、明示的なfind-methodsのような制限された領域でのみです。

1
oopexpert

Optional変更不可能なイテレータデザインパターンのインスタンスと同じ意味を持ちます

  • それはオブジェクトを参照するかもしれないし参照しないかもしれません( isPresent() によって与えられるように)
  • オブジェクトを参照している場合は( get() を使用して)参照解除することができます。
  • しかし、シーケンスの次の位置に進むことはできません(next()メソッドはありません)。

したがって、以前はJavaの使用を検討していた可能性があるコンテキストでOptionalを返すか渡すことを検討してください Iterator

0
Raedwald