Home Optional
Post
Cancel

Optional

Optional

Optional을 찾아보게 된 이유

Repository.findById()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
List<Long> allByIdx = registryRepository.findAllByIdx();
List<Long> temp = new LinkedList<>();
temp.addAll(allByIdx);
        
List<Optional<Registry>> result = new ArrayList<>();

if (temp.isEmpty()) {
    Registry registry = new Registry("admin", "admin","admin");
    result.add(Optional.ofNullable(registry));
}

if (temp.toArray().length > 10) {
    for (int i = 0; i < 10; i++) {
        result.add(registryRepository.findById(temp.get(i)));
    }
} else {
    for (int i = 0; i < temp.toArray().length; i++) {
        result.add(registryRepository.findById(temp.get(i)));
    }
}

프로젝트 기능 수정을 하기 위해 작성했던 코드 중 일부 생략된 코드이다.

내가 의문이 들었던 것은 registryRepository.findById를 작성할 때 Optional로 줘야 하는 부분이다.

그냥 List<Registry> result로 하면 안되나? 싶었는데 Optional을 생략하면 에러가 떴고 IDE에서 Optional로 바꿔주었다.

그래서 그 이유를 찾아보았고 이론에 대해서도 정리를 해보게 되었다.




JPA

해당 Repository가 상속받고 있는 JpaRepository interface에 들어가봤다.

image

JpaRepository 인터페이스에서 find와 관련된 메서드는 CrudRepository를 참고하라고 한다.



CrudRepository interface에 findById 메소드를 확인해보니 return값이 Optional 타입으로 고정되어있다.

image


원인은 바로 CrudRepository interface의 return값이 Optional로 주어져서였다.



이 부분은 아래 링크에 자세히 정리해서 넘어간다.

더 알아보기 👉🏻 Spring Data JPA




Optional 이란?

Java8에서는 Optional<T> 클래스를 사용해 NPE(NullPointerException)를 방지할 수 있도록 도와준다.

Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다.

Optional 클래스는 아래와 같은 value에 값을 저장하기 때문에 값이 null이더라도 바로 NPE가 발생하지 않으며,

클래스이기 때문에 각종 메소드를 제공해준다.

image




Optional 적용해보기

Optional.empty() : 값이 null인 경우

Optional은 Wrapper 클래스이기 때문에 값이 없을 수도 있는데, 이때는 Optional.empty()로 생성할 수 있다.

1
2
3
4
Optional<String> optional = Optional.empty();

System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false

🐣 isPresent 메서드로 현재 Optional이 보유한 값이 null인지 아닌지를 확인할 수 있다.


위에서 보았듯이 Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성해서 가지고 있다.

이러한 이유로 빈 객체를 여러 번 생성해줘야 하는 경우에도 1개의 EMPTY 객체를 공유함으로써 메모리를 절약하고 있다.




Optional.of() : null 값을 허용하지 않음

만약 어떤 데이터가 절대 null이 아니라면 Optional.of()로 생성할 수 있다.

만약 Optional.of()로 null을 저장하려고 하면 NullPointerException이 발생한다.

1
2
// Optional의 value는 절대 null이 아니다.
Optional<String> optional = Optional.of("COCO");




Optional.ofNullbale() : null 값을 허용

만약 어떤 데이터가 null이 올 수도 있고 아닐 수도 있는 경우에는 Optional.ofNullbale()로 생성할 수 있다.

그리고 이후에 orElse 또는 orElseGet 메소드를 이용해서 값이 없는 경우라도 안전하게 값을 가져올 수 있다.

1
2
3
// Optional의 value는 값이 있을 수도 있고 null 일 수도 있다.
Optional<String> optional = Optional.ofNullable(getName());
String name = optional.orElse("anonymous"); // 값이 없다면 "anonymous" 를 리턴




Optional에서 값 가져오기

Optional에서는 값을 가져올 때 자주 사용되는 메서드 orElseGet, orElse 가 있다.

null 값 체크를 할 수 있음과 동시에 null 값일 경우 간단한 코드로 처리할 수 있어

코드의 가독성이 좋아지고 코드 생산성이 올라간다는 장점이 있다.


