본문 바로가기
Book/모던 자바 인 액션

[모던 자바 인 액션] Chapter 11. null 대신 Optional 클래스 (1)

by ghan2 2024. 11. 15.
💡 이 장의 내용
- null 참조의 문제점과 null을 멀리해야 하는 이유
- null 대신 Optional: null로부터 안전한 도메인 모델 재구현하기
- Optional 활용 : null 확인 코드 제거하기
- Optional에 저장된 값을 확인하는 방법
- 값이 없을 수도 있는 상황을 고려하는 프로그래밍

 

자바로 프로그램을 개발하면서 한 번쯤 마주하게 되는 NullPointerException.. 

 

11.1 값이 없는 상황을 어떻게 처리할까?

public String getCarInsuranceName(Person person) {
	return person.getCar().getInsurance().getName();
}

코드에는 아무 문제가 없는 것처럼 보이지만 차를 소유하지 않은 사람도 많다. 이때 getCar 를 호출하면 어떻게 될까? 

 

11.1.1 보수적인 자세로 NullPointerException 줄이기

예기치 않은 널포인터 예외를 피하기 위해 대부분의 프로그래머는 필요한 곳에서 다양한 null 확인 코드를 추가해서 null 예외 문제를 해결하려 할 것이다. 

// 제목: 너무 많은 출구
public String getCarInsuranceName(Person person) {
	if(person == null) {
    	return "Unknown";
    }
    Car car = person.getCar();
    if(car == null) {
    	return "Unknown";
    }
    Insurance insurance = car.getInsurance();
    if(insurance == null) {
    	return "Unknown";
    }
    return insurance.getName();
}

 

11.1.2 null 때문에 발생하는 문제

  • 에러의 근원이다.
  • 코드를 어지럽힌다.
  • 아무 의미가 없다.
  • 자바 철학에 위배된다: 자바는 개발자로부터 모든 포인터를 숨겼다. 하지만 예외가 바로 Null 포인터다.
  • 형식 시스템에 구멍을 만든다.

11.1.3 다른 언어는 null 대신 무얼 사용하나?

그루비 같은 언어에서는 안전 내비게이션 연산자(?.)를 도입해서 null 문제를 해결했다. 

def carInsuranceName = person?.car?.insurance?.name

히스켈, 스칼라 등의 함수형 언어는 아예 다른 관점에서 접근한다. 히스켈은 Maybe라는 형식을 제공하고 스칼라는 Option[T] 구조를 제공한다. 자바에서는 히스켈과 스칼라의 영향을 받아서 java.util.Optional<T>라는 새로운 클래스를 제공한다.

 

11.2 Optional 클래스 소개

https://www.google.com/url?sa=i&url=https%3A%2F%2Freadtorakesh.com%2Foptional-in-java-8-to-avoid-nullpointer%2F&psig=AOvVaw0MHpW6DpIybI5piPVHIuFb&ust=1731746963402000&source=images&cd=vfe&opi=89978449&ved=0CBcQjhxqFwoTCNCZj6n63YkDFQAAAAAdAAAAABAE

값이 있으면 Optional 클래스는 값을 감싼다. 반면 값이 없으면 Optional.empty 메서드로 Optional을 반환한다.

 

 

11.3 Optional 적용 패턴

11.3.1 Optional 객체 만들기

  • 빈 Optional: 정적 팩토리 메서드 Optional.empty 로 빈 옵셔널 객체를 얻을 수 있다.
Optional<Car> optCar = Optional.empty();
  • null이 아닌 값으로 Optional 만들기
Optional<Car> optCar = Optional.of(car);

이제 car가 null이라면 즉시 널포인터 에러가 발생한다.

  • null값으로 Optional 만들기
Optional<Car> optCar = Optional.ofNullable(car);

널 값을 저장할 수 있는 팩토리 메서드이다. 이제 car가 null이면 빈 옵셔널 객체를 반환한다.

 

11.3.2 맵으로 Optional의 값을 추출하고 변환하기

기존의 널인지 확인하고 객체에 접근하는 방식을 옵셔널을 사용할 때는 이렇게 쓸 수 있다.

Optional<Insurance> optInsurance = Optional.ofNullable(insurance);

Optional<String> name = optInsurance.map(Insurance::getName);

 

stream/Optional - map 비교

 

11.3.3 flatMap으로 Optional 객체 연결

Optional<Person> optPerson = Optional.of(person);

Optional<String> name = optPerson.map(Person::getCar)
	.map(Car::getInsurance)
    .map(Insurance::getName);

안타깝게도 위 코드는 컴파일되지 않는다. 왜 그럴까? 맵의 연산 구조가 Optiona<Optional<Car>>를 반환하기 때문이다. 

 

Optional로 자동차의 보험회사 이름 찾기

flatMap을 사용하여 앞선 문제를 해결할 수 있다.

public String getCarInsuranceName(Optional<Person> person) {
	return person.flatMap(Person::getCar)
    		.flatMap(Person::getCar)
            .flatMap(Car::getInsurance)
            .map(Insurance::getName)
            .orElse("Unknown");