IT/Java

Optional 클래스 사용

김 정 환 2023. 1. 8. 18:15
반응형
반응형

 

Optional은 반환 값을 선택적으로 하기 위해서 사용할 수 있습니다. 일반적으로 반환 값이 null일 때에 발생하는 에러를 방지할 수 있습니다. null를 확인하는 코드를 작성하다 보면 번거로운 코드 줄을 작성할 때가 있습니다. null을 다루는 Optional에 대해서 알아 보겠습니다.

 

Optional의 사용 방법은 간단합니다. Optional 클래스로 값을 감싸면 됩니다. 만약에 값이 없을 경우에 Optional은 빈 값을 감싸서 반환합니다. 그래서 null을 다룰 수 있습니다.

 

 

예시에 사용될 데이터 모델을 정의하겠습니다.

public class Person {
  private Optional<Car> car;
  public Optional<Car> getCar() {
    return this.car;
  };
  public void setCar(Optional<Car> car) {
    this.car = car;
  };
}


public class Car {
  private Optional<Insurance> insurance;
  public Optional<Insurance> getInsurance() {
    return this.insurance;
  };
  public void setInsurance(Optional<Insurance> insurance) {
    this.insurance = insurance;
  };
}


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

 

 

 

Optional 객체 생성

// 빈 Optional 생성
Optional<Car> optionalCar = Optional.empty();

// null이 아닌 값으로 Optional 생성
Car car = new Car();
Optional<Car> optionalCar = Optional.of(car);

// null 값으로 Optional 생성
Car car = null;
Optional<Car> optionalCar = Optional.ofNullable(car);

 

 

 

Map으로 Optional의 값 다루기

map에 인수로 제공된 함수가 Optional의 값을 바꿉니다. Optional이 비어 있으면 비어 있는 Optional을 반환합니다.

// Optional 사용 이전
String name = null;
if(insurance != null) {
  name = insurance.getName();
}


// Optional 사용 이후
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName());

 

 

 

flatMap으로 Optional의 값 다루기

flatMap은 map과 유사합니다. 다른 점은 이차원으로 싸인 값을 일차원으로 만들어서 연산한다는 것입니다. 예시로 무슨 말인지 알아보겠습니다. 아래와 같이 map을 연속해서 사용하면 map(Person::getCar)가 끝났을 때에 값은 Optional<Optional<Car>> 가 됩니다. 이 상황에서 Car 클래스의 getInsurance()는 사용할 수 없습니다.

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

 

중복된 Optional을 제거하기 위해서 flatMap을 사용합니다. 평평하게 해주겠다는 것입니다.

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

위 파이프라인 연산을 자세히 보겠습니다. 

1. Person을 Optional로 감쌉니다.

 

2. flatMap로 Optional 내부의 Person을 가져와서 메소드 getCar()를 적용합니다.

3. getCar() 메서드는 Optional<Car>를 반환합니다.

4. Optional 내부에서 3번이 마무리 되었으니 반환될 때에는 Optional을 씌워줍니다.

5. Optional<Optional<Car>>가 반환되는데 flatMap 연산으로 Optional<Car>로 평준화를 해줍니다.

 

6. 2번과 동일하게 flatMap으로 Optional 내부의 Car를 가져와서 메소드 getInsurance()를 적용합니다.

...

 

 

 

그외 Optiona 클래스 메서드

메서드 설명
empty 빈 Optional 인스턴스 반환
of 값이 존재하면 값을 Optional로 감싸서 반환,
값이 null이면 NullPointException 발생
ofNullable 값이 존재하면 값을 Optional로 감싸서 반환,
값이 null이면 빈 Optional을 반환
filter 값이 존재하며 predicate와 일치하면 값을 포함하는 Optional을 반환,
값이 없거나 predicate와 일치하지 않으면 빈 Optional을 반환
get 값이 존재하면 값을 Optional로 감싸서 반환,
값이 없으면 NoSuchElementException 발생
ifPresent 값이 존재하면 지정된 Consumer를 실행하고,
값이 없으면 아무 일도 일어나지 않음
ifPresentElse 값이 존재하면 지정된 Consumer를 실행하고,
값이 없으면 아무 일도 일어나지 않음
or 값이 존재하면 값을 반환하고,
값이 없으면 기본값을 반환
orElse 값이 존재하면 값을 반환하고,
값이 없으면 Supplier에서 제공하는 값을 반환
orElseThrow 값이 존재하면 값을 반환하고,
값이 없으면 Supplier에서 생성한 예외를 발생
stream 값이 존재하면 존재하는 값만 포함하는 스트림을 반환,
값이 없으면 빈 스트림을 반환

 

 

 

Optional 사용해 보기

프로그램의 설정 값을 전달하는 코드가 있다고 가정합니다. 이럴 때에 Optional을 어떻게 사용할 수 있는지 보겠습니다. 입력 받은 설정 값으로 지속 시간을 확인하고 있습니다.

// 설정 값
Properties props = new Properties();
props.setProperty("a", "5");
props.setProperty("b", "none");
props.setProperty("c", "-1");

// 설정 값으로 지속 시간 해석
public int readDuration(Properties props, String name)

 

Optional이 아닌 방법으로 지속 시간 값을 가져오는 메서드를 작성해 봤습니다. null 체크, 값이 양수인지 확인 하는 조건이 필요해서 아래와 같이 만들어졌습니다. 그러면 다음으로 Optional을 이용한 코드를 보겠습니다.

public int readDuration(Properties props, String name){
  String value = props.getProperty(name);
  if(value != null){
    try {
      int i = Integer.parseInt(value);
      if(i > 0) return i;
    } catch(NumberFormatException e){
    }
  }
  return 0;
}

 

Optional을 사용하니 눈으로 보기 좋아졌습니다. 어떤 작업이 이루어지는지 파이프라인으로 쉽게 볼 수 있게 되었습니다.

    public int readDuration(Properties props, String name){
        return Optional.ofNullable(props.getProperty(name))
                .flatMap(s -> Optional.ofNullable(Integer.parseInt(s)))
                .filter(i -> i > 0)
                .orElse(0);
    };

 

Optional에 대한 생각

null이 발생할 수 있는 대상을 Optional로 감싸는 것은 프로그램 운영에서 예상치 못한 오류를 막는 방법이 될 수 있습니다. 그러나 null을 모두 Optional로 처리하는 것은 아니라고 생각합니다. 분명히 값이 있어야 하지만 없는 경우에는 이것을 바로 잡고 개선이 이루어져야 한다고 생각합니다. 따라서 모두 Optional을 적용하기 보다는 null이 발생하는 곳의 의미를 고려하여 사용하는 것이 바람직해 보입니다. 

반응형

'IT > Java' 카테고리의 다른 글

프로세스와 스레드 그리고 멀티 태스킹  (0) 2023.01.10
동시성(Concurrency) 과 병렬성(Parallel)  (0) 2023.01.10
Map 처리  (0) 2022.12.19
Arrays.asList() vs ArrayList()  (0) 2022.12.19
팩토리 패턴 (Factory Pattern)  (0) 2022.11.07