前書き:"==“や”!=“による判定と何が違う?

Javaでコードを書くと、nullチェック(NullPointerException防止)は避けられません。

私のようにC言語脳の人は、"=="(等価演算子)や”!="(不等価演算子)を用いてnullチェックを行うかもしれません。しかし、Java SE8以降は、null関係の判定メソッドとしてjava.utils.ObjectsにisNull()やnonNull()が用意されています。

「演算子使わずにnullチェックするメリットがあるのか?(新しい方法のメリットが分からない)」と思われる方がいらっしゃるかもしれませんが、主に2点メリットがあります。

nullチェックにisNull()やnonNull()を使うメリット

  • ソースコードの可読性向上
  • 演算子を用いるよりも関数型プログラミングが書きやすい

本記事では、Java SE11環境で「従来の演算子を用いたnullチェック方法」と「isNull()やnonNull()を用いた方法」を比較します。また、関数型プログラミングにおけるnullチェック方法に関しても説明します。

演算子を用いたnullチェック方法

以下のサンプルコードでは、文字列リストの内容を表示します。

文字列リストの表示前は、

  • 文字列リストのnullチェック("==“によるチェック)
  • リストから取り出した文字列に対するnot nullチェック(”!=“によるチェック)

を行います。

import java.util.List;
import java.util.Arrays;

public class App {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Rance", null, "Sill Plain", "KENTO Kanami", null, "Ria Parapara-Leazas" );
        System.out.println("===Argument is not null List===");
        printStr(names);
        System.out.println("===Argument is null.===");
        printStr(null);
        System.out.println("===The end.===");
    }

    /**
     * 文字列リストの内容を表示する。Listがnullの場合は、何も表示されない。
     * Listの要素がnullの場合はその要素はスキップされる。
     * @param strs 文字列リスト
     */
    private static void printStr(List<String> strs) {
        if(strs == null) {
            return;
        }
        for(String str: strs) {
            if(str != null) {
                System.out.println(str);
            }
        }
    }
}

以下が実行結果です。文字列リストがnullの場合は何も出力されず、リストの要素がnullの場合は文字列出力がバイパスされます。

===Argument is not null List===
Rance
Sill Plain
KENTO Kanami
Ria Parapara-Leazas
===Argument is null.===
===The end.===

isNull()およびnonNull()を用いたnullチェック 

文字列リスト内容を出力する前述のコード(printStr())をisNull()およびnonNull()で書き換えます。

なお、isNull(Object obj)はobjがnullの場合にtrueを返し、objが非nullの場合はfalseを返します。nonNull(Object obj)は、その逆の動作、すなわち! isNull()となります。

以下、サンプルコードです。

import java.util.List;
import java.util.Objects;
import java.util.Arrays;

public class App {
    public static void main(String[] args) {
        List names = Arrays.asList("Rance", null, "Sill Plain", "KENTO Kanami", null, "Ria Parapara-Leazas" );
        System.out.println("===Argument is not null List===");
        printStr(names);
        System.out.println("===Argument is null.===");
        printStr(null);
        System.out.println("===The end.===");
    }

    /**
     * 文字列リストの内容を表示する。Listがnullの場合は、何も表示されない。
     * Listの要素がnullの場合はその要素はスキップされる。
     * @param strs 文字列リスト
     */
    private static void printStr(List strs) {
        if(Objects.isNull(strs)) { // "strs == null"
            return;
        }
        for(String str: strs) {
            if(Objects.nonNull(str)) {  // "str != null"
                System.out.println(str);
            }
        }
    }
}

出力結果に違いはありません。

isNull()およびnonNull()を用いた方が自然言語(英語)で処理内容が記載されているため、可読性が高いです。個人的な印象としては、上記のコードはPythonライクに見えます。

なお、上記のような判定処理でisNull()/nonNull()を用いるのは冗長という意見もあります。

関数型プログラミングでのisNull()/nonNull()

Java Core API(Javadoc)の説明にある通り、isNull()とnonNull()はStram APIで使用するために設計されています。

このメソッドは、Predicate(filter(Objects::isNull))として使用するために存在します。

Streamを用いた処理は、以下の3工程に大別され、従来のfor文を用いたループ処理よりも簡潔かつ可読性の高いコードを作りやすいです。

  1. Stream生成:Collectionや配列から生成
  2. 中間処理     :フィルター/加工/nullチェックなど
  3. 終端処理     :集計/出力など

以下のサンプルコードでは、Integer型リストに格納された各要素の総和を求める処理をStream APIで記述しています。総和を求めるsum()メソッド内のfilter()処理でリスト要素のnot null判定をしています。

import java.util.List;
import java.util.Objects;
import java.util.Arrays;
import java.util.Collection;

public class App {
    public static void main(String[] args) {
        List numbers = Arrays.asList(1, null, 2, 3, null, 4);
        System.out.println("===Calculate list==");
        System.out.println(sum(numbers));
        System.out.println("===Argument is null.===");
        System.out.println(sum(null));
        System.out.println("===The end.===");
    }

    /**
     * Integer型リストの和を返す。
     * @param integers Integer型のリスト
     * @return Integerリストの和を返す。リストがnullの場合は0を返す。
     */
    public static int sum(List integers) {
        if (Objects.isNull(integers)) {
            return 0;
        }
        return integers.stream()
            .filter(Objects::nonNull)  // 別の書き方は.filter(i -> i != null)
            .mapToInt(Integer::intValue).sum();
        }
}

以下が実行結果です。Integer型リストがnullの場合は0が出力され、リストの要素がnullの場合はその要素はフィルタリング(除外)されます。

===Calculate list==
10
===Argument is null.===
0
===The end.===

フィルタリング処理を「filter(i -> i != null)」と書くより、「filter(Objects::nonNull) 」と書いた方がパッと見で処理を理解しやすい筈です。

おまけ:requireNonNull()

requireNonNull(T obj)は、指定されたオブジェクトがnullでない事を検査するメソッドです。引数がnullではない場合はobjをそのまま返しますが、nullの場合はNullPointerExceptionをスローします。

第二引数に、例外発生時のメッセージを指定できます。

 public Foo(Bar bar, Baz baz) {
     this.bar = Objects.requireNonNull(bar, "bar must not be null");
     this.baz = Objects.requireNonNull(baz, "baz must not be null");
 }

後書き

Javaを書く限りはnullチェックが必要ですが、C言語に比べるとJavaはAPIが揃っているので、まだマシと言えるでしょう。

ちなみに、nullチェックを不要にするAPIとして、java.util.Optionalもあります。