JavaのOptionalを使う

Java 8で追加されたOptionalという機能があります。
nullかもしれないオブジェクトをラップして便利に扱えるようにしたクラスです。

ここの管理人は恥ずかしながらこの機能を最近まで知らなかったので、勉強の意味も兼ねてここに記事として残しておきます。

Optionalを用いたサンプルコードは以下のような感じです。

package com.github.maeda6uiui.useoptional;

import java.util.Optional;
import java.util.Random;

public class Main {
    private static void func1() {
        //Optionalを作成する(non-null)
        String s = "あいうえお";
        Optional<String> op = Optional.of(s);

        //Optionalから値を取得する
        System.out.println(op.get());
    }

    private static void func2() {
        //空のOptionalを作成する
        Optional<String> op = Optional.empty();
        System.out.println(op.isEmpty());
    }

    private static void func3() {
        //nullかもしれない値をもつOptionalを作成する
        var random = new Random();
        String s = random.nextInt() % 2 == 0 ? "あいうえお" : null;
        Optional<String> op = Optional.ofNullable(s);

        //値が存在する場合は出力する
        op.ifPresent(v -> {
            System.out.println(v);
        });
        //以下のように書くことも可能
        op.ifPresent(System.out::println);

        //値が存在する場合と存在しない場合の処理を両方記述する
        op.ifPresentOrElse(
                System.out::println,    //値が存在する場合
                () -> System.out.println("空の値です")   //値が存在しない場合
        );
    }

    public static void main(String[] args) {
        func1();
        func2();
        func3();
    }
}

Optionalを作成するためにはOptional.ofOptional.emptyOptional.ofNullableのいずれかを使用します。

Optional.of値が存在するOptionalを作成します。
引数に渡すオブジェクトは非nullである必要があります。
Optional.empty値をもたない空のOptionalを作成します。
Optional.ofNullablenullかもしれない値をもつOptionalを作成します。

Optionalに値が存在する場合は値を取得して処理する、という流れは、ifPresentifPresentOrElseを用いて実現することができます。
(func3を参照)


たとえば、nullを返す可能性のあるメソッドの戻り値をOptionalにしておけば、他の開発者に値の有無を意識してもらうことができそうです。
また、意図しないNullPointerExceptionを発生させてしまう可能性も減らせるかもしれません。

ただ注意点として、Optionalはメソッドの戻り値に対して使うことが想定されているようで、オブジェクトのフィールドやメソッドの引数に対して使うものではないようです。

いい使い方かはわかりませんが、管理人が現在趣味で作成しているコードだと、以下のような場面でOptionalを使っています。
CSSの文字列を返すメソッドで、指定された値が不明なものだったりファイルの読込みに失敗したりした場合に、空のOptionalを返すようにしています。

/**
 * Returns CSS string.
 * This method attempts to load custom CSS from file if {@code name} is "custom"
 * and a valid string is set to {@code fromFile}.
 * It returns no CSS if {@code name} is "system".
 *
 * @return CSS string
 */
@JsonIgnore
public Optional<String> getCSS() {
    if (name != null && !name.isEmpty()) {
        if (name.equals("system")) {
            logger.info("'system' is specified as a theme. Return no CSS");
            return Optional.empty();
        }
        if (name.equals("custom")) {
            if (fromFile == null || fromFile.isEmpty()) {
                logger.warn(
                        "'custom' is specified as a theme, " +
                                "but fromFile is not a valid string. Return no CSS");
                return Optional.empty();
            }

            return Optional.ofNullable(this.loadCustomCSS());
        }

        return Optional.ofNullable(this.getBuiltInCSS());
    }

    logger.warn("Theme name is not specified. Return no CSS");
    return Optional.empty();
}

呼び出す側は以下のような感じです。
CSS文字列の値が存在する場合のみ、JavaFXアプリケーションのスタイルシートを設定します。

settings.themeSettings.getCSS().ifPresent(Application::setUserAgentStylesheet);