Categories: Java

【Java入門】Optionalオブジェクト

オブジェクトの参照がnullの場合、nullチェックをせずに参照しようとすると、NullPointerExceptionが発生する問題がありました。
Java8からはjava.util.Optionalを使うことで、この問題を回避できるようになります。

従来のnullチェック

!=などでnullチェックをしてNullPointerExceptionを回避していました。

Java

String str = null;

//nullではない場合、値を出力する。
if(str != null){
    System.out.println(str);
}

これからのnullチェック

nullでない場合だけ処理を行うように制御する。

Java

String str = null;
Optional.ofNullable(str).ifPresent(s -> System.out.println(s));

Optional型オブジェクトの生成

Optional型のオブジェクトを生成する方法にはOptional.of(XXX)を使う方法とOptional.ofNullable(XXX)を使う方法の2種類がありますが、後者を使用することをおすすめします。理由は、Optional.of(XXX)を使うとXXXがnullだった場合、java.lang.NullPointerExceptionが発生してしまうからです。(後述)

Optional.of(XXX)を使う方法

Java

// Optional<String>型のオブジェクトを生成と出力
Optional<String> str = Optional.of("aiueo");
str.ifPresent(s -> System.out.println(s));  //aiueo

// Optional<Integer>型のオブジェクトを生成と出力
Optional<Integer> num = Optional.of(100);
num.ifPresent(s -> System.out.println(s));  //100

// Optional<Boolean>型のオブジェクトを生成と出力
Optional<Boolean> bool = Optional.of(true);
bool.ifPresent(s -> System.out.println(s)); //true

// Optional<String[]>型のオブジェクトを生成と出力
String[] array = { "apple", "banana", "lemon" };
Optional<String[]> data = Optional.of(array);

// 配列で返すなら以下
data.ifPresent(s -> System.out.println(Arrays.toString(s)));// [apple, banana, lemon]

// 個別で返すなら以下
data.map(Arrays::stream).orElse(Stream.empty()).forEach(System.out::println);   
//apple 
//banana 
//lemon

// 以下の書き方でも可能。
data.ifPresent(s -> Arrays.stream(s).forEach(a -> System.out.println(a)));
//apple 
//banana 
//lemon

しかし、この書き方の場合、以下のように値がnullだった場合、java.lang.NullPointerExceptionが発生してしまいます。

Java

// Optional<String>型のオブジェクトを生成と出力
String tmp = null;
Optional<String> str = Optional.of(tmp);    //ここでNullPointerExceptionが発生。
str.ifPresent(s -> System.out.println(s));

そこでこの問題を回避するためOptional.ofNullable(XXX)を用います。

Optional.ofNullable(XXX)を使う方法

Optional.ofNullable(XXX)に渡される値がnullであっても実行時エラーが発生することはありません。

Java

// Optional<String>型のオブジェクトを生成と出力
String tmp = null;
Optional<String> str = Optional.ofNullable(tmp);
str.ifPresent(s -> System.out.println(s));

ifPresentメソッド

ifPresentメソッドは、Optional型オブジェクトに値が存在する場合(=!null)のみ、処理を実行することができます。
処理はラムダ式で記述します。なお、結果を返却することはできません。
結果を返却したい場合は、後述のmapメソッドを使用します。

Java

public static void main(String args[]) {

    Integer num = 100;
    Optional.ofNullable(num).ifPresent(n -> {
        n = n + 100;
        int result = calc(n);
        System.out.println(result); //40000
    });

}

private static int calc(Integer n) {
    return n * n;
}

ifPresentメソッドの中で別メソッドを呼び出したい場合は以下のように記述します。

Java

public static void main(String args[]) {

    Integer num = 100;
    Optional.ofNullable(num).ifPresent(n -> print(n));

}

private static void print(Integer n) {
    System.out.println(n);  //100
}

mapメソッド

ifPresentメソッドは値を返さないため、値を返したい場合はmapメソッドを使用します。

nullの場合

変数strにはnullが代入されているのでmap(s -> s + "_OK")の部分は実行されず、.orElse("なし");の実行結果が返却されます。

Java

String str = null;
Optional<String> strOpt = Optional.ofNullable(str);
String result = strOpt.map(s -> s + "_OK").orElse("なし");

System.out.println("結果:" + result); //結果:なし

null以外の場合

変数strにはtestが代入されているのでnullではありません。よってmap(s -> s + "_OK")の実行結果が返却されます。

