本ページはプロモーションが含まれています

Java入門

【Java】NullPointerExceptionを撲滅!安全なnull判定

トム

・SaaS勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

「またこのエラーか…」

Javaプログラミングを始めたばかりのころ、私はコンソールに表示されるNullPointerExceptionという文字に何度頭を抱えたか分かりません。このエラーは、Javaを学ぶ多くの人が最初にぶつかる壁の一つです。

10年以上の実務経験を積んだ今では、nullを安全に扱うための様々なテクニックを身につけ、チーム開発のコード品質を向上させる手助けができるようになりました。しかし、当時はなぜこのエラーが起きるのか、どうすれば防げるのか分からず、デバッグに多くの時間を溶かしていました。

この記事は、過去の私と同じようにNullPointerException(通称:ぬるぽ)に悩むJava初学者の方に向けて書いています。

この記事を読み終えるころには、あなたが得られることは以下の3つです。

  • nullが何か、そしてなぜNullPointerExceptionが起きるのかを根本から理解できる
  • 基本的なif文からモダンなOptionalクラスまで、null判定の正しい書き方を学べる
  • 現場で即使える、バグを未然に防ぐための実践的なテクニックが身につく

もう「ぬるぽ」で開発の手を止めるのは終わりにしましょう。この記事が、あなたのJavaプログラミング学習の一助となれば幸いです。

null判定とは?

Javaにおけるnull判定は、プログラミングの基本でありながら、多くのエラーの原因となる重要なテーマです。まずはnullそのものの意味と、それが引き起こす代表的なエラーについて理解を深めましょう。

Javaにおけるnullの意味

Javaにおけるnullとは、かんたんに言うと「参照先が何もない状態」を指す特別な値です。

変数には、数値や文字列そのものを入れる「プリミティブ型」と、オブジェクトの所在地(メモリ上のアドレス)を入れる「参照型」の2種類があります。nullは後者の参照型の変数にのみ代入でき、「この変数には、どのオブジェクトの所在地も入っていませんよ」ということを示します。

よく「空っぽの箱」に例えられます。箱(変数)はあるけれど、その中にデータ(オブジェクトの実体)が入っていない状態、それがnullです。StringInteger、自作したクラスの変数など、あらゆる参照型変数はnullを保持する可能性があります。

nullが引き起こすエラー(NullPointerException)

nullの変数をうっかり使おうとすると、Javaの世界で最も有名なエラーの一つであるNullPointerExceptionが発生します。これは、プログラマの間で「ぬるぽ」という愛称(?)で呼ばれる、非常にありふれた実行時例外です。

このエラーは、中身が空っぽの箱(nullの変数)を開けて、何かを取り出そうとしたときに起こります。例えば、以下のようなコードを見てみましょう。

String text = null;
int length = text.length(); // ここでNullPointerExceptionが発生

変数textnull、つまり中身がありません。それにもかかわらず、text.length()で文字列の長さを取得しようとしています。存在しないものの長さを測ることはできないため、Javaは「参照先がnullですよ!」とNullPointerExceptionを投げて処理を停止させるのです。

このエラーはコンパイル時にはチェックされず、プログラムを実行して初めて発覚します。そのため、nullの可能性を常に意識し、適切にnull判定を行うことが、安定したプログラムを作るうえで不可欠となります。

Javaでのnull判定の基本

NullPointerExceptionを防ぐ第一歩は、基本的なnull判定の方法をマスターすることです。ここでは、最もシンプルなif文を使った判定方法と、そこで注意すべき点について解説します。

if文を使ったシンプルな判定

最も基本的で分かりやすいnull判定の方法は、if文と==演算子を組み合わせる方法です。変数がnullかどうかを直接比較します。

String name = getSomeName(); // メソッドがnullを返す可能性がある

if (name == null) {
  System.out.println("名前が設定されていません。");
} else {
  System.out.println("ようこそ、" + name + "さん!");
  // nameを使った処理
}

また、nullでない場合に処理をしたい場合は、!=演算子を使います。

Integer score = getScore();

if (score != null) {
  // scoreがnullでない場合のみ処理を実行
  if (score >= 80) {
    System.out.println("高得点です!");
  }
}

このif (variable != null)という形は、java null判定の基本パターンとして頻繁に登場するため、必ず覚えておきましょう。

equalsと==の違いに注意

null判定でよくある間違いの一つが、==equalsメソッドの混同です。特に文字列の比較では注意が必要です。

  • ==: 2つの変数が、メモリ上で全く同じオブジェクトを参照しているかを判定します。null判定ではこちらを使います。
  • equals: 2つのオブジェクトの内容が等しいかを判定します。

nullの変数に対してequalsメソッドを呼び出そうとすると、NullPointerExceptionが発生します。

String text = null;

// 正しいnull判定
if (text == null) {
  // ...
}

// 間違い:textがnullなのでNullPointerExceptionが発生
if (text.equals("hoge")) {
  // ...
}

この問題を回避するための有名なテクニックが、「定数やリテラルを先に書く」方法です。

String text = getText(); // nullの可能性がある

