きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

JavaでNullPointerException回避する方法をまとめる

今日も小ネタ。

NullPointerExceptionを回避するのは簡単だけど、もうちょっとスマートに書けない?

ということを纏める記事。

余談ですが、NullPointerExceptionは「ぬるぽ」とか大文字取って「NPE」とか呼んでます。以後、「NPE」で統一します。

環境

データ構造

次のような親子孫関係を持つ。

リストでのNPE回避も表現したいので、リストも持たせています。

  • Parent
    • Child
      • GrandChild

f:id:nainaistar:20210410182258p:plain

一番シンプル

!= nullで回避するのが一番簡単です。

nullは基本型なのでIDEで色分けされて見やすいので、よく使用しています。

Parent parent = null;

if(parent != null) {
  // 以後の処理
}

ただし、入れ子が複雑になると、都度null回避をしなければならないので面倒です。

if (parent != null ||
  parent.getChild() != null ||
  parent.getChild().getGrandChild() != null) {

  BigDecimal zero = parent.getChild().getGrandChild().getRate();
}

Optional型を使用する

Optional型に設定する方法があります。

Parent parent = null;

Optional.ofNullable(parent)
  .orElse(null); // もしparentがnullならnullを返却する

入れ子構造が複雑になっても、中間でNPEが発生しないのでコードが書きやすいです。

Optional.ofNullable(parent)
    .map(Parent::getChild)
    .map(Child::getGrandChild)
    .map(GrandChild::getRate)
    .orElse(BigDecimal.ZERO)

型変換したい時にもOptional型を使うと、安全にNPE回避できて便利ですね。

BigDecimal a = null;
Optional.ofNullable(a).map(BigDecimal::toPlainString).orElse(null);

Objects型を使用する

Objects型にisNullnonNullメソッドがあります。

Parent parent = null;

if(Objects.nonNull(parent)) {
  // 以後の処理
}

私は単体で扱うことはあまりなく、Stream処理のfilterでNPE回避のためによく使用します。メソッド参照を使用すると、思考コストが減るので便利です。

// 親と子はいるが、孫がいないので、NPE回避を導入しないとエラーになる
ArrayList<Child> baseDetailList = new ArrayList<>();
baseDetailList.add(Child.builder().build());

Parent parent = Parent.builder()
  .children(baseDetailList)
  .build();

assertThat(
  Optional.ofNullable(parent.getChildren())
    .stream().flatMap(Collection::stream)
    .map(Child::getGrandChildren)
    .filter(Objects::nonNull) // NPE回避用ロジック
//  .filter(e -> e != null) 一瞬、何がNullではないものかがわからない。  
//  .filter(e -> e.tax != null) 実はtaxがNull以外かもしれない、という思考になる
    .flatMap(Collection::stream)
    .map(GrandChild::getRate)
    .collect(Collectors.toList())
).isEmpty();

どのNPE回避方法がオススメ?

NPE回避できればどれでもいいです。どれを使っていたらカッコイイ、とかも無いと思います。

次のコードは同じ処理をしていますが、はっきり言うとどれもイケてないな、と感じてしまいます。

BigDecimal a = null;

String hoge = a == null ? null : a.toPlainString();
String fuga = Optional.ofNullable(a).map(BigDecimal::toPlainString).orElse(null);

TypescriptにはOptional Chaining、KotlinにはSafe CallというNPE回避する方法があります。?.と書くだけです。これで書けたら一番カッコイイのですが、Javaにはありません。

String hoge = a?.toPlainString();

個人的には次の手癖でコーディングしています。参考程度に見てください。

  • 入れ子構造と型変換が少ない
    • == null
  • 入れ子構造と型変換が多い
    • Optional型を使う
  • Stream APIで処理する
    • Objects型を使う

ソースコード

https://github.com/hirotoKirimaru/cucumber-sample/blob/332777e969ef1836e0e3cd0653db6ee59bacc610/src/test/java/kirimaru/biz/domain/nest/OptionalTests.java

https://github.com/hirotoKirimaru/cucumber-sample/blob/ce9c23e0bcbd7385753c20e85288cbe658463b33/src/test/java/kirimaru/biz/domain/nest/NPETests.java

終わりに

Optional型は自分で生成するものではない、というイメージからなんとなく避けていましたが、複雑になったケースだと一考の余地ありますね。今回、Optional型で色々検証して安全にデータを取り出せることに気づけて良かったです。

まぁ、気を付けていても結構な頻度でNPEは起こしちゃうんですけどね…。

できれば、@Nullable@NonNullとか表現はしてほしいのですが、自分も付与し忘れることはたくさんあるので人のことは言えません。

NPEの回避って地味に面倒なのであんまりやりたくはないのですが、起こしてしまうと恥ずかしいので今後も地道に頑張りたいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似記事

JavaでNullを意識せずにNullableな配列項目をStream APIで処理する nainaistar.hatenablog.com

f:id:nainaistar:20210410153137p:plain