티스토리 뷰

Java/Java8

[Java8] Optional

메성 2021. 9. 28. 21:37
반응형
 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com

Optional은 언제 사용되는 것인가?

  • 아래 소스를 확인해보자.
public class App {
  public static void main(String[] args) {

    OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);

    Duration studyDuration = spring_boot.getProgress().getStudyDuration();
    //출력을 하면 결과는 어떻게 될까?
    System.out.println(studyDuration);

  }
}

public class OnlineClass {

    private Integer id;
    private String title;
    private boolean closed;
    public Progress progress;

    public OnlineClass(Integer id, String title, boolean closed) {
        this.id = id;
        this.title = title;
        this.closed = closed;
    }

    public Integer getId() {
        return id;
    }

    ...

    public Progress getProgress() {
        return progress;
    }

    public void setProgress(Progress progress) {
        this.progress = progress;
    }
}

public class Progress {

    private Duration studyDuration;
    private boolean finished;

    public Duration getStudyDuration() {
        return studyDuration;
    }

    public void setStudyDuration(Duration studyDuration) {
        this.studyDuration = studyDuration;
    }
}
  • main을 보면 OnlineClass를 생성 후 OnlineClass에 있는 Progress 인스턴스가 의존하고 있는 Duration을 호출하려는 로직을 가지고 있다.
  • 하지만 결과는 'NullPointerException'이 나타난다.
  • 이유는 당연하다. 현재 OnlineClass의 Progress는 Null이기 때문이다.
  • 그렇다면 이런 이슈를 방지 하기 위해 우리는 기존에 어떻게 체크를 해왔는가?

 

Optional 전 해결방안(조건문 활용)

  • public static void main(String[] args) {
      ...
        
      OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
      Progress progress = spring_boot.getProgress();
      if (progress != null) {
        System.out.println(progress.getStudyDuration());
      }
    }
  • 보시는 바와 같이 OnlineClass에서 Progress를 가져온 후 조건문을 통해 null 체크하는 방식을 사용해왔다.
  • 하지만 이런 방식은 개발자 이슈가 나타날 수 있는 경우이다. 즉, 사람이기에 null 체크하는 부분을 잊을 수 있다는 것이다.

 

Optional 전 해결방안(throw Exception)

  • public class OnlineClass {
      ...
        
      public Progress getProgress() {
        if (this.progress == null) {
          throw new IllegalStateException();
        }
        return progress;
      }
    }
  • getProgress()할 때 미리 null 체크를 하여 Exception을 던지는 방법이다.
  • 하지만 위 같은 방법을 보면 로직 처리 하는 부분에 Exception을 던지도록 하였는데, 이 방법은 그리 좋은 방법이 아니다.
    • 그 이유는, Exception을 발생하도록 하게 되면 Java에서는 Stack Trace를 찍게 된다. 즉, 해당 Exception이 발생하기 까지의 어떤 콜 스택을 거쳐서 Exception이 발생했는지에 대해 찍게 된다. 결과적으로 리소스를 사용하게 된다는 것이다.
    • 그러므로 정말 필요한 경우에믄 Exception을 사용해야된다.

 

Optional 사용 해결방안

  • public class OnlineClass {
      ...
        
      public Optional<Progress> getProgress() {
      	//progress가 null일 경우 optional.empty()를 반환
        return Optional.ofNullable(progress);
      }
    }
    
    public static void main(String[] args) {
      ...
        
      OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
      spring_boot.getProgress()
                    .ifPresent(duration -> {
                        System.out.println(duration.getStudyDuration());
                    });
    }

 

Optional 사용 시 주의사항

  • 리턴값으로만 쓰기를 권장한다.
    • 메소드 매개변수 타입으로 쓰지 말아라(매개변수 타입으로 Null이 들어올 수가 있다.)
    • public void setProgress(Optional<Progress> progress) {
        progress.ifPresent(p -> this.progress = p);
      }
      //progress 매개변수가 Null로 들어올 경우 위 로직은 다시 NullPoineException이 발생한다. 
      
      //만약 정상적으로 실행하고 싶다면 아래와 같이 한번 더 Null 체크를 해줘야 한다.
      if(progress != null) {
          progress.ifPresent(p -> this.progress = p);
      }
    • 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말아라.
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말아라.
    • 굳이 빈값을 리턴하고 싶으면 null을 리턴하지 말고 Optinal.empty()를 리턴하도록 해라
  • Primitive 용 Optional은 따로 존재한다.
    • OptionalDouble, OptionalInt, ...
    • 만약 Optional.of(10)을 하게되면 Optional 안에서 박싱, 언박싱을 진행하므로 성능상 이슈가 있을 수 있다.

 

