Dev/Java

Optional 제대로 알고 쓰는건가요?

아콩2 2022. 7. 10. 19:17
반응형

To. me

Optional 제대로 알고 쓰는건가요?

from java

// userRepository.findById()의 리턴 타입: Optional<User> 
User user = useRepository.findById(id).getorElse(null);
if(user == null){
    // user가 null 인경우
} else {
   // user가 null 이 아닌경우
}

이상한 점 느끼셨나요 ?

Optional의 존재 이유를 정확하게 알고 계신 분이 위의 코드를 봤다면 대충 저런 표정을 짓고 계시지 않으실까 싶습니다^^.
놀랍게도 제가 진짜 프로젝트에 적용했던 코드의 예시랍니다,,ㅎㅎ

Java 8 Optional

  • 자바 옵셔널은 자바 8에서 최초로 도입 되었습니다.
  • 예상하지 못한 NPE(NullPointerException) 예외를 제공되는 메소드로 간단히 회피 할 수 있다
  • 복잡한 조건문 없이도 널(null) 값으로 인해 발생하는 예외를 처리 할 수 있다

엽떡과 허니콤보 클래스가 있습니다.
YeopgiTteokbokk 클래스는 엽떡 한개를 표현하고 HoneyCombo는 함께 시킨 엽떡의 정보, 가격을 표현 합니다.

Class 
YeopgiTteokbokk {
    private String spicyLevel;
    private List<String> sideMenuList; 
}

Class HoneyCombo {
    private YeopgiTteokbokk yt;
    private double price;
}

허니콤보와 함께 주문한 엽떡의 매운 맛 단계를 조회 하고 싶습니다. 하지만 엽떡을 함께 주문 하지 않더라면
아래의 코드는 NPE 예외가 발생합니다.

