Java でコレクションが null かどうかをチェックする方法

JavaBeginner
オンラインで実践に進む

はじめに

この実験では、Java のコレクションを扱う際に、null 値を効果的に処理する方法を学びます。コレクションは基本的なデータ構造であり、コレクション変数が null である可能性のある状況を安全に管理できる堅牢なコードを書くことは非常に重要です。null のコレクションをテストする方法、包括的な検証のために null チェックと空チェックを組み合わせる方法、および強化された null 安全性のために Optional クラスを活用する方法を探索します。最終的には、一般的な NullPointerException エラーを回避するのに役立ちます。

コレクションが null かどうかをテストする

このステップでは、Java のコレクションを扱う際に null 値をどのように処理するかを探ります。ListSet などのコレクションは基本的なデータ構造であり、コレクション変数が null である可能性のある状況を安全に処理できるコードを書くことは非常に重要です。

Java で null 値とは、変数が何らかのオブジェクトを参照していないことを意味します。null オブジェクトのメソッドやプロパティにアクセスしようとすると、プログラムは NullPointerException でクラッシュします。これは Java で非常に一般的なエラーであり、回避方法を学ぶことは不可欠です。

まずは、この問題を示す簡単な Java プログラムを作成してみましょう。

  1. WebIDE エディタで HelloJava.java ファイルが開いていない場合は、開きます。

  2. ファイルの内容全体を以下のコードに置き換えます。

    import java.util.List;
    
    public class HelloJava {
        public static void main(String[] args) {
            List<String> names = null; // Intentionally set to null
    
            // This line will cause a NullPointerException if names is null
            // int size = names.size();
            // System.out.println("Size of the list: " + size);
    
            System.out.println("Program finished.");
        }
    }
    

    このコードでは、names という文字列の List を宣言し、明示的に null に設定しています。コメントアウトされた行は、null のリストに対して size() メソッドを呼び出そうとした場合に何が起こるかを示しています。これは NullPointerException を引き起こします。

  3. ファイルを保存します(Ctrl+S または Cmd+S)。

  4. 次に、プログラムをコンパイルしましょう。WebIDE の下部にあるターミナルを開き、~/project ディレクトリにいることを確認します。以下のコマンドを実行します。

    javac HelloJava.java
    

    コンパイルが成功すると、何も出力されないはずです。

  5. 次に、プログラムを実行します。

    java HelloJava
    

    以下の出力が表示されるはずです。

    Program finished.
    

    NullPointerException を引き起こす行がコメントアウトされているため、プログラムはクラッシュすることなく実行されます。

では、コレクションを使用する前に null をチェックするようにコードを修正しましょう。

  1. エディタで再度 HelloJava.java を開きます。

  2. main メソッドを修正して、null チェックを追加します。

    import java.util.List;
    
    public class HelloJava {
        public static void main(String[] args) {
            List<String> names = null; // Intentionally set to null
    
            if (names != null) {
                // This code will only run if names is NOT null
                int size = names.size();
                System.out.println("Size of the list: " + size);
            } else {
                System.out.println("The list is null.");
            }
    
            System.out.println("Program finished.");
        }
    }
    

    namesnull でないかどうかをチェックする if 文を追加しました(names != null)。リストのサイズを取得して表示するコードは、この if ブロック内にあります。つまり、names が有効なリストオブジェクトである場合にのみ実行されます。else ブロックは、namesnull の場合を処理します。

  3. ファイルを保存します。

  4. 修正したプログラムをコンパイルします。

    javac HelloJava.java
    
  5. 再度プログラムを実行します。

    java HelloJava
    

    今回は、以下の出力が表示されるはずです。

    The list is null.
    Program finished.
    

    プログラムはリストが null であることを正しく識別し、適切なメッセージを表示し、NullPointerException を回避しました。

この単純な if (collection != null) チェックは、コレクションを扱う際に NullPointerException を防ぐ最も基本的な方法です。これは Java プログラミングで頻繁に使用する基本的なテクニックです。

null チェックと空チェックを組み合わせる

前のステップでは、コレクションが null かどうかをチェックする方法を学びました。しかし、コレクションは null でなくても (要素を含まない)場合があります。多くの場合、null のコレクションと空のコレクションを同じように扱いたい、または少なくとも両方の可能性を処理したいことがあります。