Java

String str = "test";
Optional<String> strOpt = Optional.ofNullable(str);
String result = strOpt.map(s -> s + "_OK").orElse("なし");

System.out.println("結果:" + result);  //結果:test_OK

空文字の場合

変数strが空文字の場合、nullではないので、map(s -> s + "_OK")の実行結果が返却されます。

Java

String str = "";
Optional<String> strOpt = Optional.ofNullable(str);
String result = strOpt.map(s -> s + "_OK").orElse("なし");

System.out.println("結果:" + result);  //結果:_OK

mapメソッドで別メソッドを呼び出す

mapメソッド内で別メソッドを実行するには以下のように記述します。
nullではないときに.map(i -> calc(i))の部分が実行されるため、calcメソッド内はの変数はnullでないことが保証されています。

Java

public static void main(String[] args) {

    // 初期値を設定
    Integer num = 100;

    // Optional型に変換
    Optional<Integer> intOpt = Optional.ofNullable(num);

    // nullの場合は0を返却。null以外の場合はcalc()の実行結果を返却。
    Integer result = intOpt.map(i -> calc(i)).orElse(0);

    // 結果
    System.out.println("結果:" + result);// 結果:10000
}

private static Integer calc(Integer i) {
    return i * i;
}

orElseメソッド

注意

orElseはOptional型オブジェクトがnullの場合に結果を返却しますが、null以外の場合でも処理自体は実行されているので注意が必要です。
null以外の場合にorElseが実行されるとマズイ場合は、後述のorElseGetを使用しましょう。
Optional型オブジェクト 挙動
nullの場合 orElseメソッドの処理を実行して結果を返却する。
null以外の場合 orElseメソッドの処理を実行するだけ。結果は返却しない。

orElseメソッドで別メソッドを呼び出す。

orElseメソッド内で別メソッドを実行するには以下のように記述します。
普通にメソッド名を記述するだけでOKです。

Java

public static void main(String[] args) {

    // 初期値を設定
    Integer num = null;

    // Optional型に変換
    Optional<Integer> intOpt = Optional.ofNullable(num);

    // nullの場合はinit()の実行結果を返却。null以外の場合はcalc()の実行結果を返却。
    Integer result = intOpt.map(i -> calc(i)).orElse(init());

    // 結果
    System.out.println("結果:" + result);// 結果:0
}

private static Integer init() {
    return 0;
}

private static Integer calc(Integer i) {
    return i * i;
}

orElseメソッドはnull以外の場合も実行されている

以下の処理、CNTの値はいくつが出力されるでしょう?答えは2です。
intOptnullではないにもかかわらず、.orElse(init())の部分が実行され、initメソッドの中でCNTがインクリメントされてしまっています。
つまり、OptionalクラスのorElseメソッドはnull以外の場合も実行されているのです。

Java

public class App {

    static Integer I = 1;

    public static void main(String[] args) {

        // 初期値を設定
        Integer num = 100;

        // Optional型に変換
        Optional<Integer> intOpt = Optional.ofNullable(num);

        // nullの場合は0を返却。null以外の場合はcalcの実行結果を返却。
        Integer result = intOpt.map(i -> calc(i)).orElse(init());

        // 結果
        System.out.println("結果:" + result);// 結果:10000

        // CNTはいくつ?
        System.out.println(I);  //2
    }

    private static Integer init() {
        CNT++;
        return CNT;
    }

    private static Integer calc(Integer i) {
        return i * i;
    }
}

orElseGetメソッド

.orElseGetは、Optionalがnullの場合のみ実行されます。null以外の場合は実行されません。

Java

public class App {

    static Integer CNT = 1;

    public static void main(String[] args) {

        // 初期値を設定
        Integer num = 100;

        // Optional型に変換
        Optional<Integer> intOpt = Optional.ofNullable(num);

        // nullの場合は0を返却。null以外の場合はcalcの実行結果を返却。
        Integer result = intOpt.map(i -> calc(i)).orElseGet(() -> init());

        // 結果
        System.out.println("結果:" + result);// 結果:10000

        // CNTはいくつ?
        System.out.println(CNT);  //1
    }

    private static Integer init() {
        CNT++;
        return CNT;
    }

    private static Integer calc(Integer i) {
        return i * i;
    }
}

以上で記事の解説はお終い!

もっとJavaやSpringを勉強したい方にはUdemyがオススメ!同僚に差をつけよう!

issiki_wp