😽

Optionalクラスまとめ[Java入門]

に公開

はじめに

こんにちは。
プログラミング初心者wakinozaと申します。
Java勉強中に調べたことを記事にまとめています。

十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。

記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。

対象読者

  • Javaを勉強中の方
  • Java SE11 Gold試験を勉強中の方
  • JavaのOptionalクラスについて知りたい方

目次

1. nullチェックとnull安全性
2. Optionalクラスとは
3. Optionalの基本の使い方
4. メソッド一覧

本文

1. nullチェックとnull安全性

参照型変数がnull値の場合、その変数にアクセスしようとすると、実行時エラーであるNullPointerExceptionが発生します。こういった例外を防ぐため、nullが格納されている可能性がある変数を利用する前に、nullが格納されていないかをチェックする必要があります。 この確認を、「nullチェック」といいます。

nullチェックが必要かどうかはケースバイケースですが、nullが許容されない変数にはnullチェックは必要です。しかし、nullチェックをするかどうかは開発者の意思に委ねられるため、nullチェックの記述漏れという事態も起こりえます。
また、nullチェックを記述するとコードが冗長となり、可読性が低下します。

このようなnullチェックにまつわる問題を解決するために、実行時にNullPointerExceptionが発生しない仕組み「null安全」について考えられるようになりました。
Java言語においては、Optionalクラスを用いることで、「null安全」なコードを実現しやすくなりました。

この記事では、「null安全」を実現するOptionalクラスについて説明していきます。

2. Optionalクラスとは

Optionalクラスは、 値が存在する場合と、存在しない場合(null)を区別なく統一的に扱うためのラッパークラスです。 主にメソッドの戻り値を扱うのによく用いられています。

メソッドの戻り値をOptionalにすることで、 呼び出し元のメソッドに「戻り値が空の可能性がある」と明示することができます。それによって、呼び出し元のメソッドに、値が空である可能性を考慮した記述を促すことができ、null安全が担保されるのです。
また、例外処理の記述を省略できるため、コードが簡潔になり、可読性が増します。

3. Optionalの基本の使い方

Optionalクラスは、newによるインスタンス生成が禁止されているため、staticメソッドを用いて利用します。

Optionalクラスの基本的な使い方を、コード例でみていきましょう。

//戻り値としてOptionalクラスを返すメソッド
public static Optional<String> repeatThree(String s){ //<>で正常値の型を明示
  String r;
  if (s == null || s.length() == 0){   //引数の空の場合は
    return Optional.empty();            //empty()で空のインスタンスを生成
  } else {
    r = s + s + s;
    return Optional.of(r);  //値が正常値の場合は、of()で正常値を持ったインスタンスを生成
  }
}

上のコードは、引数を3回繰り返した文字列を戻り値として返すrepeatThree()メソッドです。 Optionalクラスが戻り値に指定され、ダイアモンド演算子<>の中に、正常の戻り値の型を指定しています。

if文で、引数が空かどうかを確認しています。
このメソッドでは、引数が空の場合は、戻り値も空となります。そのため、引数が空の場合は、Optionalクラスのメソッドempty()メソッドと使って、空のOptionalインスタンスを生成し、その参照を呼び出し元に返します。

引数が正常の文字列である場合は、戻り値も正常値を返します。Optionalクラスのof()メソッドを使って、戻り値を持ったOptionalクラスのインスタンスを生成し、その参照を呼び出し元に返します。

次に、上のメソッドを利用する呼び出し元のコードをみていきましょう。

public static void call(){
  Optional<String> result = repeatThree(""); 
  if (result.isEmpty()){        //値が空かどうかを確認
    System.out.println("empty");  //trueの場合は、値が空である
  } else {                        //falseの場合は、値が格納されている
    System.out.println(result.get()); //値が格納されている場合は、get()で値を取り出す
  }
}

変数resultに、repeatThree()メソッドの戻り値を格納しています。

repeatThree()メソッドの戻り値は、空の値が格納される可能性のあるOptionalクラスであるため、呼び出し元のメソッド内で、値が空であるかを確認する必要があります。
そのため、OptionalクラスのisEmpty()メソッドを使って、値が空かどうかを確認しています。trueは値が空であることを、falseは値が格納されていることを表します。
値が格納されている場合は、Optionalクラスのget()メソッドで戻り値を取り出します。

以上が、Optionalクラスの基本的な使い方です。