コレクションが空かどうかのチェックは、isEmpty() メソッドを使用して行います。このメソッドは、コレクションに要素が含まれていない場合は true を返し、それ以外の場合は false を返します。

null のリストと空のリストの違いを示し、その後チェックを組み合わせるようにプログラムを修正してみましょう。

  1. WebIDE エディタで HelloJava.java ファイルを開きます。

  2. 内容を以下のコードに置き換えます。

    import java.util.List;
    import java.util.ArrayList; // Import ArrayList
    
    public class HelloJava {
        public static void main(String[] args) {
            List<String> nullList = null; // Intentionally set to null
            List<String> emptyList = new ArrayList<>(); // An empty list
    
            System.out.println("Checking nullList:");
            if (nullList != null) {
                System.out.println("nullList is not null.");
                if (nullList.isEmpty()) {
                    System.out.println("nullList is empty.");
                } else {
                    System.out.println("nullList is not empty.");
                }
            } else {
                System.out.println("nullList is null.");
            }
    
            System.out.println("\nChecking emptyList:");
            if (emptyList != null) {
                System.out.println("emptyList is not null.");
                if (emptyList.isEmpty()) {
                    System.out.println("emptyList is empty.");
                } else {
                    System.out.println("emptyList is not empty.");
                }
            } else {
                System.out.println("emptyList is null.");
            }
    
            System.out.println("\nProgram finished.");
        }
    }
    

    新しい空の ArrayList として初期化される emptyList を追加しました。そして、nullListemptyList の両方に対して同じ null チェックと空チェックを行い、出力の違いを確認します。

  3. ファイルを保存します。

  4. ターミナルでプログラムをコンパイルします。

    javac HelloJava.java
    
  5. プログラムを実行します。

    java HelloJava
    

    以下のような出力が表示されるはずです。

    Checking nullList:
    nullList is null.
    
    Checking emptyList:
    emptyList is not null.
    emptyList is empty.
    
    Program finished.
    

    この出力から、nullListnull であり、emptyListnull ではないが空であることが明確にわかります。

では、null チェックと空チェックを 1 つの条件に組み合わせましょう。一般的なパターンは、コレクションが null または空であるかどうかをチェックすることです。

  1. エディタで HelloJava.java を開きます。

  2. main メソッドを修正してチェックを組み合わせます。

    import java.util.List;
    import java.util.ArrayList;
    
    public class HelloJava {
        public static void main(String[] args) {
            List<String> names = null; // Can be null or an empty list
    
            // Combined check: is names null OR is names empty?
            if (names == null || names.isEmpty()) {
                System.out.println("The list is null or empty.");
            } else {
                System.out.println("The list is not null and not empty.");
                // You can safely iterate or access elements here
                // For example:
                // System.out.println("First element: " + names.get(0));
            }
    
            // Let's test with an empty list
            List<String> anotherList = new ArrayList<>();
            System.out.println("\nChecking anotherList (empty):");
            if (anotherList == null || anotherList.isEmpty()) {
                System.out.println("anotherList is null or empty.");
            } else {
                System.out.println("anotherList is not null and not empty.");
            }
    
            // Let's test with a non-empty list
            List<String> populatedList = new ArrayList<>();
            populatedList.add("Item 1");
            System.out.println("\nChecking populatedList (not empty):");
            if (populatedList == null || populatedList.isEmpty()) {
                System.out.println("populatedList is null or empty.");
            } else {
                System.out.println("populatedList is not null and not empty.");
            }
    
    
            System.out.println("\nProgram finished.");
        }
    }
    

    論理和演算子 (||) を使用して、names == nullnames.isEmpty() の条件を組み合わせています。どちらかの条件が真の場合、if ブロックが実行されます。また、空のリストと要素があるリストでテストを追加し、組み合わせたチェックがどのように動作するかを確認します。

    重要な注意: names == null || names.isEmpty() の条件の順序は重要です。namesnull の場合、条件の最初の部分 (names == null) は真です。Java の短絡評価により、2 番目の部分 (names.isEmpty()) は 評価されません。これにより、NullPointerException が防止されます。もし names.isEmpty() || names == null と書き、namesnull だった場合、names.isEmpty() を呼び出すと NullPointerException が発生します。オブジェクトに対する他のチェックと組み合わせる場合は、常に null最初に チェックしてください。

  3. ファイルを保存します。

  4. プログラムをコンパイルします。

    javac HelloJava.java
    
  5. プログラムを実行します。

    java HelloJava
    

    以下のような出力が表示されるはずです。

    The list is null or empty.
    
    Checking anotherList (empty):
    anotherList is null or empty.
    
    Checking populatedList (not empty):
    populatedList is not null and not empty.
    
    Program finished.
    

    これは、組み合わせたチェックが null と空のリストを正しく識別し、要素があるリストと区別する方法を示しています。この組み合わせたチェックは、null または空である可能性のあるコレクションを処理する非常に一般的で安全な方法です。