// 安全な書き方
if ("hoge".equals(text)) {
  System.out.println("textは\"hoge\"です。");
}

もし変数textnullだったとしても、文字列リテラルである"hoge"nullではないため、equalsメソッドを安全に呼び出せます。この場合、equalsfalseを返すだけで、エラーにはなりません。このjava null判定テクニックは、バグの温床を一つ潰せるため、日頃から意識すると良いでしょう。

null判定でよくあるバグと対策

null判定の記述を忘れることが、バグの直接的な原因です。特に、他の人が作ったメソッドやライブラリのメソッドを呼び出す際に、その戻り値がnullを返す可能性があることを見落としがちです。

対策1:メソッドの仕様を確認する

メソッドのJavadoc(仕様書)を読み、戻り値としてnullが返される条件を確認する習慣をつけましょう。「このメソッドは絶対にnullを返さない」と確信が持てない限り、null判定を追加するのが安全なアプローチです。

対策2:防御的プログラミングを意識する

外部からの入力や、他のメソッドの戻り値など、自分の管理外から渡される値は常にnullの可能性があると仮定してプログラミングを行う「防御的プログラミング」の考え方が重要です。

public void processUserData(User user) {
  // userがnullでないことを最初にチェックする
  if (user != null && user.getName() != null) {
    System.out.println("ユーザー名: " + user.getName());
  } else {
    // userやuser.nameがnullの場合の処理
    System.out.println("ユーザー情報が不完全です。");
  }
}

このように、処理を始める前に引数のチェックを徹底することで、予期せぬNullPointerExceptionを防ぎ、プログラムの堅牢性を高められます。

nullを安全に扱うためのテクニック

基本的なif文によるnull判定も重要ですが、Javaにはより現代的で安全、かつ可読性の高いnullの扱い方が用意されています。ここでは、中級者以上を目指すならぜひ知っておきたい3つのテクニックを紹介します。

Objectsクラスの活用(Objects.isNull, Objects.nonNull)

Java 7で導入されたjava.util.Objectsクラスは、nullを安全に扱うための便利なユーティリティメソッドを提供します。

  • Objects.isNull(obj): 引数がnullの場合にtrueを返します。obj == nullと等価です。
  • Objects.nonNull(obj): 引数がnullでない場合にtrueを返します。obj != nullと等価です。

これらを使うと、null判定の意図がコード上でより明確になります。

import java.util.Objects;

String message = getMessage();

if (Objects.isNull(message)) {
  System.out.println("メッセージはありません。");
}

if (Objects.nonNull(message)) {
  System.out.println("受信メッセージ: " + message);
}

if (message != null)と書くよりも、if (Objects.nonNull(message))と書いた方が、「nullでないこと」を判定している意図が伝わりやすいと感じる開発者は少なくありません。コードの可読性を高める選択肢として覚えておくと便利です。

Optionalクラスを使った安全なnull処理

Java 8で登場したjava.util.Optionalは、nullを扱う方法を根本から変える画期的なクラスです。Optionalは、nullの可能性がある値をカプセル化する「箱」のようなものです。

Optionalを使う最大のメリットは、値が存在しないかもしれないことを型レベルで明示できる点にあります。メソッドの戻り値の型がStringであればnullの可能性がありますが、Optional<String>であれば、値が入っていない可能性があることを呼び出し元に伝えられます。

Optionalを使ったnull処理は、if文のネストを減らし、流れるようなコード(メソッドチェーン)を書けるのが特徴です。

import java.util.Optional;

public Optional<String> findUserName(int userId) {
  if (userId == 1) {
    return Optional.of("Taro"); // 値が存在する場合
  }
  return Optional.empty(); // 値が存在しない(nullに相当)場合
}

// Optionalの利用
Optional<String> userNameOpt = findUserName(1);

// 値が存在する場合のみ処理を実行
userNameOpt.ifPresent(name -> System.out.println("ユーザー名: " + name));

// 値が存在すればその値を、存在しなければデフォルト値を取得
String displayName = userNameOpt.orElse("ゲスト");
System.out.println("表示名: " + displayName);

if (userName != null)のような判定を一切書かずに、nullの可能性がある値を安全に扱えているのが分かります。Optionalを使いこなすことは、現代的なJavaプログラミングへの第一歩と言えるでしょう。

三項演算子でコンパクトに書く方法

nullだった場合にデフォルト値を設定する、という処理は頻繁に登場します。このようなシンプルなnull判定であれば、三項演算子を使うとコードを1行でコンパクトに書けます。

三項演算子の書式: (条件式) ? 値1 : 値2

条件式がtrueなら値1が、falseなら値2が返されます。

String inputName = getNameFromInput(); // nullの可能性がある

// if文で書く場合
String displayName;
if (inputName != null) {
  displayName = inputName;
} else {
  displayName = "名無し";
}

// 三項演算子で書く場合
String displayName2 = (inputName != null) ? inputName : "名無し";

System.out.println(displayName2); // inputNameがnullなら"名無し"と表示される

