盆栽エンジニアリング日記

勉強したことをまとめるブログ

Optionalを実装してみる

自分でOptionalを実装してみる

Javaの勉強として、Optionalを自分で実装してみようと思います。

Optionalの公式ドキュメント

Optionalには以下のメソッドが用意されています。

  • empty
  • equals
  • filter
  • flatMap
  • get
  • hashCode
  • ifPresent
  • isPresent
  • map
  • of
  • ofNullable
  • orElse
  • orElseGet
  • orElseThrow
  • toString

今回は、この中でもよく使うメソッドである以下を実装したMaybeクラスを実装します。

  • empty
  • get
  • isPresent
  • of
  • orElse

StringMaybe

この章では、Genericsを用いないString型専用のMaybeクラスを実装します。
クラスの雛形です。

final public class StringMaybe {
    static StringMaybe empty() {
        return null;
    }

    String get() {
        return null;
    }

    boolean isPresent() {
        return false;
    }

    static StringMaybe of(String value) {
        return null;
    }

    String orElse(String other) {
        return null;
    }
}

StringMaybeクラスでは、String型の変数を保持する必要があるため、最初にメンバ変数を用意します。

private final String value;

valueは、直接外部に公開せず一度設定されたら変更されないようにするために、private finalにしておきます。
このままでは、finalな変数なのに初期化されないため、コンストラクタを用意します。

private StringMaybe(String value) {
        this.value = value;
}

ユーザーがStringMaybeオブジェクトを生成するときは、コンストラクタではなく、staticメソッドを利用してもらうようにするために、コンストラクタにprivate修飾子をつけておきます。
次に、StringMaybeオブジェクトを生成するstaticメソッドを実装していきます。

of

static StringMaybe of(String value) {
       return new StringMaybe(Objects.requireNonNull(value));
}

ofメソッドは、引数で渡されたStringオブジェクトを保持するStringMaybeオブジェクトを返すstaticメソッドです。
valueにnullを許さないように、コンストラクタを呼ぶ際にrequirenonNullを経由させます。

empty

private static final StringMaybe EMPTY = new StringMaybe(null);

static StringMaybe empty() {
        return EMPTY;
}

emptyメソッドは、nullを保持するStringMaybeオブジェクトを返すstaticメソッドです。
このオブジェクトを毎回生成する必要はないので、staticメンバ変数として用意しておいて、毎回そのオブジェクトを返すようにします。
最後に、保持しているStringオブジェクトを取得するget, orElseと、有効なオブジェクトを保持しているか確認するisPresentを実装します。

get

public String get() {
        if (value == null) {
            throw new NoSuchElementException("No value presents");
        }
        return value;
}

getメソッドでは、valueがnullの場合は例外をスローするようにします。

orElse

public String orElse(String other) {
        return value == null ? other : value;
}

orElseメソッドは、valueがnullの場合は、引数で渡されたotherを返します。

isPresent

public boolean isPresent() {
        return value != null;
}

valueがnullでない場合にtrueを返します。
これで、String型専用のMaybeクラスが完成しました。
以下が完成したStringMaybeです。

final public class StringMaybe {
    private final String value;

    private static final StringMaybe EMPTY = new StringMaybe(null);

    private StringMaybe(String value) {
        this.value = value;
    }

    public static StringMaybe empty() {
        return EMPTY;
    }

    public String get() {
        if (value == null) {
            throw new NoSuchElementException("No value presents");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }

    public static StringMaybe of(String value) {
        return new StringMaybe(Objects.requireNonNull(value));
    }

    public String orElse(String other) {
        return value == null ? other : value;
    }
}

Maybe

この章では、前の章で作成したStringMaybeをGenericsを利用して、String以外にも適用できるようにします。
最初にStringMaybeをコピペして、Maybeクラスを作ります。

final public class Maybe {
    private final String value;

    private static final Maybe EMPTY = new Maybe(null);

    private Maybe(String value) {
        this.value = value;
    }

    public static Maybe empty() {
        return EMPTY;
    }

    public String get() {
        if (value == null) {
            throw new NoSuchElementException("No value presents");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }

    public static Maybe of(String value) {
        return new Maybe(Objects.requireNonNull(value));
    }

    public String orElse(String other) {
        return value == null ? other : value;
    }
}

次に、Genericsを導入し、Stringを型パラメータで置き換えていきます。

final public class Maybe<T> {
    private final T value;

    private static final Maybe<?> EMPTY = new Maybe<>(null);

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

    public static <T> Maybe<T> empty() {
        @SuppressWarnings("unchecked") Maybe<T> t = (Maybe<T>) EMPTY;
        return t;
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value presents");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }

    public static <T> Maybe<T> of(T value) {
        return new Maybe<T>(Objects.requireNonNull(value));
    }

    public T orElse(T other) {
        return value == null ? other : value;
    }
}

基本的にはクラス定義の際に<T>を追加し、型パラメータTでStringを置き換えるだけですが、staticメソッド内で直接Tを利用することはできないので、その場合はGeneric methodとして定義します。
これでMaybeの実装は完了です。以下のように利用できます。

public class Main {

    public static void main(String[] args) {
        Maybe<String> stringMaybe = Maybe.of("value");
        Maybe<String> emptyStringMaybe = Maybe.empty();
        Maybe<Integer> integerMaybe = Maybe.of(10);

        if (stringMaybe.isPresent()) {
            System.out.println(stringMaybe.get());
        }
        if (!emptyStringMaybe.isPresent()) {
            System.out.println("empty");
            System.out.println(emptyStringMaybe.orElse("orElse"));
        }
        if (integerMaybe.isPresent()) {
            System.out.println(integerMaybe.get());
        }
    }
}