tcdhry
@tcdhry

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

JavaのNullPointerExceptionの拾い方は何がベストか

Q&A

Javaのシステムを開発中、null処理の方法に迷ったので質問です。
String以外のキャストも同じ疑問がありますが、今回の例はStringとします。

nullもしくは文字列が格納されているobjを引数に渡します。
実行時エラーで終了しないように、安全にStringにキャストする処理を考えたとき、以下の2通りの方法が思いつきました。

// pattern 1
public static String cast(Object obj) {
    if(obj == null) {
        return null;
    }
    return obj.toString();
}

// pattern 2
public static String cast(Object obj) {
    try {
        return obj.toString();
    } catch(Exception e) {
        return null;
    }
}

どちらも結果は変わりませんが、どちらの方が「より良い方法」と言えますでしょうか。
また、これ以外にも良い方法があれば教えてください。

1

3Answer

基本的に前者でよいかと思われます。

基本的にtry catchは例外をスローされたときに受け取る機構です。
謎規定による「全部try catchで」みたいなのがあればそれに従うまでですが、nullチェックのためだけに使う事はあまりないでしょう。

2Like

Comments

  1. @tcdhry

    Questioner

    ご回答ありがとうございます。
    たとえば、`Object→String→int`と変換したい場合はどうでしょうか?

    1.nullチェック
    2.Stringに変換
    3.正規表現等で文字列の整数チェック
    4.intに変換

    という手順を踏むよりも
    ```java
    try {
    return Integer.parseInt(obj.toString());
    } catch() {
    return -1;
    }
    ```
    で済ませた方が楽に感じるのですが、
    この場合も例外を起こさない前者の方がよい方法ですか?
  2. ここからは個人的な意見ですが、nullチェックと例外処理は切り分けるべきと考えています。
    それはエラー発生源を明確にして、デバッグを容易くするためです。

    いきなりobj.toString()するのはよほどobjがnullでは無い自信の表れです。
    ですがobjはnullの可能性があり、しかもInteger.parseIntのなかに書いてしまっていす。
    ご存知かと思いますがparseIntはNumberFormatExceptionをスローします。
    NullPointerとエラー行が被る上にExceptionが混在しているのはデバッグの際に混乱を招くためあまり良いとはいえません。

    try catchでひとまとめに適する箇所も存在することでしょう。ですので私はこれを否定することはありません。
    ただ自分の中でヨシとしていても他者からするとNGだったりします。確かに楽ですが楽した分、デバッグで苦労する事になります。余計な仕事をしたくないのは皆同じです。

    余談ですが「null許容型」というのもありますが、脱線しそうなので読み物だけ張っておきます。

    https://qiita.com/omochimetaru/items/ee29d4c6eb0d78f02b15
  3. @tcdhry

    Questioner

    なるほどたしかにデバッグ面を考えると
    例外を投げさせないほうが結果として楽かもしれないですね
    ありがとうございました。

実装によっては、対象オブジェクトの toString() で Exception が発生する可能性があります。
それについて、どんなExceptionであっても無視してよいのであれば、パターン2ですね。

1Like

まず全体的にいうとNullPointerExceptionが発生してプログラムが止まることを恐れてはいけません。
処理には「nullを許容し代替処理が定義されている」ものと「nullが渡されるのを許容しない」ものの2種類があります。例外は例外なのであり「nullを渡してはいけない処理にnullを渡している」から例外が発生するのです。
システムとして例外が発生したときのことは常に想定し、より大きな適切なロジックでcatchして、ログ出力だったり代替処理だったりを用意します。

APIの入力やファイルの読み込みなど、外部通信などの結果の受け取りに2の方法を推奨します。これらはどうしてもnullの混入やその他の例外が発生しますので、読み取りエラーなど含め全てのキャッチを用意します。(ただし、キャッチしたあとは潰すのが必ず正しいわけではなく、不正な値なら例外を更に投げてプログラムを止めるのが正しいケースも多いです)

その他の箇所では1の考え方を利用します、が、あくまで「nullが許容されている値」の確認にnullチェックをするだけであり、「この関数にnullは渡したらいけないのだけどもしかしたらnullを渡すかもしれないからnullチェックして例外潰してその時だけ特別な処理しよう」というのはnullに負けています。自分のプログラム内ではnullが混入することをまず防ぎましょう。

ifでnullチェックしてから別の例外投げたりrequireNonNullなどを使いつつ「この関数は引数がnullなことは許さないから、nullなら先頭で自分で例外を投げる」というのがnullとの正しい付き合い方です。

// ** アンチパターン **
// @param Integer nullじゃない数値
// @return numの二倍
public static Integer double(Integer num) {
    if (num == null) { // 設計に書いてないのにnullの時を考慮して特別な値を返す
        return null; // 返り値がnullになる、次の処理でもnullを想定する必要がある
    }
    return num * 2;
}

// ** 良い例外を投げる **
// @param Integer nullじゃない数値 
// @return numの二倍
public static Integer double(Integer num) {
    Objects.requireNonNull(num, "num はnullを許しません!");  // nullだとここでヌルポが発生する
    // これ以降numはnullで無いことが保証される
    return num * 2; // 返り値もnullでないことが保証される
}

// ** 関数が設計としてnullを許している **
// @param Integer nullもありえる数字
// @return numの二倍またはnumがnullなら0
public static Integer double(Integer num) {
    var _num = num != null ? num : 0;  // デフォルト値や処理を飛ばすことがロジック上正しいならこれでも良いが、基本はnullを渡さない設計のほうが好ましい
    // これ以降_numはnullで無いことが保証される
    return _num * 2; // 返り値もnullでないことが保証される
}

ゴキブリ1匹見かけたら...のように、nullを許容した処理を増やすとプログラム全体でnullが発生するケースが増えます。
例外を投げてnullを許さないことを明言することで、そもそものヌルポが発生することをかえって防ぐことが出来ます。

1Like

Comments

  1. 要約しますと以下です
    ・nullが設計に入っているなら、`if (hoge == null) {`で判定すれば良い(例外の作成は少しコストがある)
    ・nullが設計に入っていない(動作させていはいけない)なら例外を投げたほうが良い


    なんとなく例外を嫌っている匂いがしましたので、質問から少し外れたことを書きました

Your answer might help someone💌