1概要

この記事では、Java 8で導入された新しい

Optional

クラスを紹介します。

このクラスの目的は、

null

参照を使用する代わりに、オプションの値を表すための型レベルの解決策を提供することです。

Optionalクラスを気にする必要がある理由をより深く理解するには、オラクルの公式Webサイトhttp://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html[article]をご覧ください。

オプションは__java.utilパッケージの一部なので、このインポートを行う必要があります。

import java.util.Optional;


2オプションのオブジェクトを作成する


Optional

オブジェクトを作成する方法はいくつかあります。空の

Optional

オブジェクトを作成するには

@Test
public void whenCreatesEmptyOptional__thenCorrect() {
    Optional<String> empty = Optional.empty();
    assertFalse(empty.isPresent());
}


isPresent

APIは、

Optional

オブジェクト内に値があるかどうかを確認するために使用されます。 null以外の値で

Optional

を作成した場合に限り、値が存在します。次のセクションで

isPresent

APIを見ていきます。

静的な

of

APIを使ってOptionalオブジェクトを作成することもできます。

@Test
public void givenNonNull__whenCreatesNonNullable__thenCorrect() {
    String name = "baeldung";
    Optional.of(name);
}

値は処理可能です。

@Test
public void givenNonNull__whenCreatesOptional__thenCorrect() {
    String name = "baeldung";
    Optional<String> opt = Optional.of(name);
    assertEquals("Optional[baeldung]", opt.toString());
}

ただし、

of

メソッドに渡される引数を

null

にすることはできません。それ以外の場合、

NullPointerException

が返されます。

@Test(expected = NullPointerException.class)
public void givenNull__whenThrowsErrorOnCreate__thenCorrect() {
    String name = null;
    Optional<String> opt = Optional.of(name);
}

しかし、渡された引数にnull値が必要な場合は、

ofNullable

APIを使用できます。

@Test
public void givenNonNull__whenCreatesNullable__thenCorrect() {
    String name = "baeldung";
    Optional<String> opt = Optional.ofNullable(name);
    assertEquals("Optional[baeldung]", opt.toString());
}

このように、null参照を渡すと、例外をスローせずに、

Optional.empty

APIを使用して作成した場合と同様に、空の

Optional

オブジェクトを返します。

@Test
public void givenNull__whenCreatesNullable__thenCorrect() {
    String name = null;
    Optional<String> opt = Optional.ofNullable(name);
    assertEquals("Optional.empty", opt.toString());
}


3

isPresent()


を使用した値の確認

アクセサメソッドから返された、または作成された

Optional

オブジェクトがある場合は、

isPresent

APIを使用して、その中に値があるかどうかを確認できます。

@Test
public void givenOptional__whenIsPresentWorks__thenCorrect() {
    Optional<String> opt = Optional.of("Baeldung");
    assertTrue(opt.isPresent());

    opt = Optional.ofNullable(null);
    assertFalse(opt.isPresent());
}

このAPIは、ラップされた値がnullでない場合に限りtrueを返します。


4

ifPresent()


による条件付きアクション


ifPresent

APIを使用すると、ラップされた値がnullでないことが判明した場合に、ラップされた値に対してコードを実行できます。

Optional

の前に、次のようにします。

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

このコードは、name変数がnullかどうかを調べてから、コードの実行を進めます。このアプローチは時間がかかり、それが唯一の問題ではありません。このアプローチに内在するのはバグの可能性がたくさんあります。

そのようなアプローチに慣れた後は、コードの一部でnullチェックを実行するのを忘れることが容易にあります。


null

値がそのコードに到達すると、実行時に

NullPointerException

が発生する可能性があります。プログラムが入力問題のために失敗するとき、それはしばしば貧弱なプログラミング習慣の結果です。


Optional

は、良いプログラミング方法を強制する方法として、null許容値を明示的に扱います。ここで、上記のコードをJava 8でリファクタリングできる方法を見てみましょう。

