카테고리 없음
Java에서 NullPointerException 피하기
ghyeong
2024. 11. 16. 22:07
반응형
NullPointerException(NPE)는 Java 개발자라면 누구나 한 번쯤 겪어본 골칫거리입니다. 이 글에서는 NPE를 예방하기 위한 Java의 주요 도구인 Optional 클래스를 중심으로, null 안전성을 향상시키는 다양한 기법을 비교하고 활용법을 소개합니다.
NullPointerException: 왜 발생할까?
NPE는 다음과 같은 상황에서 주로 발생합니다:
- null 값을 참조하려고 할 때
String name = null; System.out.println(name.length()); // NPE 발생
- 메서드의 반환값이 null인데 이를 처리하지 않을 때
public String findName() {
return null;
}
System.out.println(findName().length()); // NPE 발생
NPE는 디버깅을 어렵게 만들고, 생산성 저하와 런타임 오류를 유발할 수 있습니다.
Optional 클래스란?
Optional 클래스는 Java 8에서 도입된 기능으로, null 값을 직접 다루지 않고 대신 명시적인 처리를 강제합니다.
Optional을 사용하는 이유
- 명시적 의도 표현: 값이 null일 수 있음을 코드에서 나타냄.
- 안전한 null 처리: null 여부를 검사하는 로직을 줄이고, 메서드 체이닝을 안전하게 지원.
Optional 사용법
반응형
- Optional 생성하기
Optional<String> optionalName = Optional.of("John"); // null이 아닌 값 Optional<String> emptyOptional = Optional.empty(); // 빈 값 Optional<String> nullableOptional = Optional.ofNullable(null); // null 허용
- Optional 값 가져오기
optionalName.get(); // 값 반환 (값이 없으면 예외 발생) optionalName.orElse("Default"); // 값이 없으면 기본값 반환 optionalName.orElseThrow(() -> new IllegalArgumentException("Value is missing"));
- Optional 체이닝
optionalName.map(String::toUpperCase).ifPresent(System.out::println);
Null 안전성을 향상시키는 기법
@NonNull 어노테이션 사용
Lombok 또는 IDE 플러그인을 통해 변수가 null이 될 수 없음을 명시합니다.
public void printName(@NonNull String name) {
System.out.println(name.toUpperCase());
}
Java 14의 NullPointerException 메시지 개선
Java 14부터 NPE 메시지가 개선되어, 어떤 변수에서 문제가 발생했는지 명확하게 알 수 있습니다.
String name = null;
System.out.println(name.toUpperCase());
// 메시지: Cannot invoke "String.toUpperCase()" because "name" is null
Kotlin의 Null 안전성 비교
Java에서의 null 처리보다 Kotlin은 언어 자체에서 null을 엄격히 구분합니다.
var name: String? = null // null 허용
name?.length // 안전 호출
Optional 활용 예시
Optional로 메서드 반환값 처리
public Optional<String> findNameById(int id) {
return id == 1 ? Optional.of("John") : Optional.empty();
}
Optional<String> name = findNameById(2);
name.ifPresentOrElse(
System.out::println,
() -> System.out.println("Name not found")
);
Stream과 Optional 결합
List<String> names = List.of("Alice", "Bob", "Charlie");
Optional<String> foundName = names.stream()
.filter(name -> name.startsWith("B"))
.findFirst();
foundName.ifPresent(System.out::println);
중첩 객체에서 NullPointerException 방지
class User {
private Address address;
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
}
class Address {
private String city;
public Optional<String> getCity() {
return Optional.ofNullable(city);
}
}
Optional<String> city = user.getAddress()
.flatMap(Address::getCity)
.or(() -> Optional.of("Default City"));
System.out.println(city.orElse("Unknown"));
Optional vs Null 직접 처리
특징OptionalNull 직접 처리
코드 가독성 | 더 명확하고 직관적 | null 체크 코드 반복 |
런타임 안정성 | NullPointerException 방지 | null 체크를 누락하면 오류 |
추가 메모리 사용량 | 약간 증가 | 없음 |
API 설계의 명확성 | 명시적으로 null 가능성 표현 | 모호함 |
Optional의 한계
- 모든 경우에 적합하지 않음: 필드나 컬렉션에서 Optional 사용은 권장되지 않습니다.
- 과도한 체이닝은 가독성 저하: 복잡한 체이닝은 이해하기 어려울 수 있습니다.
상황별 Optional 활용
- 메서드 반환값: 반환값이 없거나 null일 가능성이 있으면 Optional을 사용.
- 필드에서는 사용하지 말기: 클래스 필드에 Optional을 사용하는 것은 메모리 낭비와 가독성 저하를 초래.
- 단순한 연산에는 사용하지 않기: 값이 null일 가능성이 낮거나 간단한 null 체크만 필요한 경우, Optional 사용은 과도할 수 있음.
반응형