読者です 読者をやめる 読者になる 読者になる

GuavaのOptional

Java

ScalaのOptionみたいなやつJavaにないかなーなんて思ってたら最近のGuavaにあるんですね!
http://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained#Optional

ちょっと動かしてみよう。Optionalクラスってのがそれです。
Guavaのバージョンは、11.0です。OptionalにはまだBetaアノテーションがついてます。

Optionalは値があるかもしれないし、ないかもしれない、という状態を表します。

Optionalを生成する時は、Optionalに対して値を格納して値が存在するOptionalを生成するか、値がないOptionalを生成します。
Optionalを受け取った時は、Optionalに値が存在しているかどうかを確認して、存在している場合は値をOptionalから取り出し利用します。
Optionalを使うことでNullチェックし忘れてNullPointerが発生しちゃう、なんてことを防いだりできます。

とりあえず、Optionalを使ってみる。

@Test
public void Optionalに文字列を格納() {
    Optional<String> option = Optional.of("msg"); // Optional.of()で値が存在するOptionalを生成
    assertThat(option.isPresent(), is(true));     // isPresent()で値があるかどうかをチェック
    assertThat(option.get(), is("msg"));          // get()でOptionalから値を取り出す
}

@Test
public void 値がないことを表すOptionalを生成() {
    Optional<String> option = Optional.<String>absent(); // Optional.absent()で値が存在しないOptionalを生成
    assertThat(option.isPresent(), is(false));
}

@Test(expected=NullPointerException.class)
public void Optinalにnullを格納しようするとNullPointerException() {
    Optional<String> option = Optional.of(null); // Optional.of()にnullを渡してはいけない
}

@Test
public void nullの可能性があるオブジェクトをOptionalに格納() {
    String nullableString = null;
    Optional<String> option =
        Optional.fromNullable(nullableString); // nullかもしれない値からOptionalを生成する場合は、
                                               // Optional.fromNullable()を使う
    assertThat(option.isPresent(), is(false));
}

@Test(expected=IllegalStateException.class)
public void 値がないことを表すOptionalに対してgetを呼ぶとIllegalStateException() {
    Optional<String> option = Optional.<String>absent();
    assertThat(option.isPresent(), is(false));
    String str = option.get();
}

ふむふむ。
ここで、以下の例を見てる。

Person person = repo.findPerson(id);
String name = person.getName();
// personを使う処理

このままだとfindPerson(id)の結果がnullの場合、NullPointerExceptionが発生してしまう。
でもこのことはコンパイラは教えてくれないので、開発者全員が気をつけてnullチェックするようにする。

Person person = repo.findPerson(id);
if (person != null) {
    String name = person.getName();
    // personを使う処理
} else {
    // personが存在しない場合の処理
}

さて、ここでfindPerson(id)の返り値の型をOptionalにしてみる。

Optional<Person> option = repo.findPerson(id);

おっ、findPerson(id)の結果は、値があるかもしれないし、ないかもしれない、ということがOptionalを返すことではっきりと分かるようになった。
Optionalが返ってきたので下のようにチェックしないといけないことに気づける。nullチェックのし忘れがなくなる。

Optional<Person> option = repo.findPerson(id);
if (option.isPresent()) {
    Person person = option.get();
    String name = person.getName();
    // personを使う処理
} else {
    // personが存在しない場合
}

personが存在しない場合の処理が不要なときは以下のように書ける。

Optional<Person> option = repo.findPerson(id);
for (Person person : option.asSet()) {
    Person person = option.get();
    String name = person.getName();
    // personを使う処理
}

値が存在しない場合はデフォルトの値を使いたいとき。

Optional<String> option = person.getNickName();
String nickName = option.or("無名さん");

値が存在しない場合はnullとして扱いとき。

Optional<String> option = person.address();
String address = option.orNull();

そのうちGuavaにOptionalを返すMapとかできるんだろうか。
Optionalを使ってもJava使ってる限りはnullを忘れることはできないだろうし、中途半端になっちゃうかもな。