2020/04/14

Java 14 の新機能 (6) - JEP 358: Helpful NullPointerExceptions

JEP 358: Helpful NullPointerExceptions

NullPointerException 発生時のエラーメッセージが詳しくなりました。

使い方

今のところ、デフォルトでは無効になっていて、VM 引数を付けて有効にする必要があります。-XX:+ShowCodeDetailsInExceptionMessages を付けてください。

実例

NPE の発生が分かりにくいものとして思い付いたのが、compareTo() 絡みでした。以下の例を見てください。Data というクラスがあって、Comparable にしています。長さ3 の String[] の内容を使って compareTo() を実装しています。

package net.cyberer.sample.npe;

import java.util.Arrays;

public class NPESample {
  public static void main(final String[] args) {
    Data[] data = {
        new Data("a", "b", "c", "d", "e"),
        new Data("a", "n", "c", null, null),
        new Data("b", "x", "i"),
        new Data("b", "t"),
        new Data("c", "z", "c", null, null),
        new Data("c", "s", "c", null, "m"),
        new Data("d", "a", "r", null, null),
        new Data("d", "v", "c", null, "m"),
        new Data("d"),
        new Data("o", "w", "c", null, "u"),
    };
    Arrays.parallelSort(data);
  }

  static final class Data implements Comparable<Data> {
    private String[] key;
    private String[] tail;

    public Data(final String... strs) {
      key = Arrays.copyOf(strs, 3);
      if (strs.length > 3) {
        tail = Arrays.copyOfRange(strs, 3, strs.length);
      }
    }

    public String[] getKey() {
      return key;
    }

    public String[] getTail() {
      return tail;
    }

    @Override
    public int compareTo(final Data o) {
      return key[0].compareTo(o.key[0]) == 0 ? 0 : key[1].compareTo(o.key[1]) == 0 ? 0 : key[1].compareTo(o.key[1]);
    }
  }
}

これを実行すると、NPE が発生します。
Exception in thread "main" java.lang.NullPointerException
 at net.cyberer.sample.npe.NPESample$Data.compareTo(NPESample.java:43)
 at net.cyberer.sample.npe.NPESample$Data.compareTo(NPESample.java:1)
 at java.base/java.util.Arrays$NaturalOrder.compare(Arrays.java:747)
 at java.base/java.util.TimSort.binarySort(TimSort.java:296)
 at java.base/java.util.TimSort.sort(TimSort.java:221)
 at java.base/java.util.Arrays.parallelSort(Arrays.java:799)
 at net.cyberer.sample.npe.NPESample.main(NPESample.java:19)

43行目で NPE が発生していますが、43行目を見てもオブジェクトのデリファレンスがいっぱいあって、何が起きているのかよく分かりません。 そこで、-XX:+ShowCodeDetailsInExceptionMessages を付けてみると、以下のように変化しました。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.compareTo(String)" because "this.key[1]" is null
 at net.cyberer.sample.npe.NPESample$Data.compareTo(NPESample.java:43)
 at net.cyberer.sample.npe.NPESample$Data.compareTo(NPESample.java:1)
 at java.base/java.util.Arrays$NaturalOrder.compare(Arrays.java:747)
 at java.base/java.util.TimSort.binarySort(TimSort.java:296)
 at java.base/java.util.TimSort.sort(TimSort.java:221)
 at java.base/java.util.Arrays.parallelSort(Arrays.java:799)
 at net.cyberer.sample.npe.NPESample.main(NPESample.java:19)

これを見ると、this.key[1] が null で、compareTo() が呼べなかった、とあります。その観点で見てみると、16行目のように key[1] が null になってしまうケースがあることに思い当たります。だいぶ分かりやすいですね。