典型的な関数型プログラミングスタイルでは、実際に存在するオブジェクトに対してアクションを実行することができます。

@Test
public void givenOptional__whenIfPresentWorks__thenCorrect() {
    Optional<String> opt = Optional.of("baeldung");

    opt.ifPresent(name -> System.out.println(name.length()));
}

上記の例では、最初の例で機能した5行を置き換えるのに2行のコードしか使用していません。オブジェクトを1つの

Optional

オブジェクトにラップし、次の行でコードを実行するとともに暗黙的な検証を実行します。


5

orElse


のデフォルト値


OElse

APIは、

Optional

インスタンス内にラップされた値を取得するために使用されます。デフォルト値として機能する1つのパラメータを取ります。

orElse

では、存在する場合はラップされた値が返され、ラップされた値が存在しない場合は

orElse

に渡された引数が返されます。

@Test
public void whenOrElseWorks__thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElse("john");
    assertEquals("john", name);
}


6.

orElseGet


のデフォルト値


orElseGet

APIは

orElse

に似ています。ただし、

Optional

値が存在しない場合に値を返すのではなく、呼び出されたサプライヤ機能インターフェイスを呼び出し、呼び出しの値を返します。

@Test
public void whenOrElseGetWorks__thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseGet(() -> "john");
    assertEquals("john", name);
}


7.

orElse



orElseGet


の違い


Optional

やJava 8に不慣れな多くのプログラマーにとって、

orElse



orElseGet

の違いは明らかではありません。実際問題として、これら2つのAPIは、機能的に互いに重複しているという印象を与えます。

しかし、2つの間には微妙ではあるが非常に重要な違いがあり、よく理解されていないとコードのパフォーマンスに劇的な影響を与える可能性があります。

テストクラスに

getMyDefault

という名前のメソッドを作成して、引数をとらずにデフォルト値を返します。

public String getMyDefault() {
    System.out.println("Getting Default Value");
    return "Default Value";
}

2つのテストを作成し、それらの副作用を観察して、これら2つのAPIが重複する場所と異なる場所の両方を確立します。

@Test
public void whenOrElseGetAndOrElseOverlap__thenCorrect() {
    String text;

    System.out.println("Using orElseGet:");
    String defaultText =
      Optional.ofNullable(text).orElseGet(this::getMyDefault);
    assertEquals("Default Value", defaultText);

    System.out.println("Using orElse:");
    defaultText = Optional.ofNullable(text).orElse(getMyDefault());
    assertEquals("Default Value", defaultText);
}

上記の例では、

Optional

オブジェクト内にnullテキストをラップし、2つのアプローチのそれぞれを使用してラップされた値を取得しようとします。副作用は以下の通りです:

Using orElseGet:
Getting default value...
Using orElse:
Getting default value...

いずれの場合も

getMyDefault

APIが呼び出されます。 ** ラップされた値が存在しない場合は、

orElse



orElseGet

APIの両方がまったく同じように機能することがあります。

それでは、値が存在する別のテストを実行しましょう。理想的には、デフォルト値も作成しないでください。

@Test
public void whenOrElseGetAndOrElseDiffer__thenCorrect() {
    String text = "Text present";

    System.out.println("Using orElseGet:");
    String defaultText
      = Optional.ofNullable(text).orElseGet(this::getMyDefault);
    assertEquals("Text present", defaultText);

    System.out.println("Using orElse:");
    defaultText = Optional.ofNullable(text).orElse(getMyDefault());
    assertEquals("Text present", defaultText);
}

上記の例では、null値をラップしなくなり、残りのコードは同じままになります。それでは、このコードを実行した場合の副作用を見てみましょう。

Using orElseGet:
Using orElse:
Getting default value...

ラップされた値を取得するために

orElseGet

を使用する場合、含まれている値が存在するため、

getMyDefault

APIが呼び出されることすらありません。

ただし、

orElse

