개발을 하다보면 가장 흔히 맞닦드리는 예외로 NullPointerException이 있습니다. 컴파일 과정에서는 잡기 힘듦과 동시에, 런타임 중 불시에 발생하여 프로그램을 강제 종료시킬 수 있는 이 위협적인 예외는 자바를 사용하는 모든 개발자들이 골머리를 앓는 문제입니다. 이번 포스팅에서는 NullPointerException을 방지하기 위한 방안인 Optional 클래스의 등장 배경과 그 효과에 대해 알아보도록 하겠습니다.
Optional 클래스는 참조되는 객체가 Null인지를 컴파일 타임에 확인하도록 개발자에게 의무를 부여합니다.
평화롭던 주말, 당신이 배포한 프로그램이 에러를 일으켜, 잠옷 차림으로 회사에 출근합니다. 책상에 앉아 골똘히 고민하던 당신은 다음과 같은 코드에서 NullPointerException이 발생했다는 걸 알았습니다.
public class Main {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.getCar().getInsurance().getName());
}
}
똘똘한 당신은 getCar() 메소드가 null을 반환하거나 getInsurance() 메소드가 null을 반환하여 NullPointerException가 발생했다는걸 알고, 아래와 같은 코드를 작성할 수 있겠습니다.
public class Main {
public static void main(String[] args) {
Person person = new Person();
Car personOwnedCar = person.getCar();
if (personOwnedCar == null) {
System.out.println("이 사람은 차가 없습니다!");
return;
}
Insurance carInsurance = personOwnedCar.getInsurance();
if (carInsurance == null) {
System.out.println("이 사람은 차 보험이 없습니다!");
}
System.out.println(carInsurance.getName());
}
}
위와 같은 코드를 작성한 당신은 무사히 테스트, 배포를 마치고 집에 다시 돌아갈 수 있게 됐습니다. 집에 가던 당신은 미리 이런 로직을 짜지 않았던 자신에 대해 후회하게 됩니다. null 리턴 가능성이 있는 메소드인지를 전부 파악했다면, 이런 고생은 안해도 됐을텐데 말이죠.
하지만, API에서 발생가능한 리턴값을 일일이 파악하는게 가능할까요?
API를 사용하는 개발자에게 에러처리를 하도록 프로그램 차원에서 강제할 수는 없을까요?
Optional 클래스는 이런 null에 대한 처리를 개발자에게 강제합니다.
아래 코드는 이전에 당신이 사용했던 API입니다.
class Person {
private Car car;
public Car getCar() {
return this.car;
}
}
class Car {
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
}
class Insurance {
private String name;
public String getName() {
return this.name;
}
}
위와 같은 구성을 계속 사용한다면, 위 API를 사용하는 누군가는 당신이 겪은 끔찍한 경험을 또 겪겠죠.
당신은 이를 끊어내기 위해 API의 리턴 타입를 Optional<T> 로 변경합니다.
class Person {
private Car car;
public Optional<Car> getCar() { // Optional을 통해 리턴값이 null일 수 있다는 것을 명시합니다.
return Optional.ofNullable(this.car);
}
}
class Car {
private Insurance insurance;
public Optional<Insurance> getInsurance() { // Optional을 통해 리턴값이 null일 수 있다는 것을 명시합니다.
return Optional.ofNullable(insurance);
}
}
class Insurance {
private String name;
public Optional<String> getName() { // Optional을 통해 리턴값이 null일 수 있다는 것을 명시합니다.
return Optional.ofNullable(name);
}
}
이를통해 당신은 위 API를 사용하는 개발자가 로직에 관한 처리에 앞서 Optional 객체를 먼저 처리하도록 강제합니다. 이 과정에서 이 API 호출의 결과 값이 null일 수 있음을 인지할 수 있게 됩니다.
따라서 NullPointerException 걱정 없이 다음과 같이 사용할 수 있게 됩니다.
public class Main {
public static void main(String[] args) {
Person person = new Person();
Optional<Car> optionalCar = person.getCar();
if (optionalCar.isEmpty()) {
System.out.println("차가 없습니다");
return;
}
Car personCar = optionalCar.get();
Optional<Insurance> optionalInsurance = personCar.getInsurance();
if (optionalCar.isEmpty()) {
System.out.println("보험이 없습니다");
return;
}
Insurance carInsurance = optionalInsurance.get();
System.out.println(carInsurance);
}
}
따라서, API를 사용하는 개발자는 위와 같은 로직을 필수적으로 구현할 수 밖에 없도록 강제되어 NullPointerException을 근본적으로 방지할 수 있게 됩니다.
이렇게 Optional 클래스가 등장하게 된 경위에 대해서 알아봤습니다.
아래 코드는 Stream을 이용해 Optional을 처리하는 간결한 코드입니다. Optional 클래스가 제공하는 API와 stream을 이용한 효율적인 연산, 그리고 주의해야 할 점에 대해서는 다음 포스팅에서 자세하게 다뤄 보도록 하겠습니다
public class Main {
public static void main(String[] args) {
Person person = new Person();
// person의 car, 그리고 car의 insurance의 name을 안전하게 접근하고 출력
String insuranceName = person.getCar()
.flatMap(Car::getInsurance)
.flatMap(Insurance::getName)
.orElse("Unknown"); // Optional이 비어있다면 "Unknown" 반환
System.out.println(insuranceName);
}
}
긴 글 읽어주셔서 감사합니다.
다음 글 링크입니다.
'Java' 카테고리의 다른 글
Java IO / NIO (0) | 2024.12.16 |
---|---|
[Java] Optional 클래스 살펴보기 (2) (0) | 2024.01.24 |
[Java] 제네릭 - 와일드 카드 (0) | 2024.01.15 |
[Java] Collection Framework (1) | 2024.01.10 |
[Java] 제네릭의 기초 (1) | 2024.01.04 |