はじめに
Javaでnullを扱う結論は、参照が存在しない可能性を境界で明示し、内部処理ではif、Optional、ライブラリ、テストを役割ごとに使い分けることです。初心者がつまずきやすいのは、nullそのものではなく、toUpperCase()やlength()のようなメソッド呼び出しを安全確認なしに行う点になります。
そのため、null判定は単なる条件分岐ではなく、エラー処理、戻り値設計、テスト設計まで含めて整理すると理解しやすくなります。公式ドキュメントによれば、NullPointerExceptionはオブジェクトが必要な場面でnullを使ったときに投げられる例外です。
- Java 17 / JDK 17 API を基準に記述
- JUnit Jupiter 5.10 系の書き方を補足
- Apache Commons Lang 3.x / Google Guava 33.x の代表的なAPIを参照
- Javaにおける
nullの意味と発生しやすい場面 == null、三項演算子、Objectsを使う判定の考え方Optional、Stream API、外部ライブラリによるnull処理- 引数、戻り値、例外を含めたエラー処理の組み立て方
- JUnitで
nullをテストするサンプルコードの読み方
nullとは何か
nullは、変数がオブジェクトを参照していない状態を表す特別な値です。参照型には代入できますが、intやbooleanのようなプリミティブ型には代入できません。
これを言い換えると、nullは空文字や数値0とは別物です。""は長さ0のStringオブジェクトであり、nullには呼び出せるメソッドも保持している値もありません。
nullの定義
Javaの変数は、参照型であればオブジェクトへの参照を保持します。その参照先が存在しないことを表す値がnullであり、判定を入れないまま操作すると例外につながります。
結果: 期待される状態は、strとobjがどちらもオブジェクトを参照していない状態です。
このサンプルコードは出力を伴いませんが、null判定を学ぶ最小単位になるのが基本です。初心者はString str = nullとString str = ""を混同しやすいため、空の値と参照なしを分けて読むと理解しやすくなります。
nullが生まれるシナリオ
一般にnullは、変数の初期化が遅れる場面、検索結果が存在しない場面、外部APIやデータベースから値が返らない場面で発生します。そのため、入力値、戻り値、設定値、通信結果の境界で判定を設ける設計が現実的です。
- 変数を宣言したが、まだ初期化していない
- メソッドが条件に合うオブジェクトを返せなかった
- 外部リソースとの通信が失敗した
- データベース検索で対象レコードが存在しなかった
- 判定の結果として、特定条件で
nullを返した
こうした場面をすべて禁止することは難しいため、nullが来る契約なのか、来ない契約なのかを早い段階で決める必要があるのが目安です。契約が曖昧なまま処理を書くと、上級者でもエラー処理の位置が散らばり、保守時に原因を追いにくくなります。
💡 Tips: nullを返す可能性があるメソッドには、名前、Javadoc、戻り値型のいずれかで意図を残すと読み手の負担が下がります。| 場面 | よく使う判定 | 向いている書き方 | 注意点 |
|---|---|---|---|
| 単純な参照確認 | value == null | if文 | 処理分岐が長くなりすぎないようにする |
| 代替値の設定 | value != null | 三項演算子やObjects.requireNonNullElse | 代替値が妥当かを別途考える |
| 戻り値の不在表現 | Optional.empty() | Optional | フィールドや引数に乱用しない |
| コレクション内の除外 | Objects::nonNull | Stream.filter | 除外してよいデータか確認する |
| 文字列処理 | StringUtils | Apache Commons Lang | 依存ライブラリの方針をそろえる |
| 引数の契約違反 | requireNonNull | 早期例外 | 例外メッセージを具体化する |
| テスト | assertThrows | JUnit Jupiter | 正常系と異常系を分けて書く |
この早見表は、判定の書き方を暗記するためではなく、値がない状況をどの層で扱うかを選ぶための整理です。入力の入口ではrequireNonNull()、値の変換ではOptional.map()、集合の整理ではfilter()のように、責務に合わせて選ぶと分岐が散らかりにくくなります。
Javaでのnullの扱い
Javaではnullを参照型に代入できる一方、nullに対してメソッドを呼び出すことはできません。公式ドキュメントによれば、Optionalは値が存在する場合と存在しない場合を型で扱うためのクラスです。
その考え方を踏まえると、null判定は例外を隠す処理ではなく、値がないことをどの層で受け止めるかという設計になります。Java List型完全ガイドで扱うListでも、要素にnullを許すかどうかを先に決めると判定が整理できます。
nullが問題を引き起こす例
初心者が最初に遭遇しやすいのは、nullの変数からメソッドを呼び出してNullPointerExceptionが投げられるケースです。コンパイル時にすべてのnull可能性が検出されるわけではないため、実行経路に入ってから例外になることがあるのがポイントです。
結果: 期待される結果は、str.toUpperCase()の行でNullPointerExceptionが発生することです。
このサンプルコードでは、strが参照先を持たないままtoUpperCase()を呼び出しています。そのため、大文字変換の対象となるStringを見つけられず、エラー処理を入れていない場合は処理が中断されます。
nullと比較する正しい方法
nullとの比較には、equals()ではなく==を使いるのが一般的です。str.equals(null)のように書くと、str自体がnullのときにequals()を呼び出せず、判定前に例外が発生します。
結果: 期待される出力はstrはnullですです。
この判定では、str == nullの分岐で参照の有無を先に確認しています。そのため、else側に入った時点でstrはnullではないと読めるため、toUpperCase()の呼び出しが安全になります。
基本的なnull判定の方法
基本的なnull判定は、if文で明示する方法と、短い代入式に三項演算子を使う方法に分けられますし、ここがポイントです。どちらもnullそのものを直接比較するため、初心者が最初に押さえる書き方になります。
一方、判定後の処理が長い場合に三項演算子を使うと読みづらくなります。エラー処理、ログ出力、早期returnを含むならif文、値を選ぶだけなら三項演算子と整理すると扱いやすくなるのが現実的です。
サンプルコード1:if文でのnull判定
if文は、null判定の意図が読み手に伝わりやすい書き方です。条件式ではstr == nullで参照がない場合、str != nullで参照がある場合を表します。
結果: 期待される出力はstrはnullですです。
このサンプルコードでは、String型のstrにnullを代入し、if文で判定しています。上級者向けの設計でも、入力境界でif (value == null)を使って早めに分岐させる書き方はよく採用されます。
サンプルコード2:三項演算子でのnull判定
三項演算子は、条件式に応じて代入する値を切り替える構文です。条件式 ? 値1 : 値2の形になり、trueなら前側、falseなら後ろ側の値が選ばれますが、これは押さえたい点です。
結果: 期待される出力はstrはnullですです。
この書き方は、判定の結果をmessageのような単一の変数に入れる場合に向いています。ただし、例外を投げる、ログを残す、複数の値を更新する処理では、if文に分けたほうが条件の意味を追いやすくなります。
equals()を使う比較では、左辺がnullの可能性を常に考える必要があると整理できます。文字列リテラルと比べる場合は"OK".equals(value)のように、nullにならない値を左側へ置く書き方もあります。同様に、Objects.equals(a, b)を使うと、片方がnullでも安全に等価判定できます。文字列やIDを比較する処理では、単純な==とequals()の役割を分けておくと、意図しない例外を避けやすくなると理解できます。
オプショナルクラスを使用したnull判定
Optionalは、値が存在するかもしれない状況を型として表すJava 8以降のクラスです。公式APIでは、値が存在する場合はisPresent()がtrueを返し、存在しない場合は空のOptionalとして扱われます。
そのため、戻り値で見つからなかった状態を表したいときは、nullを直接返すよりOptional.empty()を返すほうが契約を読み取りやすくなります。Javaアノテーションの記事で扱う@Nullable系の注釈と組み合わせる設計もあると覚えるとよいでしょう。
サンプルコード3:Optionalクラスの基本的な使い方
Optional.ofNullable()は、引数がnullなら空のOptionalを作り、nullでなければ値を持つOptionalを作ります。orElse()を続けると、値がない場合の代替値を同じ式で扱えます。
結果: 期待される出力は結果: デフォルト値です。
このサンプルコードでは、Optional.ofNullable(null)により空のOptionalが作られます。その後のorElse("デフォルト値")が代替文字列を返すため、resultにはデフォルト値が入ります。
サンプルコード4:Optionalでnullを安全に処理
Optional.map()は、値が存在する場合だけ変換処理を適用すると考えられます。値がない場合は変換関数を呼ばず、後続のorElse()で代替値へ進みます。
結果: 期待される出力は結果: HELLOです。
このコードでは、optionalがhelloを保持しているため、String::toUpperCaseが適用されます。一方でOptional.ofNullable(null)に変えると、map()は変換を行わず、orElse()の値が使われます。
ただし、Optionalをすべての変数に使えば安全になるわけではありません。引数やフィールドに広げすぎると、Optional<Optional<T>>のような読みづらい型につながるため、主に戻り値で不在を表す場面に絞るのが一般的です。
Java8+の機能を使ったnull処理
Java 8以降では、StreamとOptionalを組み合わせることで、コレクション内のnullや存在しない値を流れの中で処理できると言えるでしょう。個別のif文だけで書くより、データ変換の意図が短くまとまる場合があります。
一方、Streamの処理は中間操作と終端操作の流れを読む必要があります。初心者はstream()、filter()、collect()の順に、上級者はObjects::nonNullやflatMap(Optional::stream)まで広げて学ぶと理解が深まりますし、これが一つの目安です。
サンプルコード5:Stream APIでnullをフィルタ
Stream.filter()は、条件を満たす要素だけを後続へ流す中間操作です。item -> item != nullを条件にすると、nullではない要素だけがcollect()に渡ります。
結果: 期待される出力は[Java, Python, Ruby]です。
このサンプルコードでは、Arrays.asList()で作ったリストにnullが混ざっています。filter()で非nullの要素だけを残すため、結果のfilteredListにはJava、Python、Rubyが入ります。
サンプルコード6:OptionalとStreamの組み合わせ
Stream<Optional<String>>のような形では、値が存在する要素と存在しない要素が同じ流れに入りますが、覚えておくと役立つでしょう。Java 8の書き方ではfilter(Optional::isPresent)で存在する値だけを残し、map(Optional::get)で中身を取り出します。
結果: 期待される出力は結果: Java, Pythonです。
このコードでは、Optional.empty()がfilter(Optional::isPresent)で除外されます。その後、map(Optional::get)で値を取り出し、Collectors.joining(", ")で文字列として連結します。
ライブラリを使ったnull判定
Java標準APIだけでもnull判定は書けますが、文字列処理や引数検証では外部ライブラリのメソッドが役立つ場面があるのが基本です。Apache Commons LangのStringUtilsやGuavaのPreconditionsは、nullに対する挙動が明確に設計されています。
そのため、既にプロジェクトで導入されているなら、同じライブラリの方針に寄せるとコードの見通しがそろいます。Javaエスケープ処理の記事で扱う文字列の前処理でも、入力がnullかどうかは早めに判断しておく必要があるのが目安です。
サンプルコード7:Apache Commons Langを使用したnull安全なコード
StringUtils.upperCase()は、引数がnullの場合に例外を投げず、nullを返す挙動を持ちます。文字列変換だけを安全に通したい場合には、毎回if文を書くより短く表現できます。
結果: 期待される出力は大文字に変換した結果:nullです。
このサンプルコードでは、strがnullのままStringUtils.upperCase(str)に渡されます。StringUtilsがnullを許容するため、NullPointerExceptionではなくnullのまま結果に反映されます。
サンプルコード8:Google Guavaでのnull処理
GuavaのPreconditions.checkNotNull()は、値がnullならNullPointerExceptionを投げ、nullでなければ値を返するのがポイントです。これは、この引数は必ず存在するという契約をメソッドの入口で明示したい場合に使われます。
結果: 期待される出力は文字列がnullですです。
この例では、checkNotNull()がstrの状態を判定し、nullなら指定メッセージ付きの例外を投げます。tryとcatchで受けるため、エラー処理としてメッセージを出力する流れになります。
Objects.requireNonNull()があるのが一般的です。外部ライブラリを増やしたくない場合は、標準APIで足りるかを確認すると依存関係を抑えられます。nullの注意点と対処法
nullの注意点は、値がないこと自体よりも、値がない可能性を呼び出し側と呼び出される側のどちらが受け持つかにあります。メソッド設計では、引数、戻り値、フィールドのそれぞれでnullを許可するかどうかを分けて考えますし、ここを基本と考えるとよいでしょう。
そのため、エラー処理の方針も一律ではありません。入力が契約違反ならIllegalArgumentExceptionやNullPointerExceptionを早期に投げ、検索結果の不在ならOptionalや空のListで返すと、読み手が処理の意味を追いやすくなります。
nullを引数として渡す際の注意
引数にnullが渡る可能性があるメソッドでは、入口で判定するか、呼び出し契約としてnull禁止を明記する必要があります。判定を入れないままstr.length()を呼ぶと、メソッド内部のどこで失敗したのかが見えにくくなるのが現実的です。
結果: 期待される結果は、str.length()の呼び出しでNullPointerExceptionが発生することです。
このサンプルコードでは、printLength(null)によってstrにnullが渡ります。その後、length()を呼び出すため、エラー処理がなければ例外で中断されます。
対処法
対処法は、メソッドの冒頭でnullを判定し、処理を続けるか戻るかを明確にすることです。業務ロジックとして空扱いにできるならメッセージを返し、契約違反なら例外を投げる形に分けますし、ここがポイントです。
結果: 期待される出力は、引数がnullの場合に引数がnullですです。
この書き方では、returnによって後続のlength()呼び出しを止めています。処理を早めに抜けるため、通常処理のネストが浅くなり、null判定が読み取りやすくなります。
nullを返値とするメソッドの対処法
戻り値としてnullが返るメソッドは、受け取る側で必ず判定する必要があると整理できます。とくにリポジトリ検索や設定値取得のように存在しない状態が自然に起きる処理では、戻り値の契約を文書化しておくとエラー処理が安定します。
結果: 期待される結果は、result.length()の呼び出しでNullPointerExceptionが発生することです。
この例では、getString()が常にnullを返すため、受け取ったresultに参照先がありません。その状態でlength()を呼ぶと、長さを計算する対象を見つけられません。
対処法
返値がnullになり得るなら、受け取り直後に判定を置くと原因の位置が明確になります。Optional<String>を返せる設計なら、呼び出し側に値の不在を型として伝えることもできます。
結果: 期待される出力は、resultがnullの場合に取得した文字列はnullですです。
この判定を入れると、result.length()はnullではない場合だけ呼び出されます。Javaでうるう年を判定する記事のような条件分岐でも、判定対象が存在するかを先に確認すると分岐の意味が崩れません。
もっとも、すべての不在をnullで返す設計は、呼び出し側の負担を増やします。空のコレクションを返せるならCollections.emptyList()、不在の意味を明確にしたいならOptionalを検討すると、上級者向けの保守設計に近づきますが、これは押さえたい点です。
nullを握りつぶして空文字や0に変換すると、入力ミスと本当に空の値を区別できなくなる場合があります。代替値を使うときは、ログや例外で原因を追える形を残すと安全です。テストコードでnullをチェックする方法
null判定は、実装だけでなくテストコードでも確認します。特にエラー処理を含むメソッドでは、nullを渡した場合に例外を投げるのか、代替値を返すのか、何もしないのかをテスト名から読み取れるようにすると理解できます。
そのため、正常系と異常系を同じテストに詰め込まず、assertEquals()とassertThrows()を分けて書く構成が扱いやすくなります。JUnit公式のAssertions APIには、条件確認や例外確認のためのメソッドが用意されています。
サンプルコード9:JUnitを使ったnullのテストケース
JUnit Jupiterでは、例外が投げられることをassertThrows()で確認できると覚えるとよいでしょう。null判定を意図的に入れないメソッドをテストする場合も、例外が仕様であるならテストに明記します。
結果: 期待される結果は、testStringLengthWithNonNull()が長さ4を確認し、testStringLengthWithNull()がNullPointerExceptionを確認することです。
このサンプルコードでは、getStringLength("Java")に対してassertEquals(4, ...)を使い、正常な文字列の長さを確認します。反対にgetStringLength(null)はassertThrows()で囲み、エラー処理として例外が起きる仕様を固定します。
一方で、メソッドを安全にしたい場合は、実装側でif (str == null)を入れ、テスト側では代替値や戻り値を検証すると考えられます。Javaのオーバーライド解説で扱う継承関係でも、親クラスと子クラスでnull許容の契約が変わると利用者が混乱します。
こうしたテストは、初心者にとっては例外の学習になり、上級者にとっては仕様変更の検知になります。エラー処理を後から足す場合でも、先にテストでnull入力の期待値を固定しておくと、コードの変更範囲を判断しやすくなると言えるでしょう。
その視点で読むと、nullは避けるだけの値ではなく、入力の欠落、検索結果の不在、設定値の未指定を表す合図として扱えます。ただし、合図の意味をコード上で説明しないまま広げると、判定とエラー処理が各所に散り、後から読む人が原因を追いにくくなります。
具体的には、公開メソッドの引数ではObjects.requireNonNull()や明示的なifで契約を示し、戻り値ではOptionalや空コレクションを選ぶと、呼び出し側の責任範囲が明確になるのが基本です。サンプルコードを写すだけでなく、どの層でnullを受け止めるかを決めることが、実用的な判定設計になります。
まとめ
Javaでnullを安全に扱うには、値がない可能性を曖昧にせず、判定、代替値、例外、テストのどこで受け止めるかを決める必要があります。初心者は== nullと!= nullを使った基本の判定から始め、三項演算子やOptionalへ進むと理解がつながりますし、これが一つの目安です。
そのうえで、Stream API、Apache Commons Lang、Guava、JUnitを組み合わせると、null処理はより読みやすくなります。ただし、ライブラリに任せるだけでは契約の曖昧さは消えないため、引数や戻り値にnullを許すかどうかを明確にする姿勢が欠かせません。
具体的には、単純なnull判定にはif文、短い値の切り替えには三項演算子、不在を戻り値で表す場面にはOptional、コレクションの整理にはStream.filter()を使い分けます。エラー処理を伴う箇所では、例外メッセージやテストコードまで含めて読むと、上級者向けの保守しやすいコードに近づきますが、覚えておくと役立つでしょう。
最後の確認として、nullを来ないはずと考えるだけではなく、来た場合にどう振る舞うかをサンプルコードとテストで固定しておくと、変更に強い設計になります。判定処理を小さく保ち、エラー処理を境界に集めることが、読みやすいコードを保つ近道です。
関連記事
- Java List型完全ガイド!初心者でもマスターできる7つのステップ
- Javaアノテーションの12選!初心者から上級者まで徹底ガイド
- Javaでうるう年を判定!初心者でも分かる9ステップ解説
- Javaエスケープ処理の10ステップマスターガイド
- Javaでマスターする!オーバーライドのたった7つのステップ
※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。