を使用すると、ラップされた値が存在するかどうかにかかわらず、デフォルトオブジェクトが作成されます。したがって、この場合は、使用されていない冗長オブジェクトを1つ作成しました。

この単純な例では、JVMがその処理方法を知っているので、デフォルトオブジェクトを作成するのに大きなコストはかかりません。ただし、

getMyDefault

などのメソッドでWebサービスを呼び出す必要がある場合、またはデータベースに対してクエリを実行する必要がある場合、コストは非常に明らかになります。


8

orElseThrow


での例外


orElseThrow

APIは、

orElse



orElseGet

inから派生し、不在値を処理するための新しいアプローチを追加します。ラップされた値が存在しないときにデフォルト値を返す代わりに、例外をスローします。

@Test(expected = IllegalArgumentException.class)
public void whenOrElseThrowWorks__thenCorrect() {
    String nullName = null;
    String name = Optional.ofNullable(nullName).orElseThrow(
      IllegalArgumentException::new);
}

Java 8のメソッド参照は、例外コンストラクターを渡すために、ここで役に立ちます。


9

get()


で値を返す

ラップされた値を取得するための最後のアプローチは

get

APIです。

@Test
public void givenOptional__whenGetsValue__thenCorrect() {
    Optional<String> opt = Optional.of("baeldung");
    String name = opt.get();

    assertEquals("baeldung", name);
}

ただし、上記の3つの方法とは異なり、get APIはラップされたオブジェクトがnullでない場合にのみ値を返すことができ、それ以外の場合はno such element例外をスローします。

@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull__whenGetThrowsException__thenCorrect() {
    Optional<String> opt = Optional.ofNullable(null);
    String name = opt.get();
}

これが

get

APIの大きな欠陥です。理想的には、

Optional

は、そのような予期しない例外を回避するのに役立ちます。したがって、このアプローチは

Optional

の目的に反して機能し、おそらく将来のリリースでは非推奨になるでしょう。

したがって、nullケースを準備して明示的に処理できるようにする他のバリアントを使用することをお勧めします。


10

filter()


による条件付き戻り


filter

APIは、ラップされた値に対してインラインテストを実行するために使用されます。それは引数として述語を取り、

Optional

オブジェクトを返します。ラップされた値が述部によるテストに合格した場合は、

Optional

がそのまま返されます。

ただし、述語がfalseを返すと、空の

Optional

が返されます。

@Test
public void whenOptionalFilterWorks__thenCorrect() {
    Integer year = 2016;
    Optional<Integer> yearOptional = Optional.of(year);
    boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent();
    assertTrue(is2016);
    boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent();
    assertFalse(is2017);
}


filter

APIは通常、事前定義された規則に基づいてラップされた値を拒否するためにこの方法で使用されます。あなたはそれを使用して間違ったEメールフォーマットまたは十分に強力ではないパスワードを拒否することができます。

別の意味のある例を見てみましょう。モデムを購入したいのですが、モデムの価格だけが気になります。特定のサイトからモデムの価格に関するプッシュ通知を受け取り、それらをオブジェクトに格納します。

public class Modem {
    private Double price;

    public Modem(Double price) {
        this.price = price;
    }
   //standard getters and setters
}

次に、これらのオブジェクトをモデムの価格が予算の範囲内かどうかを確認することを目的としたコードに渡します。我々はそれに10から15ドルの間で使いたいです。

Optional

なしのコードを見てみましょう。

public boolean priceIsInRange1(Modem modem) {
    boolean isInRange = false;

    if (modem != null && modem.getPrice() != null
      && (modem.getPrice() >= 10
        && modem.getPrice() <= 15)) {

        isInRange = true;
    }
    return isInRange;
}

特に

if

条件でこれを達成するためにどれだけのコードを書かなければならないかに注意を払ってください。アプリケーションにとって重要なif条件の唯一の部分は、最後の価格帯チェックです。残りのチェックは防御的です。

