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

Java Optional

by dvid 2023. 6. 14.

Optional

개발 배경

자바로 프로그램을 개발하면서 NullPointerException을 겪지 않은 사람은 없다.

public class Person {
    private Car car;

    public Car getCar() {
        return this.car;
    }
}

public class Car {
    private Insurance insurance;

    public Insurance getInsurance() {
        return this.insurance;
    }
}

public class Insurance {
    private String name;
    public String getName() {
        return this.name;
    }
}

Null을 체크하는 방법

public String getInsuranceName(Person person) {
    if (person != null) {
        Car car = person.getCar();
        if (car != null) {
            Insurance i = car.getInsurance();
            if (insurance != null) {
                return i;
            }
        }
    }
    return "Unknown";
}

public String getCarInsuranceName(Person person) {
    if (person == null) {
        return "Unknown";
    }

    Car car = person.getCar();
    if (car == null) {
        return "Unknown";
    }

    Insurance i = car.getInsurance();
    if (i == null) {
        return "Unknown";
    }

    return i.getName();
}

두 방법 모두 가독성이 좋지 않고 충분히 오류를 일으킬 수 있음.

null 때문에 발생하는 문제

  • 에러의 근원
  • 코드 가독성 저하
  • 의미 없음
  • 자바 철학에 위배

Optional 클래스 등장

자바 8은 하스켈과 스칼라의 영향을 받아 java.util.Optional<T> 클래스를 제공한다.

Optional은 값을 캡슐화한다.

public class Person {
    private Car car;

    public Optional<Car> getCar() {
        return Optional.nullable(this.car);
    }
}

public class Car {
    private Insurance insurance;

    public Optional<Insurance> getInsurance() {
        return Optional.nullable(this.insurance);
    }
}

public class Insurance {
    private String name;
    public String getName() {
        return this.name;
    }
}

Optional 클래스를 사용하면 모델의 의미가 더 명확해진다. 사람은 자동차를 소유할 수도, 안 할 수도 있고, 자동차는 보험이 있을 수도, 없을 수도 있다.

또한, 보험은 이름을 반드시 가지는 것을 보여준다.

Optional 객체 만들기

빈 Optional

Optional<Car> car = Optional.empty();

값을 가진 Optional

Optional<Car> optcar = Optional.of(car);

null이 들어가면 NPE

null을 저장할 수 있는 Optional

Optional<Car> optcar = Optional.ofNullable(car);

Optional Best Practice

Optional 변수에 null 할당하지 말자

// Bad
public Optional<Car> getCar() {
    Optional<Car> emptyCar = null;
    ...
}

// Good
public Optional<Car> getCar() {
    Optional<Car> emptyCar = Optional.empty();
    ...
}

Optional.get을 사용하기 전에 값이 있는지 확인하자

  • get()은 가장 간단한 방법이지만 위험하다. 값이 있으면 해당 값을 반환하지만, 값이 없으면 NoSuchElementException을 발생시킨다.
// Bad
Optional<Car> optCar = Optional.nullable(car);
Car myCar = optCar.get();

// Good
Optional<Car> optCar = Optional.nullable(car);

if(optCar.isPresent()) {
    Cart myCar = optCar.get();
}else {
    ...
}

ifPresent() - get() 보다는 orElse()

// Bad
public String getInsuranceName(Car car) {
    Optional<Insurance> optInsurance = car.getInsurance();

    if (optInsurance.isPresent()) {
        return optInsurance.get().getName();
    } else {
        return "Unknown";
    }
}

// Good
public String getInsuranceName(Car car) {
    Optional<Insurance> optInsurance = car.getInsurance();

    return optInsurance.orElse("Unknown");
}

orElseGet()


public double calculate() {
    // 보험료 계산
}

// Bad
public int getInsuranceCost(Car car) {
    Optional<Insurance> optInsurance = car.getInsurance();

    return optInsurance.orElse(calculate()); 
}

// Good
public String getInsuranceCost(Car car) {
    Optional<Insurance> optInsurance = car.getInsurance();

    return optInsurance.orElseGet(() -> calculate()); 
}

값이 있어야 calculate() 실행

orElseThrow

public Car getCar(long personId) {
    return carRepository.findCarByPersonId(personId)
                        .orElseThrow(CarNotFoundException::new);
}

ifPresent

// Bad
Optional<Car> optCar = Optional.nullable(car);
if (optCar.isPresent()) {
    System.out.println(optCar.get());
}

// Good
Optional<Car> optCar = Optional.nullable(car);
optCar.ifPresent(System.out::println);

사용하지 말아야 할 곳

필드, 파라미터 등

// Bad
public class Foo {
    Optional<String> optName;

    public setName(Optional<String> name) {
        this.optName = name;
    }
}

출처
모던 자바 인 액션 364~388
Java Optional Best Practices

댓글