新しいJavaのOptionalを作ってみる
前回の記事でJavaのOptionalクラスについて考察しました。その中でいくつか不満点も挙げました。
具体的な不満点は前回の記事を読んでいただくとして、Java標準のOptionalクラスは僕が求めるものとは少しマッチしないものでした。
求めるOptionalクラス
僕が求めるOptionalは以下の2点です。
Nullableな変数であることを型で表現できること
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> {
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クラスを考えてみました。
まずは試験的にプロジェクトの一部に導入してみて、効果検証したいと思います。