@Test
public void whenFiltersWithoutOptional__thenCorrect() {
    assertTrue(priceIsInRange1(new Modem(10.0)));
    assertFalse(priceIsInRange1(new Modem(9.9)));
    assertFalse(priceIsInRange1(new Modem(null)));
    assertFalse(priceIsInRange1(new Modem(15.5)));
    assertFalse(priceIsInRange1(null));
}

それとは別に、長い一日のコンパイル時エラーを発生させずにnullチェックを忘れることも可能です。

それでは、

Optional filter

APIを使ったバリアントを見てみましょう。

public boolean priceIsInRange2(Modem modem2) {
     return Optional.ofNullable(modem2)
       .map(Modem::getPrice)
       .filter(p -> p >= 10)
       .filter(p -> p <= 15)
       .isPresent();
 }


map

呼び出しは単に値を他の値に変換するために使用されます。

この操作は元の値を変更しないことに注意してください。

この例では、

Model

クラスからpriceオブジェクトを取得しています。次のセクションでmap APIについて詳しく見ていきます。

まず第一に、nullオブジェクトがこのAPIに渡されたとしても、問題はありません。

第二に、私たちがその本体の中に書く唯一のロジックはまさに​​API名が記述するもの、価格範囲チェックです。

Optional

が残りの面倒を見ます:

@Test
public void whenFiltersWithOptional__thenCorrect() {
    assertTrue(priceIsInRange2(new Modem(10.0)));
    assertFalse(priceIsInRange2(new Modem(9.9)));
    assertFalse(priceIsInRange2(new Modem(null)));
    assertFalse(priceIsInRange2(new Modem(15.5)));
    assertFalse(priceIsInRange2(null));
}

以前のAPIは、価格帯をチェックすることを約束していましたが、その固有の脆弱性を防ぐためにそれ以上のことをしなければなりません。したがって、

filter

APIを使用して不要な

if

ステートメントを置き換えて、不要な値を拒否することができます。


11

map()


を使用した値の変換

前のセクションでは、フィルタに基づいて値を拒否または受け入れる方法について説明しました。

map

APIを使用して

Optional

値を変換するために、同様の構文を使用できます。

@Test
public void givenOptional__whenMapWorks__thenCorrect() {
    List<String> companyNames = Arrays.asList(
      "paypal", "oracle", "", "microsoft", "", "apple");
    Optional<List<String>> listOptional = Optional.of(companyNames);

    int size = listOptional
      .map(List::size)
      .orElse(0);
    assertEquals(6, size);
}

この例では、

Optional

オブジェクト内に文字列のリストをラップし、そのマップAPIを使用して含まれているリストに対してアクションを実行します。実行するアクションはリストのサイズを取得することです。


map

APIは、

Optional

で囲まれた計算結果を返します。次に、返された

Optional

に対して適切なAPIを呼び出してその値を取得する必要があります。

フィルタAPIは単に値をチェックしてブール値を返すことに注意してください。一方、map APIは既存の値を取り、この値を使用して計算を実行して、Optionalオブジェクトにラップされた計算結果を返します。

@Test
public void givenOptional__whenMapWorks__thenCorrect2() {
    String name = "baeldung";
    Optional<String> nameOptional = Optional.of(name);

    int len = nameOptional
     .map(String::length())
     .orElse(0);
    assertEquals(8, len);
}

もっと強力なことをするために

map



filter

をチェーンすることができます。

ユーザーが入力したパスワードの正当性をチェックしたいとしましょう。

map

変換を使用してパスワードを消去し、

filter

を使用してパスワードの正当性を確認できます。

@Test
public void givenOptional__whenMapWorksWithFilter__thenCorrect() {
    String password = " password ";
    Optional<String> passOpt = Optional.of(password);
    boolean correctPassword = passOpt.filter(
      pass -> pass.equals("password")).isPresent();
    assertFalse(correctPassword);

    correctPassword = passOpt
      .map(String::trim)
      .filter(pass -> pass.equals("password"))
      .isPresent();
    assertTrue(correctPassword);
}