ちなみに、Optionalクラスは、参照型を取り扱うクラスです。
プリミティブ型を扱いたい場合も、内部的にラッパー型へ変換(ボクシング)されるため利用は可能ですが、パフォーマンス上のオーバーヘッドが発生します。これを避けるために、プリミティブ型専用の Optionalクラスなどが用意されています

格納するプリミティブ型 対応するOptionalクラス
int OptionalInt
long OptionalLong
double OptionalDouble

プリミティブ型ではnullは扱わないため、プリミティブ型対応のOptionalクラスでは、値が存在するかどうかをチェックします。
その他の利用方法は、Optionalクラスと同様です。

4. メソッド一覧

empty()

public static <T> Optional<T> empty()

・空のOptionalインスタンスを生成し、その参照を返す。

public static Optional<String> method(String s){
  if (s == null || s.length() == 0){
    return Optional.empty(); 
  } 
  //anycode
}  

ofNullable()

public static <T> Optional<T> ofNullable(T value)

・値がnullの時は空のOptionalインスタンスを生成し、値がある場合は値を持つOptionalインスタンスを生成し、その参照を返す。
・値が正常値のときも、空の時もどちらでも対応できる。

public static Optional<String> method(String s){
  String r  = s + s;
  return Optional.ofNullable(r);
}  

of()

public static <T> Optional<T> of(T value)

  • nullではない値を持つOptionalインスタンスを生成し、その参照を返します
  • 引数にnullを受け付けることができないため、もしOf()メソッドの引数にnullを渡すと、実行時エラーのNullPointerExceptionがスローされます
  • 値がnullでないと保証されている場合はof()メソッドを、nullの可能性がある場合はofNullable()を利用します

isEmpty()

public boolean isEmpty()

  • Optionalインスタンスが空の場合はtrueを、値が記述されている場合はfalseを返す
  • 戻り値として受け取ったOptionalインスタンスに値が入っているか確認するために用いる

isPresent()

public boolean isPresent()

  • Optionalインスタンスが値を持っている場合はtrueを、空の場合はfalseを返す
  • 戻り値として受け取ったOptionrlインスタンスに値が入っているか確認するために用いる

get()

public T get()

  • Optionalインスタンスから値を取り出す。
  • 値が空の場合は、実行時エラーNoSuchElementExceptionをスローする
  • isEmpty()かisPresent()で値が格納されていることを確認してから、get()で値を取り出す
  • get() を使用する際は isPresent() でのチェックが必須です。そのため、if 文が必要にな理、コードが冗長となります。後述するorElse() や ifPresent() などを使うと、より簡潔にコードが記述できます

orElse()

public T orElse(T other)

  • 引数に代替値を指定することができる。Optionalインスタンスに値が格納されていればその値を、空であれば引数で指定した代替値を返す
  • 値の有無を調べるために、if文を記述する必要がないため、コードが簡潔になる
Optional<String> o1 = Optional.of("abcde");
System.out.println(o1.orElse("empty"));  //結果:abcde

Optional<String> o2 = Optional.empty();
System.out.println(o2.orElse("empty"));  //結果:empty

orElseGet()

public T orElseGet(Supplier<? extends T> supplier)

  • 引数としてSupplier型のラムダ式を指定することができる
  • Optionalインスタンスに値が格納されていればその値を、空であれば引数で指定したラムダ式を実行してその結果を返す
  • 固定値を戻したい場合はOrElse()メソッドを利用し、何らかの処理を実行してからその結果を戻したい場合はorElseGet()を利用する。 値が空の場合の処理によって使い分ける
final String KEY = "key";
Optional<String> o3 = Optional.empty();
System.out.println(o3.orElseGet(
    () -> KEY + KEY + KEY));  //結果:keykeykey

orElseThrow()

public T orElseThrow()
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X

  • Optionalインスタンスに値が存在する場合は値を返し、インスタンスが空で引数に指定がない場合はNoSuchElementExceptionをスローする
  • 引数に、例外やエラーのスーパークラスであるThrowableを継承した型を戻すSuppllier型のラムダ式を指定することもできる。引数でラムダ式を指定して、インスタンスが空の場合は、指定した検査例外をスローすることができる
  • Optionalインスタンスが空の時に意図的に例外を発生させたい場合に利用する
public static void main (String[] args) throws Exception {
  Optional<String> o4 = Optional.empty();
  System.out.println(o4.orElseThrow(() -> new Exception())); //結果:Exceptionがスローされる
}

ifPresent()

