見出し画像

新しいJavaのOptionalを作ってみる

Java標準のOptionalクラスにいくつか不満点があったので、新しいOptionalクラスを考えてみました。

前回の記事でJavaのOptionalクラスについて考察しました。その中でいくつか不満点も挙げました。

具体的な不満点は前回の記事を読んでいただくとして、Java標準のOptionalクラスは僕が求めるものとは少しマッチしないものでした。

求めるOptionalクラス

僕が求めるOptionalは以下の2点です。

  1. Nullableな変数であることを型で表現できること

  2. nullチェックを強制できること

1.に関してはJava標準のOptionalでもクリアしていましたが、2.に関しては課題が残るものでした。
そこで今回は僕が求める新しいOptionalクラスを考案してみました。

Optionalクラスの新案

ソースコード

まずはソースコードから。

import java.util.Objects;

/**
 * nullかもしれない値を表現するクラス
 */
public final class Optional<T{
    /**
     * 値がnullでした
     */
    public static final class NotPresentException extends Exception {
    }

    public interface Initializer<T{
        init() throws Exception;
    }

    private final T value;

    /**
     * 内包する値がnullであるインスタンスを生成します
     */
    public static <T> Optional<T> empty() {
        return new Optional<>(null);
    }

    /**
     * インスタンスを生成します
     */
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    /**
     * イニシャライザを使用してインスタンスを生成します。
     * イニシャライザが例外を発生した場合、内包する値がnullであるインスタンスを生成します
     */
    public static <T> Optional<T> of(Initializer<T> initializer) {
        try {
            T value = initializer.init();
            return new Optional<>(value);
        } catch (Exception e) {
            return Optional.empty();
        }
    }

    private Optional(T value) {
        this.value = value;
    }

    /**
     * 内包する値がnullでないならばtrue、それ以外はfalseを返します
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * 値がnullでないならば、その値を返します。nullならば例外が発生します
     *
     * @throws NotPresentException 値がnullのとき発生します
     */
    public T unwrap() throws NotPresentException {
        if (value == null) {
            throw new NotPresentException();
        } else {
            return value;
        }
    }

    /**
     * 強制的に値を非nullとみなして返します。
     * もしnullであった場合、NullPointerExceptionが発生します。
     * このメソッドは、値が確実にnullでないことが判っているときのみ使うことを推奨します
     *
     * @throws NullPointerException 値がnullのとき発生します
     */
    public T forced() {
        return Objects.requireNonNull(value);
    }

    /**
     * 値がnullでないならば、その値を返します。nullならば指定した代替値を返します。
     *
     * @param other 代替値
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    public String toString() {
        if (value == null) {
            return "Optional.empty";
        } else {
            return "Optional(" + value + ")";
        }
    }
}

基本的な使い方

基本的な使い方は以下の通りです。

String str1 = "Hoge";
Optional<String> opt1 = Optional.of(str1);

try {
    System.out.println(opt1.unwrap()); // "Hoge"が出力されるcatch (Optional.NotPresentException e) {
    System.out.println(opt1.toString());
}

String str2 = null;
Optional<String> opt2 = Optional.of(str2);

try {
    System.out.println(opt2.unwrap());
} catch (Optional.NotPresentException e) {
    System.out.println(opt2.toString()); // "Optional.empty"が出力される
}

Optional.ofメソッドでインスタンスを作成し、unwrapメソッドで内包する値を取り出します。
Java標準のOptional#getメソッドは、内包する値がnullのとき、NoSuchElementExceptionが発生しますが、この例外は非検査例外のため、try-catchしなくてもコンパイルエラーになりません。
新案のunwrapメソッドは、内包する値がnullのとき、Optional.NotPresentExceptionを投げます。この例外は検査例外のため、try-catchまたは呼び出し元に例外を投げないといけません。これでnullチェックし忘れ問題を回避できます。

他の値の取り出し方

他の値の取り出し方として、orElseメソッドも用意しています。(Java標準のOptionalクラスにもありますね。)

String str1 = "Hoge";
Optional<String> opt1 = Optional.of(str1);
System.out.println(opt1.orElse("Foo")); // "Hoge"が出力される

String str2 = null;
Optional<String> opt2 = Optional.of(str2);
System.out.println(opt2.orElse("Foo")); // "Foo"が出力される

このメソッドは三項演算子を使った場合と同じです。

String str = "Hoge";
System.out.println(str != null ? str : "Foo"); // "Hoge"が出力される

三項演算子はよく使うのでorElseメソッドを実装しました。

その他の値の取り出し方として、forcedメソッドも用意しています。このメソッドは内包する値が非nullであるとみなし値を返します(強制アンラップ)。もしnullであった場合はNullPointerExceptionが発生します。

String str1 = "Hoge";
Optional<String> opt1 = Optional.of(str1);
System.out.println(opt1.forced()); // "Hoge"が出力される

String str2 = null;
Optional<String> opt2 = Optional.of(str2);
System.out.println(opt2.forced()); // ヌルポ!

Optionalが内包する値が明らかにnullではないと分かっているケースもあり、そういうときにunwrapやorElseは冗長なコードになってしまうので、forcedメソッドを実装しました。nullチェックの抜け道にもなってしまいますが…。

もしくはisPresentメソッドと併せて使います。

if (opt.isPresent()) {
    System.out.println(opt.forced());
}

ただ、これよりはunwrapメソッドを使うことを推奨します。

他のインスタンス生成方法

最後にインスタンスの生成方法としてInitializerインタフェースを使用したやり方も用意しています。

Optional<String> opt = Optional.of(new Optional.Initializer<String>() {
    @Override
    public String init() throws Exception {
        // 例外が発生し得る処理
        // => 例外を投げたらOptionalの中身はnullになる
        return Stringオブジェクト
    }
});

initメソッド内で例外を投げた場合、nullを内包するOptionalインスタンスを生成します。「初期化時に例外が発生したらnullにする」というコードを書く機会は多々あるので、このインスタンス生成方法を実装しました。

さいごに

Java標準のOptionalクラスにいくつか不満点があったので、新しいOptionalクラスを考えてみました。
まずは試験的にプロジェクトの一部に導入してみて、効果検証したいと思います。

この記事が気に入ったらサポートをしてみませんか?