void main(){
   HoneyCombo hc = hcService.getHoneyCombo();
   YeopgiTteokbokk yt = hc.getYt();
   System.out.println("허니 콤보와 함께 먹을 엽떡의 매운맛 단계는"+yt.getSpicyLevel()+"입니다";
}

NPE 예외를 처리 하기 위해 if문을 이용했습니다.

void main(){
   HoneyCombo hc = hcService.getHoneyCombo();
   YeopgiTteokbokk yt = hc.getYt();
   if(yt!=null){
       System.out.println("허니 콤보와 함께 먹을 엽떡의 매운맛 단계는"+yt.getSpicyLevel()+"입니다";
    } else {
       System.out.println("허니 콤보를 먹는데 엽떡을 주문하지 않았네요,,,,");    
    }

}

클라이언트 : 엽떡을 주문했는데 매운맛 단계가 누락 되거나 엽떡을 주문하지 않은 경우에는 매운맛 단계 대신 "-"를 출력해주세요
클라이언트의 새로운 요구사항을 구현하기 위해 아래처럼 코드를 작성 하였습니다.
만약 클래스의 필드의 수, 클라이언트의 요청 수가 늘어날 수록 if 문을 남발하는 지저분한 코드를 작성 하게 됩니다.

void main(){
   HoneyCombo hc = hcService.getHoneyCombo();
   YeopgiTteokbokk yt = hc.getYt();
   Strign spicyStep = "-";
   if(yt!=null && yt.getSpicyStep()!=null){
      spicyStep = yt.spicyStep();
    } 
   System.out.println(spicyStep);

Optional을 적용해보겠습니다.

void main(){
     HoneyCombo hc = hcService.getHoneyCombo();
     Strign spicyStep =  Optional.of(hc)
                                     .map(HoneyCombo :: getYeopgiTteokbokk)
                                    .map(YeopgiTteokbokk :: getSpicyStep)
                                    .orElse("-");
     System.out.println(spicyStep);
}

가독성이 좀 더 좋아지지 않았나요?
Optional에서 제공하는 메서드에 대해 알아보겠습니다!


Optional에서 제공하는 메서드

1. Optional 객체 생성

1.1 Optional.of()

value가 null인 경우 NPE 예외가 발생합니다. 반드시 값이 있어야 하는 객체인 경우 해당에만 해당 메서드를 사용합니다.

value가 null 인경우에 NPE가 발생하는 이유
Optional<Stirng> opt = Optional.of("hello");

1.2 Optional.ofNullable

value가 null인 경우 비어있는 Optional를 반환합니다. 값이 null 일 수도 있을때 사용합니다.

Optional<Stirng> opt = Optional.ofNullable(null);
value가 null인 경우 빈 객체를 반환하는 이유

1.3 Optional.empty()

비어있는 옵셔널 객체를 생성합니다. 조건에 따라 분기를 태워야하고 반환할 값이 없는 경우에도 사용됩니다.

Optional<String> emptyOpt = Optional.empty();

2. Optional 중간처리

옵셔널 객체를 생성한 후 사용 가능한 메서드입니다. 해당 메서드들은 다시 옵셔널을 반환하므로 메서드 체이닝을 통해 원하는 로직을 반복 삽입 할 수 있습니다.

2.1 filter

predicate 값이 참이면 해당 필터를 통과시키고 거짓이면 통과 되지 않습니다.

Optional.of("abc").filter(val -> "abc",equlas(val)).orElse("not matched");  
조건이 true가 아니라면 빈 객체를 반환합니다.

2.2 map

mapper 함수를 통해 입력값을 다른 값으로 변환하는 메서드 입니다.

YeopgiTteokbokk yt = getYt(); 
String spicyStep = Optional.of(yt).map(YeopgiTteokbokk :: getSpicyStep).orElse("-");
map() 의 내부

2.3. flatMap

mapper 함수를 통해 입력값을 다른 값으로 변환하는 메서드 입니다. map()메서드와 다른점은 메서드 시그니처의 매개변수 입니다.
map()에서는 제너릭으로 U를 정의 했지만 flatMap() 에서는 제너릭으로 Optional(U)를 정의 하였습니다.
즉, flatMap() 메서드가 반환해야하는 값은 Optional 입니다.

String result = Optional.of("result")
         .flatMap((val) -> Optional.of("good"))
         .get();
 // result : good         
map()&amp;nbsp; / flatMap() 의 차이

3. Optional 종단 처리

옵셔널 객체의 메서드 체이닝을 끝냅니다.

3.1 ifPresent

최종적으로 연산을 끝낸 후 값이 비어있지 않다면 입력값으로 주어집니다.

Optional.of("hihi").ifPresent( value -> {
    // 수행할 작업
});

Optional.ofNullable(null).ifPresent( value -> {
    // 이부분에 작성한 코드 동작하지 않음
});

3.2 isPresent

최종적으로 연산을 끝난 후 객체가 존재하는지 여부를 판별합니다.

Optional.ofNullable("test").isPresent(); // true
Optional.ofNullable("test").filter((val) -> "result".eqauls(val)).isPresent(); // false

3.3 get

최종적으로 연산을 끝낸 후 객체를 꺼냅니다. 단, 비어있는 옵셔널 객체였다면 예외가 발생합니다

Optional.of("hihi").get(); // hihi
Optional.ofNullable(null).get() // NoSuchElementException 예외 발생
빈 객체인 경우 NoSuchElementException 예외가 발생하는 이유

3.4 orElse

최정적으로 연산을 끝난 후에도 옵셔널 객체가 비어있다면 기본값으로 제공할 객체를 지정합니다.

String result = Optional.ofNullable(null).orElse("default");
System.out.println(result); // default;
null&amp;nbsp; 값인 경우 대체 값을 리턴하는 이유

3.5 orElseGet

최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 기본값으로 제공할 공급자 함수를 지정합니다.

String result = Optional.ofNullable("input").filter("test"::equals).orElseGet(() -> "default");
System.out.println(result); // default;
null 값인 경우 대체 값을 리턴하는 이유

3.6 orElseThrow

최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 예외 공급자 함수를 통해 예외를 발생 시킵니다.
매개변수가 필요 없는 예외 메서드가 자바 10에서 추가 되었습니다!

// 예제 (자바 8)
Optional.ofNullable(something).orElseThrow(NoSuchElementException::new);
// 예제 (자바 10)
Optional.ofNullable(something).orElseThrow();

마무리

옵셔널의 개념과 옵셔널 내장 메서드에 대해 알아보았습니다. 어떠신가요? 이 글을 끝까지 읽으셨더라면 초반에 언급한 저의 실수가 얼마나 얼토당토 않은
실수인지 느껴지시나요 ? 🤦🏻‍♀️ 개인적으로는 많은 반성을 하게 되는 포스팅 이였습니다. 오늘 이후로는 코드 한 줄을 작성 하더라도 왜 이렇게 해야 하는건지
더 나은 방법은 없는건지 꼼꼼하게 깐깐하게 작성할거에요,,,진짜루,,

옵셔널은 개념 및 메서드의 역할을 알고 있는 것도 중요하지만 적재적소에 사용해야 한다고 합니다.
다음 포스팅에는 옵서녈을 사용할 적절한 타이밍 !에 대해 다루어 보겠습니다.

그럼 이번 포스팅 여기서 마무리 하겠습니다. 끝까지 읽어주셔서 감사합니다 🌈

참고

https://jdm.kr/blog/234

반응형