Java8 Optional

Java8 Optionalについてのメモです。

Java8で追加されたOptionalについて書いてなかったのでメモです。

Optional

Java8でOptionalが追加されました。
OptionalはあるT型のオブジェクトについてのラッパーで、
T型の値が存在する状態、もしくは何も存在しない状態をラップしています。
Optionalではnullチェックや、nullだった場合の処理などのメソッドがあるため、
正しく使用することで、nullチェックの煩雑さを軽減できたり、nullに対しての安全度が増します。

of

of()で引数で指定したクラスのOptionalを返します。ただし、引数で指定する値はnull以外である必要があります。

@Test
public void of() {
    Zoo zoo = new Zoo("zoo");
    Optional<Zoo> ofZoo = Optional.of(zoo);

    assertThat(ofZoo.get().getClass()).isEqualTo(Zoo.class);
}

of(nullの場合)

of()の引数がnullの場合、NullPointerExceptionが発生します。

@Test(expected = NullPointerException.class)
public void ofButNull() {
    Zoo zoo = null;
    Optional<Zoo> ofZoo = Optional.of(zoo);

    assertThat(ofZoo.get().getClass()).isEqualTo(Zoo.class);
}

ofNullable

ofNullable()では引数で指定したクラスのOptionalを返します。
of()との違いは引数で指定する値はnullでも可です。

@Test
public void ofNullable() {
    Zoo zoo = new Zoo("zoo");
    Optional<Zoo> ofNullableZoo = Optional.ofNullable(zoo);

    assertThat(ofNullableZoo.get().getClass()).isEqualTo(Zoo.class);

    Zoo zooNull = null;
    Optional<Zoo> ofNullableZooNull = Optional.ofNullable(zooNull);

    assertThat(ofNullableZooNull.isPresent()).isFalse();
}

get

get()でOptionalから値を取得します。
値が存在する場合は値を返し、存在しない場合はNoSuchElementExceptionをスローします。

値が存在する場合。

@Test
public void get() {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("bbb"))
            .findAny();
    String get = any.get();
    assertThat(get).isEqualTo("bbb");
}

値が存在しない場合。

@Test(expected = NoSuchElementException.class)
public void getWithException() throws Exception {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("ddd"))
            .findAny();
    String get = any.get();
}

getAsInt

getAsInt()はOptionalIntでのget()の様なもので、
値が存在する場合はintを返し、存在しない場合はNoSuchElementExceptionをスローします。

同様にOptionalLongのgetAsLong()、OptionalDoubleのgetAsDouble()があります。

@Test
public void getAsInt() {
    OptionalInt intAny = IntStream.rangeClosed(1, 10)
            .filter(i -> i == 3)
            .findAny();
    int getInt = intAny.getAsInt();
    assertThat(getInt).isEqualTo(3);
}

ifPresent

ifPresent()でOptionalに値が存在する場合は、引数で指定した関数を実行します。
Consumerインターフェースを引数に取るので、値を返さない関数を記述します。

@Test
public void ifPresent() {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("bbb"))
            .findAny();
    any.ifPresent(System.out::println);
}

isPresent

isPresent()でOptionalに値が存在する場合はtrue、それ以外の場合はfalseを返します。

@Test
public void isPresent() {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("bbb"))
            .findAny();
    assertThat(any.isPresent()).isTrue();
}

orElse

orElse()でOptionalに値が存在する場合はその値を返し、存在しない場合は引数で記述した値を返します。
値が存在しない場合のデフォルト値のような感じです。

@Test
public void orElse() {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("ddd"))
            .findAny();
    String get = any.orElse("fallback");
    assertThat(get).isEqualTo("fallback");

    OptionalInt intAny = IntStream.rangeClosed(1, 10)
            .filter(i -> i == 12)
            .findAny();
    int getInt = intAny.orElse(-1);
    assertThat(getInt).isEqualTo(-1);
}

orElseGet

orElseGet()でOptionalに値が存在する場合はその値を返し、存在しない場合は引数で記述した関数の結果を返します。
Supplierインターフェースを引数に取るので、何らかの値を返す関数を記述します。

@Test
public void orElseGet() {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("ddd"))
            .findAny();
    String get = any.orElseGet(() -> "eee");
    assertThat(get).isEqualTo("eee");

    OptionalInt intAny = IntStream.rangeClosed(1, 10)
            .filter(i -> i == 12)
            .findAny();
    // IntSupplier
    int getInt = intAny.orElseGet(() -> -1);
    assertThat(getInt).isEqualTo(-1);
}

orElseThrow

orElseGet()でOptionalに値が存在する場合はその値を返し、存在しない場合は引数で記述した関数で生成された例外をスローします。
Supplierインターフェースを引数に取るので、スローする例外を生成する処理を記述します。

@Test(expected = IllegalArgumentException.class)
public void orElseThrow() throws Exception {
    Optional<String> any = Stream.of("aaa", "bbb", "ccc")
            .filter(s -> s.endsWith("ddd"))
            .findAny();
    String get = any.orElseThrow(IllegalArgumentException::new);
}

filter

filter()で引数に記述した条件を満たす場合値を返し、それ以外は空のOptionalを返します。
Predicateインターフェースを引数に取るので、booleanを返す関数を記述します。

@Test
public void filter() {
    String text = Stream.of("aa", "ab", "ba", "bb")
            .filter(s -> s.endsWith("b"))
            .findAny()
            .filter(s -> s.startsWith("a"))
            .get();
    assertThat(text).isEqualTo("ab");
}

map

map()でOptionalに値が存在する場合は引数で記述した関数の結果を返し、存在しない場合は空のOptionalを返します。
Functionインターフェースを引数に取るので、何らかの値を返す関数を記述します。

@Test
public void map() {
    String text = Stream.of("aa", "ab", "ba", "bb")
            .filter(s -> s.equals("ab"))
            .findAny()
            .map(String::toUpperCase)
            .get();
    assertThat(text).isEqualTo("AB");
}

flatMap

flatMap()でOptionalに値が存在する場合は、引数で記述したOptionalを返す関数の結果を返し、存在しない場合は空のOptionalを返します。
Optionalの結果が返りますが、flatMap()ではOptional<Optional<String>>のようにラップされないようになります。

@Test
public void flatMap() {
    Optional<String> text = Stream.of("aaa", "bbb", "ccc")
            .max(Comparator.naturalOrder());
    Optional<Integer> num = Stream.of(3, 5, 2, 8)
            .max(Comparator.naturalOrder());

    Optional<String> max = text.flatMap(s ->
            num.flatMap(i -> Optional.of(s + i))
    );

    String result = max.get();
    assertThat(result).isEqualTo("ccc8");
}

テストコードは下記に上げました。

github.com


終わり。