Null 安全のために Optional を使用する

このステップでは、Java 8 で導入された Optional クラスを使用して、Java で潜在的な null 値を処理するより現代的なアプローチを探ります。Optional は、null ではない値を含む場合と含まない場合があるコンテナオブジェクトです。値の存在または不在をより明示的に表現する方法を提供し、NullPointerException のリスクを減らすのに役立ちます。

if (collection != null) のチェックは多くの状況で完全に有効で必要ですが、Optional を使用すると、特に値を返す場合と null を返す場合があるメソッドを扱うときに、コードがより読みやすく表現力豊かになります。

コレクションで Optional をどのように使用できるか見てみましょう。Optional は通常、単一の値に使用されますが、メソッドが Optional<List<SomeObject>> を返すシナリオに遭遇することがあります。

  1. WebIDE エディタで HelloJava.java ファイルを開きます。

  2. 内容を、潜在的に null のリストで Optional を使用する例を示す以下のコードに置き換えます。

    import java.util.List;
    import java.util.ArrayList;
    import java.util.Optional; // Import Optional
    
    public class HelloJava {
    
        // A method that might return an Optional containing a list, or an empty Optional
        public static Optional<List<String>> getNames(boolean includeNames) {
            if (includeNames) {
                List<String> names = new ArrayList<>();
                names.add("Alice");
                names.add("Bob");
                return Optional.of(names); // Return an Optional containing the list
            } else {
                return Optional.empty(); // Return an empty Optional
            }
        }
    
        public static void main(String[] args) {
            // Case 1: Get names when includeNames is true
            Optional<List<String>> namesOptional1 = getNames(true);
    
            System.out.println("Checking namesOptional1:");
            // Check if the Optional contains a value
            if (namesOptional1.isPresent()) {
                List<String> names = namesOptional1.get(); // Get the list from the Optional
                System.out.println("List is present. Size: " + names.size());
                // You can also check if the list itself is empty
                if (names.isEmpty()) {
                    System.out.println("List is empty.");
                } else {
                    System.out.println("List is not empty. First name: " + names.get(0));
                }
            } else {
                System.out.println("List is not present (Optional is empty).");
            }
    
            System.out.println("---");
    
            // Case 2: Get names when includeNames is false
            Optional<List<String>> namesOptional2 = getNames(false);
    
            System.out.println("Checking namesOptional2:");
            if (namesOptional2.isPresent()) {
                List<String> names = namesOptional2.get();
                System.out.println("List is present. Size: " + names.size());
                if (names.isEmpty()) {
                    System.out.println("List is empty.");
                } else {
                    System.out.println("List is not empty. First name: " + names.get(0));
                }
            } else {
                System.out.println("List is not present (Optional is empty).");
            }
    
    
            System.out.println("\nProgram finished.");
        }
    }
    

    このコードでは:

    • getNames メソッドを定義して、Optional<List<String>> を返します。このメソッドは、リストを取得する場合と何も取得しない場合(空の Optional で表される)のシナリオをシミュレートします。
    • main メソッドで、getNamestruefalse で呼び出して、両方のケースをテストします。
    • namesOptional.isPresent() を使用して、Optional にリストが含まれているかどうかをチェックします。
    • isPresent() が true の場合、namesOptional.get() を使用してリストを取得します。これは、存在をすでにチェックしているため安全です。
    • isPresent() ブロック内で、names.isEmpty() などのリスト自体のチェックを実行できます。
  3. ファイルを保存します。

  4. ターミナルでプログラムをコンパイルします。

    javac HelloJava.java
    
  5. プログラムを実行します。

    java HelloJava
    

    以下のような出力が表示されるはずです。

    Checking namesOptional1:
    List is present. Size: 2
    List is not empty. First name: Alice
    ---
    Checking namesOptional2:
    List is not present (Optional is empty).
    
    Program finished.
    

    この出力は、Optional がリストがまったく返されない場合をどのように処理するのに役立つかを示しています。