orElseGet은 Optional이 가지고 있는 값이 null일 경우에만 orElseGet에 주어진 함수를 실행하지만

orElsenull값 유무와 상관없이 사용하게 되어있다.



orElse와 orElseGet

  • orElse : 파라미터로 값을 받는다.
  • orElseGet : 파라미터로 함수형 인터페이스(함수)를 받는다.
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
32
33
34
35
import java.util.Optional;

public class OptionalPrac {
    private static final String emptyStr = "Empty";

    public void useOrElse() {
        String result = Optional.ofNullable(emptyStr)
                .orElse(getbeep());

        System.out.println(result);
    }

    public void useOrElseGet() {
        String result = Optional.ofNullable(emptyStr)
                .orElseGet(this::getbeep);

        System.out.println(result);
    }

    public String getbeep() {
        System.out.println("beep !");
        return "sound";
    }

    public static void main(String[] args) {
        OptionalPrac test = new OptionalPrac();
        System.out.println(" < orElse > ");
        test.useOrElse();
        System.out.println();
        System.out.println(" = = = = = = = = = = = = = ");
        System.out.println();
        System.out.println(" < orElseGet > ");
        test.useOrElseGet();
    }
}

출력

1
2
3
4
5
6
7
8
 < orElse > 
beep !
Empty

 = = = = = = = = = = = = = 

 < orElseGet > 
Empty



출력 순서 방식

OrElse

  1. Optional.ofNullable로 “EMPTY”를 갖는 Optional 객체 생성
  2. getbeep()가 실행되어 반환값을 orElse 파라미터로 전달
  3. orElse가 호출되고 “EMPTY”가 null이 아니므로 “EMPTY”를 그대로 가진다.

OrElseGet

  1. Optional.ofNullable로 “EMPTY”를 갖는 Optional 객체 생성
  2. getbeep() 함수 자체를 orElseGet 파라미터로 전달
  3. orElseGet이 호출되고 “EMPTY”가 null이 아니므로 “EMPTY”를 그대로 가지며 getbeep()이 호출되지 않는다.




orElseGet에서는 파라미터로 넘어간 값인 getbeep 함수가 null이 아니므로 .get에 의해 함수가 호출되지 않는다.



만약 Optional의 값으로 null이 있다면,

다음과 같은 흐름에 의해 orElseGet의 파라미터로 넘어온 getbeep()이 실행될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public void useOrElseGet() {
        Object result = Optional.ofNullable(null)
                .orElseGet(this::getbeep);

        System.out.println(result);
    }

    public String getbeep() {
        System.out.println("beep !");
        return "sound";
    }

    public static void main(String[] args) {
        OptionalPrac test = new OptionalPrac();
        System.out.println(" < orElseGet > ");
        test.useOrElseGet();
    }
1
2
3
 < orElseGet > 
beep !
sound



orElse와 orElseGet의 차이점 및 사용법 정리

orElse

  • 파라미터로 값을 필요로한다.
  • 값이 미리 존재하는 경우에 사용한다.

orElseGet

  • 파라미터로 함수(함수형 인터페이스)를 필요로 한다.
  • 값이 미리 존재하지 않는 거의 대부분의 경우에 orElseGet을 사용하면 된다.




정리

Optional은 null 또는 값을 감싸서 NPE(NullPointerException)로부터 부담을 줄이기 위해 등장한 Wrapper 클래스이다.

Optional은 값을 Wrapping하고 다시 풀고, null 일 경우에는 대체하는 함수를 호출하는 등의 오버헤드가 있으므로
잘못 사용하면 시스템 성능이 저하된다.

그렇기 때문에 메소드의 반환 값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 좋다.

즉, Optional은 메소드의 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용되어야 한다.

또한 Optional은 파라미터로 넘어가는 등이 아니라 반환 타입으로써 제한적으로 사용되도록 설계되었다.




reference
[JPA] 리턴타입이 옵셔널(Optional)인 이유는?
[Java] Optional이란? Optional 개념 및 사용법 - (1/2)
[Java] Java Optional (자바 옵셔널) 정리, 예제모음

This post is licensed under CC BY 4.0 by the author.