Optional 주요 사용 메소드

  • public static void main(String[] args) {
    
      List<OnlineClass> springClasses = new ArrayList<>();
      springClasses.add(new OnlineClass(1, "spring boot", true));
      springClasses.add(new OnlineClass(2, "spring data jpa", true));
      springClasses.add(new OnlineClass(3, "spring mvc", false));
      springClasses.add(new OnlineClass(4, "spring core", false));
      springClasses.add(new OnlineClass(5, "rest api development", false));
      
    }
  • Optional of()와 ofNullable()
    • of(); -> of 메소드 안에 파라미터가 Null이 아님을 확신할 때 사용. 만약 파라미터가 Null이면 NullPointerException 발생
    • ofNullable(); -> ofNullable 메소드 안에 파라미터가 Null일수도 있을 경우 사용.
  • Optional 값 여부 확인
    • isPresent(); -> Optoinal의 값이 있는지 판단(true/false 반환)(Null이면 NPE)
    • isEmpty(); (Java 11 부터 제공) -> true/false 반환(Null이면 NPE)
      • public static void main(String[] args) {
        
          ...
        
          //isPresent
          Optional<OnlineClass> spring = springClasses.stream()
            .filter(onlineClass -> onlineClass.getTitle().startsWith("spring"))
            .findFirst();
          boolean present = spring.isPresent();
          System.out.println(present);	//true
        
          //isEmpty
          Optional<OnlineClass> jpa = springClasses.stream()
            .filter(onlineClass -> onlineClass.getTitle().startsWith("jpa"))
            .findFirst();
        	boolean present2 = jpa.isEmpty();
          System.out.println(present2);	//true
        }
  • Optional 값 호출
    • get(); -> Optional 값 가져옴(Null이면 NPE).
      • public static void main(String[] args) {
          ...
          Optional<OnlineClass> optional = springClasses.stream()
          			.filter(onlineClass -> onlineClass.getTitle().startsWith("spring"))
        		  	.findFirst();
        
          //get을 통해 Optional의 값을 호출할 수 있음
          OnlineClass onlineClass = optional.get();
          System.out.println(onlineClass.getTitle());	//spring boot
        }
      • 위 소스 처럼 get()을 통해 Optional의 값을 호출할 수 있으나, 만약 Optional이 없는 경우에는 어떻게 될까?
        • public static void main(String[] args) {
            ...
            Optional<OnlineClass> optional = springClasses.stream()
                    .filter(onlineClass -> onlineClass.getTitle().startsWith("jpa"))
                    .findFirst();
          
            //get을 통해 Optional의 값을 호출할 수 있음
            OnlineClass onlineClass = optional.get();
            System.out.println(onlineClass.getTitle());	//NoSuchElementException
          }​
           
        • 현재 'jpa'로 시작되는 타이틀은 존재하지 않는다. 이런 경우에는 NoSuchElementException으로 RuntimeException이 발생하게 된다. 결과적으로 get()이란 메소드를 사용하는 것은 크게 권장하지 않는다.
  • Optional 값 있는 경우 해당 값 사용
    • ifPresent(); -> 값이 있을 경우에만 해당 메소드 로직 수행(Null이면 NPE) 
      • 위에서 사용한 get() 메소드 대신 ifPresent()를 사용해보자.
      • public static void main(String[] args) {
          ...
          Optional<OnlineClass> optional = springClasses.stream()
                          .filter(onlineClass -> onlineClass.getTitle().startsWith("spring"))
                          .findFirst();
          optional.ifPresent(onlineClass -> System.out.println(onlineClass.getTitle()));
          //결과 : spring boot
        
          Optional<OnlineClass> optional = springClasses.stream()
                          .filter(onlineClass -> onlineClass.getTitle().startsWith("jpa"))
                          .findFirst();
          optional.ifPresent(onlineClass -> System.out.println(onlineClass.getTitle()));
          //결과 : 빈값
        }
      • 이 처럼 Optional의 값을 사용하고 싶을 때는 ifPresent를 사용하는 것이 효율적이다.
  • Optional 값 있는 경우 가져오고, 없는 경우 정의한 값 리턴
    • orElse(); -> (상수로 이미 만들어져있는 것을 사용할 때 사용하는 것을 권장)(Null이면 NPE)
      • public static void main(String [] args) {
          Optional<OnlineClass> optional = springClasses.stream()
                        .filter(onlineClass -> onlineClass.getTitle().startsWith("jpa"))
                        .findFirst();
        
          //orElse : 값이 있는 경우 해당 값을 가져오고, 없는 경우 orElse 매개변수에 있는 값을 리턴한다.
          OnlineClass onlineClass = optional.orElse(createNewClass());
          System.out.println(onlineClass.getTitle())
        }
        
        private static OnlineClass createNewClass() {
          System.out.println("creating new class");
          return new OnlineClass(6, "New Class", false);
        }
        //결과
        //creating new class
        //New Class
      • 위 처럼 값이 없는 경우에는 새롭게 생성한 클래스를 리턴하는 모습을 볼 수 있다.
      • 값이 있는 경우에도 확인해보자.
        • public static void main(String [] args) {
            Optional<OnlineClass> optional = springClasses.stream()
                          .filter(onlineClass -> onlineClass.getTitle().startsWith("spring"))
                          .findFirst();
          
            //orElse : 값이 있는 경우 해당 값을 가져오고, 없는 경우 orElse 매개변수에 있는 값을 리턴한다.
            OnlineClass onlineClass = optional.orElse(createNewClass());
            System.out.println(onlineClass.getTitle())
          }
          
          private static OnlineClass createNewClass() {
            System.out.println("creating new class");
            return new OnlineClass(6, "New Class", false);
          }
          //결과
          //creating new class
          //spring boot
          • 여기서 주의해야할 점은, 분명 값이 있는데도 불구하고 새롭게 생성하는 클래스의 로직을 타는 것을 볼 수 있다.
          • 즉, 값이 있든 없든 orElse()에 속해있는 로직은 무조건 탄다.
          • 'create new class' 출력 
  • Optional 값 있는 경우 가져오고, 없는 경우 로직 처리
    • orElseGet();  -> (동적으로 작업을 해서 만들어야할 경우 사용하는 것을 권장)(Null이면 NPE)
      • orElse()를 보면 뭔가 찝찝하다.. 값이 있다면 새롭게 생성하는 클래스를 호출하기 싫은데.. 이럴 때 orElseGet()을 사용하면 된다.
      public static void main(String [] args) {
        Optional<OnlineClass> optional = springClasses.stream()
                      .filter(onlineClass -> onlineClass.getTitle().startsWith("spring"))
                      .findFirst();
      
        //orElseGet : 값이 있는 경우 해당 값을 가져오고, 없는 경우에만 orElseGet 매개변수에 있는 값을 리턴한다.
        OnlineClass onlineClass = optional.orElseGet(createNewClass());
        System.out.println(onlineClass.getTitle())
      }
      
      private static OnlineClass createNewClass() {
        System.out.println("creating new class");
        return new OnlineClass(6, "New Class", false);
      }
      //결과
      //spring boot
  • Optional 값 있는 경우 가져오고 없는 경우 예외 처리
    • orElseThrow();
      • 또한, 클래스를 새롭게 생성하지 않고 Exception을 날려줄 수도 있다.(Null이면 NPE)
      public static void main(String [] args) {
        Optional<OnlineClass> optional = springClasses.stream()
                      .filter(onlineClass -> onlineClass.getTitle().startsWith("jpa"))
                      .findFirst();
      
        //람다식
        OnlineClass onlineClass = optional.orElseThrow(() -> {
          return new IllegalArgumentException();
        });
      
        //메소드 레퍼런스
         OnlineClass onlineClass = optional.orElseThrow(IllegalArgumentException::new)
           System.out.println(onlineClass.getTitle())
      }
      
      private static OnlineClass createNewClass() {
        System.out.println("creating new class");
        return new OnlineClass(6, "New Class", false);
      }
      
       
  • 결과적으로  isPresent() 와 get()은 사용하지 않는 것이 좋다.
    • isPresent(); 와 get(); 대신 ifPresent, orElseGet, orElseThrow등을 사용하라
      • 이유는 get(); isPresent()를 통해서  로직을 구성할 시  코드가 길어지는 것을 볼 수 있다.
      • public static void main(String [] args) {
          Optional<OnlineClass> optional = springClasses.stream()
                        .filter(onlineClass -> onlineClass.getTitle().startsWith("spring"))
                        .findFirst();
        
        //isPresent()와 get() 사용
        if(optional.isPresent()) {
          OnlineClass onlineClass = optional.get();
        } else {
          OnlineClass onlineClass = createNewClass();
        }
        //orElseGet() 사용
        OnlineClass onlineClass = optional.orElseGet(createNewClass());
    • Java9에서는 ifPresentOrElse();를 통해 ifPresent와 orElseGet을 동시에 사용할 수 있다.
  • Optional에 있는 값 필터링
    • filter();
      • Optional의 filter() 메소드를 통해 Optional의 조건 값을 줄 수 있다.
      public static void main(String [] args) {
        ...
        springClasses.add(new OnlineClass(5, "rest api development", false));
      
        Optional<OnlineClass> optional = springClasses.stream()
          .filter(onlineClass -> onlineClass.getTitle().startsWith("rest"))
          .findFirst();
      
        //filter를 통해 optional 안에 있는 값의 조건을 줄 수 있다.
        Optional<OnlineClass> optional1 = optional.filter(onlineClass -> onlineClass.isClosed());
        System.out.println(optional1.isEmpty());
      }
      
      //결과 : true
  • Optional에 있는 값 변환
    • map();
      • Optional의 mpa() 메소드를 통해 Optional의 리턴값을 변환할 수 있다.
      public static void main(String [] args) {
        ...
        springClasses.add(new OnlineClass(5, "rest api development", false));
      
        Optional<OnlineClass> optional = springClasses.stream()
          .filter(onlineClass -> onlineClass.getTitle().startsWith("rest"))
          .findFirst();
      
        //map을 통해 getId()를 호출할 시 Optional<Integer> 형태로 리턴이 된다.
        Optional<Integer> integer = optional.map(onlineClass -> onlineClass.getId());
        System.out.println(integer.isPresent());
      }
      
      //결과 : true
    • flatMap();
      • map으로 추출 시 Optional<>이 아닌 Opitonal을 한번 더 감싼 Optional<Optional<>>이라면 flatMap을 통해 한번에 Optional<>로 변경할 수 있다.
      public class OnlineClass {
        public Optional<Progress> getProgress() {
          return Optional.ofNullable(progress);
        }
      }
      
      public static void main(String [] args) {
        ...
        springClasses.add(new OnlineClass(5, "rest api development", false));
      
        Optional<OnlineClass> optional = springClasses.stream()
          .filter(onlineClass -> onlineClass.getTitle().startsWith("rest"))
          .findFirst();
      
        //map으로 표현하면 아래와 같다.
        Optional<Optional<Progress>> progress1 = optional.map(onlineClass -> onlineClass.getProgress());
        Optional<Progress> progress2 = progress1.orElse(Optional.empty());
      
        //flatMap을 사용할 시 쌍으로 되어있는 Optional을 풀 수 있다.
        Optional<Progress> progress = optional.flatMap(onlineClass -> onlineClass.getProgress());
      }

      • Stream의 flatMap()을 사용하는 경우는 input은 하나인데 output이 여러개일 경우 사용하는 것으로서 Optional과는 다르다.
        • Ex. input은 하나인데, list1과 list2가 output 되는 경우, flatMap을 사용하여 list1과 list2를 하나의 list로 변경
반응형

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

[Java8] Stream API  (0) 2021.08.14
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함