お分かりのように、最初に入力を消去せずに、それは除外されます – それでも、ユーザーは先頭と末尾のスペースがすべて入力を構成することを当然のことと思うかもしれません。そのため、不正なパスワードを除外する前に、汚れたパスワードを

map

を使用してクリーンなパスワードに変換します。


12.

flatMap()


を使用した値の変換


map

APIと同じように、値を変換するための代替手段として

flatMap

APIもあります。違いは、

map

はラップされていない場合にのみ値を変換するのに対して、

flatMap

はラップされた値を取り、変換前にラップを解除するという点です。

以前は、

Optional

インスタンスをラップするための単純な

String

および

Integer

オブジェクトを作成しました。ただし、多くの場合、これらのオブジェクトは複雑なオブジェクトのアクセサから受け取ります。

違いをより明確に把握するために、名前、年齢、パスワードなどの個人の詳細を示す

Person

オブジェクトを見てみましょう。

public class Person {
    private String name;
    private int age;
    private String password;

    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }

    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }

   //normal constructors and setters
}

通常、このようなオブジェクトを作成し、Stringを使用したときと同じように

Optional

オブジェクトでラップします。代わりに、それは別のAPI呼び出しによって私たちに返されることができます:

Person person = new Person("john", 26);
Optional<Person> personOptional = Optional.of(person);


Person

オブジェクトをラップすると、ネストされた

Optional

インスタンスが含まれるようになります。

@Test
public void givenOptional__whenFlatMapWorks__thenCorrect2() {
    Person person = new Person("john", 26);
    Optional<Person> personOptional = Optional.of(person);

    Optional<Optional<String>> nameOptionalWrapper
      = personOptional.map(Person::getName);
    Optional<String> nameOptional
      = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
    String name1 = nameOptional.orElse("");
    assertEquals("john", name1);

    String name = personOptional
      .flatMap(Person::getName)
      .orElse("");
    assertEquals("john", name);
}

ここでは、

Person

オブジェクトのname属性を取得してアサーションを実行しようとしています。

3番目のステートメントで

map

APIを使用してこれを達成する方法に注意してください。その後、

flatMap

APIを使用して同じことを行う方法に注意してください。

__Person

getName

メソッドの参照は、パスワードをクリーンアップするために前のセクションで行った

String :: trim__呼び出しに似ています。

唯一の違いは、

trim()操作のように

getName()がStringではなく

Optionalを返すことです。これは、

map

変換結果が

Optional

オブジェクトにラップされているという事実と相まって、ネストされた

Optional__につながります。

したがって、

map

APIを使用している間は、変換された値を使用する前に、値を取得するための追加の呼び出しを追加する必要があります。このようにして、

Optional

ラッパーは削除されます。この操作は、

flatMap

を使用するときに暗黙的に実行されます。

13. JDK 9の

Optional

API

Java 9のリリースにより、さらに多くの新しいメソッドが

Optional

APIに追加されました。

  • 代替案を作成するサプライヤを提供するための

    or()

    メソッド


オプション

**

ifPresentOrElse()

メソッド。


Optional

が存在するか、存在しない場合は別のアクション
**

Optional



Stream

に変換するための

stream()

メソッド

これがリンクの完全な記事です:/java-9-optional[続きを読む]。


14. 結論

この記事では、Java 8

Optional

クラスの重要な機能の大部分について説明しました。

また、明示的なnullチェックと入力検証の代わりに

Optional

を使用する理由をいくつか簡単に調べました。

最後に、

orElse



orElseGet

の微妙ではあるが大きな違いに触れました。ここにいくつかの良いリンクがあります:/java-filter-stream-of-optional[さらに読む]トピックに関する。

この記事のすべての例のソースコードは入手可能です。

over on
GitHub。