public void ifPresent(Consumer<? super T> action)

  • 引数にConsumer型のラムダ式を指定することができる
  • Optionalインスタンスに値が記述されている場合は値をラムダ式の引数に渡して処理を行う
  • Optionalインスタンスが空の場合は、何もしない
  • Optionalインスタンスの値を取り出さず、そのまま処理したい場合に用いる
  • 引数として渡すConsumerがnullの場合にNullPointerExceptionをスローする
Optional<String> o5 = Optional.of("abcde");
o5.ifPresent((str) -> System.out.println(str)); //結果:abcde

ifPresentOrElse()

public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

  • 第1引数にConsumer型のラムダ式を、第2引数にRunnable型のラムダ式を指定することができる
  • Optionalインスタンスが値を持っている場合は、第1引数のConsumer型のラムダ式に値を渡して処理を行う。Optionalインスタンスが空の場合は、第2引数のRunnable型のラムダ式を実行する
  • 引数として渡すConsumerかRunnableがnullの場合にNullPointerExceptionをスローする
Optional<String> o6 = Optional.of("abcde");
o6.ifPresentOrElse(
    (str) -> System.out.println(str),
    () -> System.out.println("empty"));  //結果:abcde

Optional<String> o7 = Optional.empty();
o7.ifPresentOrElse(
    (str) -> System.out.println(str),
    () -> System.out.println("empty"));  //結果:empty

map()

public <U> Optional<U> map(Function<? super T,? extends U> mapper)

  • 引数にFunction型のラムダ式を指定できる
  • Optionalインスタンスが値も持っている場合は、値をラムダ式の引数に渡し、処理結果を格納した新しいOptionalインスタンスを生成し、その参照を返す。Optionalインスタンスが空の場合は、空のOptionalインスタンスを返す
  • 引数として渡すFunctionがnullの場合にNullPointerExceptionをスローする
Optional<String> o8 = Optional.of("abcde");
Optional<String> result = o8.map(
    str -> str.toUpperCase());  //結果:ABCDE

flatMap()

public <U> Optional<U> flatMap(Function<? super T,? extends Optional<? extends U>> mapper)

  • 引数にFunction型のラムダ式を指定できる
  • Optionalインスタンスが値も持っている場合は、値をラムダ式の引数に渡し、処理結果をそのまま返します。Optionalインスタンスが空の場合は、空のOptionalインスタンスを返します
  • 上のmap()とよく似ていますが、インスタンスに値があった場合の処理結果の返し方に違いがあります。map()は処理結果を格納したOptionalインスタンスを生成し、その参照を返します。一方、flatMap()は、処理結果をそのまま返します
  • flatMap()は、map()ではエラーが起こる処理などに用います。例えば、map()メソッドがOptionalを返すメソッドを呼び出した場合です。 一見問題なさそうに見えますが、Optionalを返すメソッドが戻り値としてOptionalインスタンスを生成し、map()メソッドに渡します。map()メソッドは結果からOptionalインスタンスを生成しその参照を返します。そのため、map()メソッドは、戻り値として受け取ったOptionalインスタンスからさらにOptionalインスタンスを生成してしまいます。その結果、最終的な戻り値は、Optionalインスタンスの中にOptionalインスタンスへの参照が入るという入れ子状態になってしまいます。Optional<String>型の変数に、Optional<Optional<String>>という入れ子構造のインスタンスを代入するため、コンパイラエラーになってしまいます
  • このようにmap()メソッドで入れ子状態が発生してしまうような場合に、flatMap()を利用します。flatMap()は処理をそのまま返すため、入れ子構造にはなりません
//mapを使って入れ子状態になってしまうコード 注意:コンパイラエラーになる
public static void main(String[] args){
  Optional<String> o9 = Optional.Of("abcde");
  Optional<String> result = o9.map(str -> test(str));
}
public static Optional<String> test(String str){ 
  return Optional.OfNullable(str.toUpperCase());
}  
//上のコードをflatmapに変更した コンパイラエラーは起きない
public static void main(String[] args){
  Optional<String> o10 = Optional.Of("abcde");
  Optional<String> result = o10.flatMap(str -> test(str));
}
public static Optional<String> test(String str){ 
  return Optional.OfNullable(str.toUpperCase());
}  

まとめ

  • Optionalクラスは、nullであるか否かに関わらず値を扱えるラッパークラス。これにより、メソッドの戻り値がnullである可能性を呼び出し元に伝え、null安全なコードの記述を促すことができる

記事は以上です。
最後までお読みいただき、ありがとうございました。

参考情報一覧

この記事は以下の情報を参考にして執筆しました。

GitHubで編集を提案

Discussion