Guide to Java 8オプション
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。