このように、単純な値の割り当てであれば三項演算子が非常に有効です。ただし、処理が複雑になる場合は可読性が落ちるため、素直にif文を使う方が良いでしょう。

実践的なnull判定の書き方

基本的なテクニックを学んだところで、より実践的なシナリオでjava null判定をどのように活用するかを見ていきましょう。メソッドの戻り値やコレクションの扱いなど、日常的な開発で直面する場面を想定して解説します。

メソッドの戻り値がnullの場合の対処

データベース検索やAPIからのデータ取得など、処理の結果として該当するデータが存在しない場合にnullを返すメソッドは数多く存在します。このようなメソッドを呼び出す側は、必ずnullチェックを行う必要があります。

public User findUserById(String id) {
  // データベースを検索... 見つからなければnullを返す
  return user;
}

// メソッドの呼び出し側
User foundUser = findUserById("001");

// 必ずnullチェックを行う
if (foundUser != null) {
  // ユーザーが見つかった場合の処理
  processUser(foundUser);
} else {
  // ユーザーが見つからなかった場合の処理
  showNotFoundMessage();
}

設計の観点からは、安易にnullを返すのではなく、前述のOptionalクラスを戻り値の型として使うことが推奨されます。Optional<User>を返すようにすれば、呼び出し元はnullの可能性を強制的に意識させられるため、nullチェックの漏れを防ぎやすくなります。

コレクションや配列でのnullチェック

ListMapなどのコレクション、そして配列を扱う際には、2種類のnullを意識する必要があります。

  1. コレクションや配列のインスタンス自体がnullである可能性
  2. コレクションや配列の要素にnullが含まれている可能性

これらを考慮しないと、思わぬNullPointerExceptionに繋がります。

List<String> names = getNames(); // このリスト自体がnullかもしれない

if (names != null) { // 1. インスタンス自体のnullチェック
  for (String name : names) {
    // 2. 要素のnullチェック
    if (name != null) {
      System.out.println(name.toUpperCase());
    }
  }
}

特に拡張for文(for-each)は、コレクションや配列のインスタンス自体がnullの場合、ループに入る前にNullPointerExceptionが発生します。ループ処理を行う前には、必ずインスタンスのnullチェックを行いましょう。

デフォルト値を設定してエラーを防ぐ

プログラムの要件によっては、nullの代わりに安全な「デフォルト値」を設定することで、後続の処理でNullPointerExceptionが発生するのを防ぐことができます。

例えば、ユーザーの設定情報がnullの場合、システムの標準設定値を使う、といったケースです。

// ユーザー設定を取得(nullの可能性がある)
UserPreferences prefs = loadPreferences(userId);

// 三項演算子でデフォルト値を設定
int fontSize = (prefs != null && prefs.getFontSize() != null) ? prefs.getFontSize() : 14;

// Optionalを使うと、よりスマートに書ける
Optional<UserPreferences> prefsOpt = Optional.ofNullable(loadPreferences(userId));
int fontSize2 = prefsOpt
  .map(UserPreferences::getFontSize) // prefsが存在すればフォントサイズを取得
  .orElse(14); // nullならデフォルト値の14を設定

nullnullのまま後続の処理に渡すのではなく、早い段階で安全な値に置き換えてしまう。このアプローチは、プログラムの各所でnull判定を繰り返す手間を省き、コード全体をシンプルで安全なものにします。

まとめ

今回は、Javaプログラミングにおける永遠のテーマである「java null判定」について、その基本から実践的なテクニックまでを網羅的に解説しました。

null判定のポイントのおさらい

最後に、この記事で紹介した重要なポイントを振り返りましょう。

  • nullとは: 「参照先が何もない」状態。nullの変数を使おうとするとNullPointerExceptionが発生します。
  • 基本の判定: if (variable == null) または if (variable != null) を使います。
  • equalsの注意点: "リテラル".equals(variable) の順で書くとNullPointerExceptionを予防できます。
  • Objectsクラス: Objects.isNull()Objects.nonNull() でコードの意図を明確にできます。
  • Optionalクラス: nullの可能性を型で表現し、安全でモダンなコードを書くための強力な武器です。
  • 防御的プログラミング: メソッドの入口で引数をチェックし、戻り値がnullでないか常に確認する習慣が大切です。

初心者が陥りやすい落とし穴と回避法

Java初心者がnull関連でつまずくのは、主に「nullの可能性を想定していない」ことが原因です。

  • 落とし穴: メソッドの戻り値は常に有効なオブジェクトだと信じ込んでしまい、nullチェックを怠る。
  • 回避法: 「どんな参照型変数もnullになりうる」という心構えを持ちましょう。確信が持てない限り、if文やObjects.nonNull()でチェックする癖をつけるのが一番の近道です。

nullとの付き合い方は、一日にして成らず。しかし、今回紹介した基本的な考え方とテクニックを一つずつ実践していけば、NullPointerExceptionに悩まされる時間は確実に減っていきます。

  • この記事を書いた人
  • 最新記事

トム

・SaaS勤務/javaのバックエンドエンジニア
/java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎

-Java入門