java, Optional 활용하기

Optional?

  • Optional는 null을 효과적으로 관리하기 위한 인터페이스이다. if(obj==null) 이란 형태를 뛰어넘어 유연한 코드를 작성하도록 도와준다.
  • 그러므로 아래와 같이 단순한 null 체크 용도로 사용한다면 Optional의 경우 큰 의미를 가지지 못한다. 오히려 코드만 늘어난다. 기존의 두 줄을 사용한 코드가 Optional을 사용하는 순간 3줄로 늘어난다.
if(optional.isPresent()){
    Something something = optional.get();
    something.doSomething();
}

if(something != null){
    something.doSomething();
} 
  • Optional은 그것이 제공하는 여러 메서드가 존재한다. 제공하는 메서드를 활용하여 더 좋은 코드를 작성할 수 있다.

Optional의 기본적인 사용 패턴 - orElse

  • Optional의 기본적인 사용은 orElse와 함께 한다. null일 경우 적절한 값을 전달하거나 NPE(NullPointerException)가 아닌 다른 예외로 변환하여 전달한다.
Optional<String> op = Optional.empty();
String result = op.orElse("빈 값!");
System.out.println(result); // "빈 값!"
  • orElseGet은 함수형 인터페이스를 전달한다. 지연로딩으로 최적화 가능하기 때문에, 해당 값을 생성하는데 많은 리소스를 사용할 경우 사용을 권장한다.
String result = op.orElseGet(longTimeTakenJob()); // Supplier<String>
  • 자바의 NPE는 구체적인 메시지가 없어서 로그를 분석할 때 즉각적인 이해가 어렵다. 예외 객체 선택하고 적절한 메시지를 작성하여 이해하기 쉽도록 한다.
MyRequest request;
Optional<String> stringOp = getResult(request);
String value = stringOp.orElseThrow(() -> new IllegalArgumentException("요청에 대한 결과 값이 존재하지 않습니다. 요청 값 : " + request.toString()));

스트림처럼 사용하기 - map과 filter

  • Optional을 컬렉션처럼 생각하면 값이 하나만 있거나 혹은 값이 없는 스트림처럼 이해할 수 있다. 스트림의 중간 연산은 스트림에 값이 없더라도 아무런 문제 없이 동작한다.
  • 아래는 map을 사용하여 요청에 대한 응답값을 SUCCESS 혹은 ERROR란 응답값으로 반환한다. 만약 값이 없을 경우 NO_RESPONSE로 응답한다.
Optional<String> op = getResponse();
Status result = op
        .map(Status::resolveResponse)
        .orElse(Status.NO_RESPONSE);

enum Status{
    NO_RESPONSE, 
    ERROR, SUCCESS;

    static Status resolveResponse(String s){
        if(s.equals("success")) return SUCCESS;
        return ERROR;
    }
}

Optional을 필드로 사용

  • 객체를 구현할 때, 필드의 nullable을 명시하는 것은 중요하다. 해당 객체를 사용할 때 null-safe를 보장하지 못하면 클라이언트 개발자는 보수적으로 코드를 작성할 수밖에 없기 때문이다. 이로 인해 아래와 같이 if가 반복되어 사용되는 장황한 코드가 될 수도 있다.
MyObject o = getMyObject();
String name = o.getName(); 
if(name!=null){
    name = generateName(o, someArgs);
}
  • 아래와 같이 null이 가능한 필드는 Optional로 감쌀 수 있다.
class MyObject{
    Optional<String> nameOp;
}
  • 다만 위의 방식은 직렬화와 관련한 문제로 권장하지 않는다.
  • 절충한으로 getter에서 Optional로 감쌀 수 있다.
public Optional<String> getName(){
    return Optional.ofNullable(name);
}

2023년 7월 30일 내용 추가 및 수정