Java8 Optionalの逆引きレシピ

# はじめに

[Java Advent Calendar 2020](https://qiita.com/advent-calendar/2020/java)の20日目の記事です。（[Zenn](https://zenn.dev/nslender/articles/e2292eb9e06d35)とダブルポストです。）

2014年にJava8がリリースされ、そこからOptionalの機能が導入されました。
Javaのリリースサイクルも変わり、2020年12月時点では、最新のLTS版はJava11（2018年リリース）、 Java15(2020年9月リリース)が最新です。

Java8がリリースされてから、11がでるまでの間に始まったプロジェクトでは、まずJava8が使われているでしょう。[AWSではJava8のサポート](https://aws.amazon.com/jp/about-aws/whats-new/2020/08/amazon-corretto-8-11-support-extended/)をかなりのばしているので、まだまだ使う機会がありそうです。

Java8のOptionalは決して使いやすいとはいえないですが、それでもマッチしたケースではそれなり有効です。ケース別にどういった使い方をすればいいかをまとめてみました。

英語ではOptionalを使ってはいけないケースをまとめた記事も結構あります。そのうちのいくつかをピックアップしてみました。

# 原則、Optional#get()は使わない

大前提として、[Optional#get()](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Optional.html#get--) を原則使わないようにしましょう。
Optional#get()は値が存在しないときは例外スローされます。なので、これをいきなり使うのはnull checkしないで参照するのと同じことになります。（NullPointerExceptionのかわりに、NoSuchElementExceptionがスローされます。）

なんのチェックもしないでOptional#get()を使った場合、最近のIDEや[SonarLint](https://www.sonarlint.org/)などの静的解析ツールでは、警告がでてきます。Optionalからの値の取り出し方として良くないとして、すでに知られているといえます。Optionalを使い始めのときにやりがちなので、注意が必要です。

Optionalを使うためには、Optionalで包んだ値の取り出し方（使い方）を知っていることが重要です。関数型のパラダイムを最初からとりいれている言語であれば、取り出し方自体がJava8に比べて豊富で、IDEでだいたい然るべきメソッドにたどり着きやすいと思います。Java8では選択肢が少ないとの、高階関数のAPIがあまり使い勝手が良くないので、取り出し方を先に意識しないと、「Optional使ってみたけど、なんか読みづらいし、書きにくい」、となりがちです。

# Optionalからの取り出し方

各メソッドの詳細は[javadoc](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Optional.html)を参照してください。

以下のメソッドをつかって取り出します。


| メソッド | 使用例 | サンプルコード |
|---|----|---|
|ifPresent|存在するときだけ処理する| userOpt.ifPresent(x -> x.save())|
|orElse|存在しないときは初期値を返す|userOpt.orElse(new User())|
|orElseGet|存在しないときは、関数を使って初期値を返す|userOpt.orElseGet(User::new)|
|orElseThrow|存在しないときは例外スローする|userOpt.orElseThrow(IllegalStateException::new)|


なんらかの変換や絞り込みが必要な場合は、以下のメソッドを先に使ってから、上記にあげたメソッドで値を取り出します。

| メソッド | 使用例 | サンプルコード |
|---|----|---|
|filter|ある条件を満たすものに絞り込む|user.filter(User::isActive)|
|map|プロパティをとりだす、別の型にするなどの変換をする|user.map(User::getName)|


Java8ではあまり使うことはなさそうですが、単純にやると、`Optional<Optional<T>>` のように、Optionalが2重になるような変換がある場合は、`flatMap`を使います。今回はこれはとりあげません。

上記を組み合わせて、必要な値の取り出しや処理します。

## 包んだ値をそのまま使う

[Spring Data JPA](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.core-concepts) では、`findById`など、検索結果が最大1件しかない場合はOptionalで受け取ることができます。こういった場合、entityのままで扱いたいケースが多いです。

### ifPresent

```java

// 更新処理
Optional<User> user = userRepository.findById(id);
user.ifPresent(x -> {
   user.setStatus(2);
   userRepository.save(user);
});
```

### orElse

```java

// ユーザ詳細画面の表示
@GetMapping(/{id})
public ModelAndView showUser(@PathVariable("id") Long id) {
  ModelAndView view = new ModelAndView("userDetail");
  User user = userRepostitory.findById(id).orElse(new User());
  view.set("user", user);
  return view;
}
```

### orElseThrow

```java

// ユーザ詳細情報のAPI
@GetMapping(/{id})
public ResponseEntity<User> findById(@PathVariable("id") Long id) {
  return userRepository.findById(id).orElseThrow(() -> new NotFoundException(id + " does not exist"));
}

```


## 包んだ値が持っているプロパティを使う

entityがもっている特定のプロパティだけが必要なケースがあります。
そういう場合は、Optional#mapを使います。

```java

Optional<User> user = ...;
String userName = user.map(User::getName).orElse("");
Long userId = user.map(User::getId).orElseGet(() -> 1L);
UserStatus userStatus = user.map(User::getStatus).orElseThrow(IllegalStateException::new);
Integer userStatusValue = user.map(User::getStatus).map(UserStatus::getValue).orElse(0);
```

## 包んだ値をもとになんらかの変換して使う

entitをそのままでなく、画面表示用にいろんな加工が必要な場合などは、別のオブジェクトに変換することがあります。変換先のオブジェクトに[static factory method](https://www.baeldung.com/java-constructors-vs-static-factory-methods)を用意しておくと、Optional#mapをうまく使えます。

```java

public class UserViewDTO

   public static UserViewDTO of(User user) {...}
```

```java

Optional<User> user = userRepository.findById(id);
UserViewDTO viewDTO = user.map(UserViewDTO::of).orElse(UserViewDTO::new);
```

# Optionalの作り方

Optionalクラスにstatic factory methodが用意されています。

## ofNullable

nullのときは空のOptionalを、そうでないときはその値のOptionalを返します。
実際のコーディングではこれを使うことが多いです。

Optionalで取り扱いたいが、ライブラリではそうなっていないときなどに、使います。
例えば、Spring Data JPAと違って、[ebean](https://ebean.io/)というORMでは、主キーでの検索結果はOptional型になっていません。同じ様にOptionalで扱いたければ、下記のようにします。

```
Optional<User> user = Optional.ofNullable(User.find.byId(1L));
```

## empty

空のOptionalを返すメソッドです。よくあるのは、例外がおきたときなど、明示的に空のOptionalを返せるときに使います。

```
try {
  // 処理に成功したらOptionalを返す
  return Optional.ofNullable(...);
} catch (Exception e) {
  // 例外が発生したら空のOptionalを返す 
  return Optional.empty(); 
}
```

## of

non-nullな値をOptionalにするときに使います。nullでないことがわかっていて、かつOptionalを使った方がいいという、実用的なケースがあまりなさそうです。返り値がOptionalのメソッドのテストコードなどでは、使いみちがあるかもしれません。

```
return Optional.of(new User());
```

# ケース別使用例

## 存在しないときは初期値を使える

orElseかorElseGetを使います。初期値の生成のコストが大きいときは、orElseGetを使います。

```java

Optional<User> user = ...;
Long userId =  user.map(User::getId).orElse(1L);
```

値がなにも入っていないインスタンスを初期値として渡す場合はorElseGetが使えます。
nullを返すよりも空のインスタンスの方が、なにかと取り扱いがいいです。
upsert処理用のインスタンスを作るときなどに使えます。

```java

User modifiedUser = user.orElseGet(User::new);
```

## 存在しないときは例外スローできる

orElseThrowを使います。存在しないときの処理を、例外をキャッチする別の箇所でやれる場合などに使えます。Rest APIで、id指定のエンドポイントの実装などに使えます。

```java

@GetMapping("/users/{id}")
public ResponseEntity<User> showById(@PathVariable("id") long id) {
   Optional<User> userOpt = userRepository.findById(id);
   User user = user.orElseThrow(() -> new NotFoundException("not found"));
   return ResponseEntity.ok(user);
}
```


## 存在しないときはなにも処理しない

ifPresentを使います。存在するときにだけになにか処理するときに使えます。
更新系の処理で、正しいidが指定されたときだけ実行するというときなどに使えます。

```java

Optional<User> user = userRepository.findById(id);
user.ifPresent(x -> {
  x.setName(form.getName());
  userRepository.save(x);
});
```

## 存在しないときは別の処理をする

Java8では、isPresentとif文を使うことになります。
この場合は、isPresentのチェック後に、Optional#get()で値を取り出します。
存在しない場合はログ出力するときなどです。

```java

Optional<User> userOpt = ...;
if (userOpt.isPresent()) {
  User user =  userOpt.get();
  // 存在したときの処理
} else {
  // 存在しないときの処理
}

```

Java8でこのケースになった場合は、Optionalを使い続けるメリットがあまりないです。
orElseなどで、値を取り出し、そこで値があるかないかを別の形でチェックするのを検討してみましょう。

```java

Optional<User> userOpt = ...;
User user = userOpt.orElseGet(User::new);
if (user.getId() == null) {
  // 存在しないケース
} else {
  // 存在するケース
}
```

Java9からifPresentOrElseが追加されていて、存在しないケースも関数でかけるようになりました。

```java

Optional<User> user = ...;
user.ifPresentOrElse(
   x -> {
     // 存在したケース1
     x.setName(name);
     userRepository.save(x);
   }, 
  () -> {
   // 存在しないケース 
   logger.info("not found")
  }):

```

## 存在したとき、なんらかの条件で絞り込みたい

filterを使います。DBからの取得する段階では絞り込みができないときなどに使います。

```java

Optional<User> user = userRepository.findById(id);
User activeUser = user.filter(x -> x.getStatus().isActive())
                      .orElseThrow(IllegalStateException::new);

```

## Optionalは使わない方がいいケース

### Stringや、intなどのプリミティブ型をOptionalで包みたい

Stringの場合は、nullを極力使わず、値がないことは空文字で表現すればだいたいのことはできます。

```java

String name = ...;
if (StringUtils.isEmpty(name)) {
  ...
} else {
  ...
}

```

int,longなどは、OptionalInt、OptionalLongなどの専用のOptional型は用意されています。
これは、List<Integer>など、コレクション型からStream APIを使った際に使うことが想定されているようです。

non-nullが担保できる場合は、プリミティブ型（int,longなど）を使い、nullableな場合は、ラッパー型（Integer,Longなど）を使い、必要に応じてnull checkすればいいです。


### リストなどのコレクション型をOptionalで包みたい

基本的には不要です。nullを使わず、空のコレクションかどうかで、値の有無を判定しましょう。
例えば、返り値がコレクション型のメソッドを作る場合、nullではなく空のコレクションを返却すればいいです。そうすれば、Optionalでラップする機会はないです。大概のライブラリはそのような使用になっています。


```java

List<Integer>　list = ...;
if (list.isEmpty()) {
  ...
} else {
  ...
}
```

### メソッドの引数にOptionalを使いたい

nullを引数に渡すことは一般的にはよくないことと知られています。これもそれの延長ということです。
Optionalを渡すと、なかで存在するかどうかのチェックをすることになります。
メソッド内でやるよりも、呼び出し側でOptionalをはずした方が、メソッドも使いやすく、読みやすくなるようです。

```java

public void execute(String name, Optional<User> userOpt) {
   // 初期値にできるものがケースによって違うと、orElseでOptionalをはずしづらくなる
   User user = userOpt.orElse(...) 
}

```

```java

public class Something {

  public void execute(String name, User user) {...}

}


Something something = new Something();
Optional<User> user = ...;

user.ifPresent(x -> something.execute("test", x));
something.execute("test", user.orElseGet(User::new));
```

### 成功（正常）か失敗（異常）かどうかを返り値で判定したい

成功のときは値があるOptional、そうでないときは空のOptionalを返す、として、呼び出し側で、成功か失敗に応じて処理を分けたい、というような使い方です。

```java

try {
  ... 
  return Optional.of(...);
} catch (Exception e) {
  return Optional.empty();
}

Optional<Result> result = ...;
if (result.isPresent()) {
 ...
} else {
 ...
}

```

これはisPresentを使うケースにつながるので、単純なケースでない限りはやらない方が良さそうです。
Either型がある言語では、それを使えばいいのですが、ない場合はそれに近いものを用意せざるを得ないです。
結果用のオブジェクトをつくって、その中に成功かどうかのフラグや、返却したいものをつめるといいです。

```java

public class SomethingResult {

  private final boolean isSuccess;

  private final Something result;

  private final Exception error;

  public static SomethingResult success(Something something) {...};

  public static SomethingResult error(Exception exception) {...};

}
```

```java

try {
  ...
  return SomethingResult.success(...);
} catch (Exception e) {
  return SomethingResult.error(e);
}


SomethingResult result = ...;
if (result.isSuccess()) {
  ...
} else {
  ...
}
```



# 参考リンク

https://qiita.com/nowokay/items/2858699bc1cd89222cd8

https://qiita.com/yamadamn/items/2dd26a014791b9557199

https://qiita.com/nowokay/items/1ce24079f4daafc73b4a

https://qiita.com/TakumiKondo/items/6313eae8c0cde2780b89

https://qiita.com/tasogarei/items/f289d125660af6fe5ba6

https://qiita.com/suzuki-hoge/items/4f223022626b5b7d601c

https://qiita.com/BlueRayi/items/ef46496ef2ef36b9cbb7



* https://dzone.com/articles/java-8-optional-usage-and-best-practices
* http://dolszewski.com/java/java-8-optional-use-cases/
* https://www.baeldung.com/java-optional
* https://www.baeldung.com/java-optional-return
* https://community.oracle.com/tech/developers/discussion/4418055/optionals-patterns-and-good-practices
* https://www.dariawan.com/tutorials/java/using-java-optional-correctly/
* https://javadevcentral.com/a-complete-guide-to-java-optional
* https://ducmanhphan.github.io/2019-12-06-Best-practice-for-Optional-in-Java/
* https://tech401.com/2017/12/01/2017/2017-11-30-java-optional-good-and-bad/
* https://www.baeldung.com/java-constructors-vs-static-factory-methods
* http://sumikitch.com/blog/2019/02/17/java-static-factory-method/
* https://www.baeldung.com/java-9-optional
* https://stackoverflow.com/questions/4535112/is-null-arguments-a-bad-practice