본문 바로가기
Java

[모던 자바 인 액션] Optional 클래스

by hyhs 2023. 12. 9.
728x90
반응형
SMALL

Goal: optional을 사용하기 전 개념을 확실하게 알고 써야할 것 같아 모던 자바 인 액션을 읽고 정리

 

null 참조 역사

  • 자바 포함 최근 수십 년 탄생한 대부분 언어 설계에는 null 참조 개념 포함
  • 함수형 언어인 하스켈, ML 등을 예외적으로 null 참조 개념을 사용하지 않는 언어
    • 대수적 데이터 형식 포함
      • 데이터 형식을 간결하게 표현할 뿐만 아니라 null 같은 특별한 값을 포함할 것인지 등을 명시하는 규격 명세 포함

11. 1 값이 없는 상황 처리

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

예제 11-2

문제점

  • 변수 접근 시마다 중첩된 if 추가
  • 코드 들여쓰기 수준 증가

깊은 의심(deep doubt) (= 반복 패턴(recurring pattern))

예제 11-3

문제점

  • 중첩 if 블록 제거, 즉시 return “Unknow” 반환

→ 너무 많은 출구 - 유지보수 어려움

11.1.2 null 때문에 발생하는 문제

자바에서 null 참조를 사용하면서 발생할 수 있는 이론적, 실용적 문제

  • 에러의 근원
  • 코드를 어지럽힘
  • 아무 의미가 없음
  • 자바 철학에 위배
    • 자바는 개발자로부터 모든 포인터를 숨김
    • but 예외가 있는 것이 null 포인터
  • 형식 시스템에 구멍을 만듦

11.1.3 다른 언어에서 null 대신 사용하는 것

  • 그루비
    • 안전 내비게이션 연산자
  • 하스켈
    • 선택형값(optional value)을 저장할 수 있는 Maybe라는 형식 제공
  • 스칼라
    • T형식의 값을 갖거나 아무 값도 갖지 않을 수 있는 Optiona[T]라는 구조 제공

11. 2 Optional 클래스 소개

자바 8 - java.util.Optional<T>

Optional

  • 선택형값을 캡슐화하는 클래스
  • 값이 있으면 값을 감쌈
  • 값이 없으면 Optional.empty 메서드로 Optional 반환
    • Optional의 특별한 싱글턴 인스턴스를 반환하는 정적 팩토리 메서드

Optional.empty()와 null 참조 차이

  • null 참조 - NullPointerException 발생
  • Optional.empty()는 Optional 객체이므로 이를 다양한 방식으로 활용

null → Optional 사용

  • null 참조 대신 값이 없을 수 있음 명시적으로 표시
public class Person {
    private Optional<Car> car; // 사람이 차를 소유했을 수도, 소유하지 않았을 수도 있으므로 Optional로 정의
    public Optional<Car> getCar() {
        return car;
    }

    public class Car {
        private Optional<Insurance> insurance; // 자동차가 보험에 가입되어 있을 수도, 가입되어 있지 않았을 수도 있으므로 Optional로 정의
        public Optional<Insurance> getInsurance() {
            return insurance;
        }
    }
    
    public class Insurance {
        private String name; // 보험회사에는 이름이 반드시 있음
        public String getName() {
            return name;
        }
    }
}

 

 

  • 도메인 모델의 의미가 더 명확해짐 - 있을 수도 아닐 수도 있음을 명확히 설명
  • 값이 없는 상황이 우리 데이터에 문제가 있는 것인지 아니면 알고리즘의 버그인지 명확하게 구분 가능
  • Optional이 등장하면 이를 언랩해서 값이 없을 수 있는 상황에 적절하게 대응하도록 강제하는 효과

 

11.3 Optional 적용 패턴

  • 실제 사용

11.3.1 Optional 객체 만들기

  • 빈 Optional
    • 정적 팩토리 메서드 Optional.empty()
    Optional<Car> optCar = Optional.empty();
    
  • null이 아닌 값으로 Optional 만들기
    • 정적 팩토리 메서드 Optional.of로 null이 아닌 값을 포함하는 Optional
    Optional<Car> optCar = Optional.of(car);
    
    • car가 null이라면 즉시 NullPotinterException 발생
      • Optional을 사용하지 않았다면 car의 프로퍼티에 접근하려 할 때 에러가 발생했을 것
  • null값으로 Optional 만들기
    • 정적 팩토리 메서드 Optional.ofNullable로 null 값을 저장할 수 있는 Optional을 만듦
    Optional<Car> optCar = Optional.ofNullable(car);
    

Optional이 비어있으면 get을 호출했을 때 예외 발생

→ Optional로 명시적인 검사를 제거할 수 있는 방법

Optional에서 제공하는 기능이 스트림 연산에서 영감을 받았음

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

  • Optional은 map 메서드 지원
    • 스트림의 map - 스트림의 각 요소에 제공된 함수를 적용하는 연산
  • Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
    Optional<String> name = optInsurance.map(Insurance::getName);
  • 스트림과 Optional의 map 메서드 비교

11.3.3 flatMap으로 Optional 객체 연결

  • map으로 코드 재구현
    • 위 코드 컴파일되지 않음 - getInsurance는 또 다른 Optional 객체를 반환하므로 getInsurance 메서드를 지원하지 않음
    • 스트림의 flatMap - 함수를 인수로 받아서 다른 스트림을 반환하는 메서드
      • 인수로 받은 함수를 적용해서 생성된 각각의 스트림에서 콘텐츠만 남김
        • 이차원 Optional을 일차원 Optional로 평준화해야 됨
        • flatMap 메서드 덕분에 이차원 Optional이 하나의 삼각형을 포함하는 하나의 Optional로 바뀜
      • → 함수를 적용해서 생성된 모든 스트림이 하나의 스트림으로 병합되어 평준화됨
      • Optional<Person> optPerson = Optional.of(person);
        Optional<String> name = optPerson.map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName);
728x90
반응형
LIST

댓글