Java 에서 컬렉션이 Null 인지 확인하는 방법

JavaBeginner
지금 연습하기

소개

이 랩에서는 Java 에서 컬렉션 작업 시 null 값을 효과적으로 처리하는 방법을 배우게 됩니다. 컬렉션은 기본적인 데이터 구조이며, 컬렉션 변수가 null 일 수 있는 상황을 안전하게 관리할 수 있는 견고한 코드를 작성하는 것이 중요합니다. null 컬렉션을 테스트하는 방법, 포괄적인 유효성 검사를 위해 null 및 empty 검사를 결합하는 방법, 그리고 향상된 null 안전성을 위해 Optional 클래스를 활용하는 방법을 탐구하여, 궁극적으로 일반적인 NullPointerException 오류를 방지하는 데 도움이 될 것입니다.

컬렉션 Null 검사

이 단계에서는 Java 에서 컬렉션 작업 시 null 값을 처리하는 방법을 살펴보겠습니다. List 또는 Set과 같은 컬렉션은 기본적인 데이터 구조이며, 컬렉션 변수가 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과 같지 않은지 (names != null) 확인하는 if 문을 추가했습니다. 크기를 가져와 출력하는 코드는 이제 이 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 및 Empty 검사 결합

이전 단계에서는 컬렉션이 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 및 empty 검사를 수행하여 출력의 차이점을 확인합니다.

  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 및 empty 검사를 단일 조건으로 결합해 보겠습니다. 일반적인 패턴은 컬렉션이 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.");
        }
    }

    논리 OR 연산자 (||) 를 사용하여 조건 names == nullnames.isEmpty()를 결합합니다. if 블록은 조건 중 하나라도 참이면 실행됩니다. 또한 빈 목록과 채워진 목록으로 테스트를 추가하여 결합된 검사가 어떻게 작동하는지 확인했습니다.

    중요 참고 사항: names == null || names.isEmpty()에서 조건의 순서는 매우 중요합니다. namesnull인 경우 조건의 첫 번째 부분 (names == null) 이 참입니다. Java 의 단락 평가로 인해 두 번째 부분 (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 클래스를 사용하여 잠재적인 null 값을 처리하는 보다 현대적인 접근 방식을 살펴보겠습니다. Optionalnull이 아닌 값을 포함할 수도 있고 포함하지 않을 수도 있는 컨테이너 객체입니다. 값의 존재 또는 부재를 보다 명시적으로 나타내는 방법을 제공하여 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.");
        }
    }

    이 코드에서:

    • Optional<List<String>>을 반환하는 getNames 메서드를 정의합니다. 이 메서드는 목록을 얻을 수도 있고 아무것도 얻지 못할 수도 있는 시나리오를 시뮬레이션합니다 (빈 Optional로 표시됨).
    • main 메서드에서 truefalsegetNames를 호출하여 두 경우를 모두 테스트합니다.
    • 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에서 생성된 예외를 throw 합니다.
  • 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의 발생 가능성을 줄일 수 있습니다.

요약

이 랩에서는 NullPointerException을 방지하기 위해 Java 에서 null 컬렉션을 처리하는 방법을 배웠습니다. 런타임 오류로 이어지는 null 컬렉션에서 메서드를 호출하는 문제점을 먼저 시연했습니다.

그런 다음, 요소 또는 속성에 접근하기 전에 컬렉션이 null 인지 안전하게 확인하는 다양한 기술을 탐구했습니다. 이는 견고하고 오류 없는 Java 코드를 작성하기 위한 기본적인 기술입니다.