Optional は、値が存在しない場合を処理するための他の便利なメソッドも提供します。例えば:

  • orElse(defaultValue): 値が存在する場合はその値を返し、そうでない場合はデフォルト値を返します。
  • orElseGet(supplier): 値が存在する場合はその値を返し、そうでない場合は supplier 関数の結果を返します。
  • orElseThrow(exceptionSupplier): 値が存在する場合はその値を返し、そうでない場合は exceptionSupplier によって生成される例外をスローします。
  • ifPresent(consumer): 値が存在する場合、指定されたアクションを実行します。

リストが存在する場合により簡潔に処理するために、ifPresent を使用するようにコードを修正してみましょう。

  1. エディタで HelloJava.java を開きます。

  2. main メソッドを修正して ifPresent を使用するようにします。

    import java.util.List;
    import java.util.ArrayList;
    import java.util.Optional;
    
    public class HelloJava {
    
        public static Optional<List<String>> getNames(boolean includeNames) {
            if (includeNames) {
                List<String> names = new ArrayList<>();
                names.add("Alice");
                names.add("Bob");
                return Optional.of(names);
            } else {
                return Optional.empty();
            }
        }
    
        public static void main(String[] args) {
            // Case 1: Get names when includeNames is true
            Optional<List<String>> namesOptional1 = getNames(true);
    
            System.out.println("Checking namesOptional1 using ifPresent:");
            namesOptional1.ifPresent(names -> {
                // This block only runs if namesOptional1 contains a list
                System.out.println("List is present. Size: " + names.size());
                if (names.isEmpty()) {
                    System.out.println("List is empty.");
                } else {
                    System.out.println("List is not empty. First name: " + names.get(0));
                }
            });
            if (!namesOptional1.isPresent()) { // Still need a check if you need to handle the absence case
                 System.out.println("List is not present (Optional is empty).");
            }
    
    
            System.out.println("---");
    
            // Case 2: Get names when includeNames is false
            Optional<List<String>> namesOptional2 = getNames(false);
    
            System.out.println("Checking namesOptional2 using ifPresent:");
             namesOptional2.ifPresent(names -> {
                System.out.println("List is present. Size: " + names.size());
                if (names.isEmpty()) {
                    System.out.println("List is empty.");
                } else {
                    System.out.println("List is not empty. First name: " + names.get(0));
                }
            });
             if (!namesOptional2.isPresent()) {
                 System.out.println("List is not present (Optional is empty).");
             }
    
    
            System.out.println("\nProgram finished.");
        }
    }
    

    if (namesOptional.isPresent()) { ... namesOptional.get() ... } 構造を namesOptional.ifPresent(names -> { ... }) に置き換えました。ラムダ式 (names -> { ... }) 内のコードは、Optional に値が含まれている場合にのみ実行されます。ifPresent は値が存在する場合のみを処理するため、Optional が空の場合を処理するために if (!namesOptional.isPresent()) チェックを追加しています。

  3. ファイルを保存します。

  4. プログラムをコンパイルします。

    javac HelloJava.java
    
  5. プログラムを実行します。

    java HelloJava
    

    出力は前と同じになるはずで、ifPresentOptional 内の値の存在を処理する代替方法を提供することを示しています。

Optional を使用すると、値が存在しない可能性に関するコードの意図が明確になり、その不在を明示的に処理するように促されるため、予期しない NullPointerException の可能性が減少します。

まとめ

この実験では、Java で null のコレクションを処理して NullPointerException を防ぐ方法を学びました。まず、null のコレクションに対してメソッドを呼び出すことで実行時エラーが発生する問題を示しました。

次に、コレクションの要素やプロパティにアクセスする前に、コレクションが null かどうかを安全にチェックするさまざまな手法を探りました。これは、堅牢でエラーのない Java コードを書くための基本的なスキルです。