まず全体的にいうと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を許さないことを明言することで、そもそものヌルポが発生することをかえって防ぐことが出来ます。