안녕하세요! 오늘은 Java로 프로그래밍할 때 NPE(Null Pointer Exception)을 방지하는 방법에 관해 이야기해 보겠습니다.
들어가기에 앞서, 코드 표현에 유의 사항을 알려드립니다.
String 같은 라이브러리나 Util 클래스에서 static method로 사용한 것은 메서드 실행 시, 참조하는 클래스가 대문자로 시작합니다.
String.valueOf
클래스 명이 string, service, controller 같이 보편적인 타입을 의미하고 인스턴스를 만들어서 메서드를 사용하는 경우, 인스턴스를 선언 및 초기화하는 코드 없이 참조하는 클래스가 소문자로 시작합니다.
string.equals()
우선 우리가 피해야 할 null, NPE이란 무엇이고 언제 발생하는지 보겠습니다.
1. null, NPE은 무엇인가
null
인스턴스나 기본 자료의 변수가 비어 있거나 의미가 없는 성질을 나타냅니다.
도메인과 결합하여 생각해보면 고객번호가 아직 부여 되지 않은 고객을 생성해야 할 때에, 고객번호를 null로 하여 고객 객체를 생성하는 것이 있습니다.
NPE
우리는 참조, point 기능을 사용하여 변수를 선언 및 초기화하며 변수를 활용하는 프로그래밍을 합니다.
본체를 가리키는 분신과 같은 변수를 사용하여 코드를 구성하는 것이죠.
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
public void gobackHome() {
System.out.println(name + "이 집에 갑니다.");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("최수정");
student.gobackHome();
}
}
예를 들어 Student 클래스 안에 gobackHome
이라는 메서드를 사용하기 위해new Student()
로 인스턴스를 만들어 Student student
변수에 생성한 인스턴스의 포인트(참조)를 넣어 준뒤,
이 참조 변수를 통해 메서드를 실행할 수 있습니다.
하지만 만약 Student 인스턴스가 변수에 제대로 초기화 되지 않은 상태에서 메서드가 실행되면 어떻게 될까요?
public class Test {
public static void main(String[] args) {
Student student = null;
student.gobackHome();
}
}
그럼 NPE이 터지게 됩니다!
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Student.gobackHome()" because "student" is null
우리가 기본 타입 자료처럼 쓰는 String도 자바 내부에서 인스턴스처럼 쓰이므로 NPE이 터지지 않도록 유의해야 합니다.
아래와 같은 예시를 통해 String 라이브러리를 사용하며 생길 수 있는 NPE를 알 수 있습니다.
// string.length()
String nolenStr = "";
String nullStr = null;
System.out.println(nolenStr.length()); // 0
System.out.println(nullStr.length()); // NPE 발생
// string.equals()
String str = null;
boolean result = str.equals("example"); // NPE 발생
boolean result = "example".equals(str); // 안전
이와 같이 null은 런타임 에러인 NPE을 유발합니다. 따라서 null을 사용해야 한다면 NPE가 발생하지 않도록 처리해주어야 해서 아래와 같은 단점이 발생하게 됩니다.
null 사용을 조심해야 하는 이유
1. null이 남용되면 시스템의 상태가 불명확해 진다.
null이 '값이 없음'을 의미하는지 '초기화되지 않음'을 의미하는지 '에러 발생'을 나타내는지 불분명해서 디버깅과 유지보수를 어렵게 합니다.
2. null 체크를 해야 한다.
null을 처리하기 위해 모든 객체 사용 전에 null을 체크 하는 코드를 계속 추가해줘야 합니다.
3. 호출 계층에서 문제가 확산된다.
null이 반환 될 때 제때 처리하지 않으면 계층 전체에 문제를 전파합니다.
그럼 null처리를 모든 계층에 계속 해주어야 합니다.
2. null-safety programming
null 사용시 겪은 단점과 NPE를 피하기 위해 null-safefy하게 코드를 구성하는 방법에 대해 알아보겠습니다.
명시적 null 체크
if (str == null || str.isEmpty()) {
// 인스턴스가 의미가 없거나 비어 있는 상태이므로 예외 처리 코드를 추가합니다.
}
// 정상 로직 ...
이때, `== null`은 변수가 null인지를 검사하며 `isEmpty()`는 String 객체가 공백만을 가진 ("") 빈 값인지 확인합니다.
isEmpty() 함수도 사용 시 NPE이 발생할 수 있기 때문에 꼭 null인지 확인하는 로직이 먼저 들어가야 합니다.
+) String은 값이 빈 값(len = 0)인지, 공백인지 확인하는 메서드를 제공합니다.
String str1 = " ";
String str2 = "";
str1.isBlank(); // true
str2.isBlank(); // true
str1.isEmpty(); // false
str2.isEmpty(); // ture
미리 초기화
클래스 멤버 변수나 배열을 선언하고 초기화 하지 않으면 null상태가 될 수 있습니다.
이를 방지하기 위해 초기화 하는 로직을 확실히 하거나, 선언과 동시에 초기 값을 할당해주는 것이 좋습니다.
class Student {
private String name; // 초기화하지 않으면 null 상태
public Student(String name) {
this.name = name; // 생성자에서 초기화
}
//private List<String> inBag; 초기화 하는 로직 없이 바로 참조하면 NPE 발생
private List<String> inBag = new ArrayList<>(); // 바로 초기화하여 방지
}
class Main {
Student student = new Student("수정");
System.out.println(student.getInBag().size());
}
기본값 지정으로 null 회피
public List<String> getItems() {
return items != null ? items : Collections.emptyList();
}
Optional
java8 이후에 도입되었습니다.
Optional<String> optionalValue = Optional.ofNullable(someString);
optionalValue.ifPresent(value -> System.out.println(value));
이와 같은 방법들로 null-safety한 프로그램을 만들 수 있습니다!
번외) 언어적 차원 : 코틀린
자바가 null을 도입한 이후 null 위험성이 높아지면서 null을 엄격히 관리하는 코틀린에 대해 이목이 집중되고 있습니다.
코틀린은 java와 달리 null을 직접적으로 허용하지 않으며 컴파일 타임에 null 관련 오류를 예방할 수 있도록 설계되어 있습니다.
따라서 완전 null에 안전한 프로그램을 강제하고 싶다면 java에서 코틀린으로 시스템 언어 변경을 고려해볼 수 있습니다.
- Nullable과 Non-Nullable 타입의 구분
- 안전 호출 연산자 `?.`
Nullable 타입의 변수를 다룰 때, 안전 호출 연산자를 사용하여 null일 때 메서드 호출을 무시하게 할 수 있습니다.
val length: Int? = nullableString?.length
nullableString이 null이면 ?. 연산자가 해당 호출을 무시하고 결과는 null
- 엘비스 연산자 `?:`
null에 대한 대체 값을 제공합니다.
val lengthL Int = nullableString.length ?: 0
nullableString이 null이면 0을 반환
- 안전한 타입 캐스팅 `as?`
캐스팅이 실패하면 null을 반환합니다.
val number: Int? = obj as? Int
obj가 Int 타입이 아니면 null 반환
- 코틀린 표준 라이브러리 제공 함수
let, apply, run, also
Nullable 객체를 안전하게 처리할 수 있는 매커니즘 제공
3. 마무리
이번 포스트에서는 null을 어떻게 사용하는지, NPE이 무엇인지와 어느 상황에서 발생하는지 알아보았습니다.
또한 NPE을 피하는 null-safety 코드를 만들기 위한 규칙 또한 알아보며 코틀린 언어에서는 null을 어떻게 방지하는지 알아보았습니다.
null에 대한 정확한 이해로 NPE를 피한 즐거운 프로그래밍을 합시다 ^0^
'Tech > java' 카테고리의 다른 글
람다식을 사용하기 전에 꼭 알아야 하는 클래스 구조 (feat. OOP를 향해) (0) | 2024.06.05 |
---|