241129 TIL - Java Optional 주의해야 할 부분

J_log·2024년 11월 29일
0
post-thumbnail

1. 필드변수로 사용

@Getter
public class User {
	private Optional<Address> address; // Optional 사용 금지
}

이 클래스가 DTO로 사용된다면 Optional이 문제가 된다. 스프링에서는 데이터의 응답으로 DTO <-> JSON 직렬화, 역직렬화 방식을 사용하는데, 이 때 Optional은 직렬화, 역질렬화 시에 예상치 못 한 문제를 발생시킬 수 있다.
Entity역시 만찬가지로 Optional을 사용하는 경우 DB에 데이터 맵핑이 되지 않을 수 있다.

2. List, Set 등과 같은 컬렉션에 사용

Optional은 단일 값에 초점이 맞춰져 있다. Optional객체를 리스트로 만든다면 불필요한 코드를 더 작성해야하며, 성능도 떨어진다

예시 )

/****************************/
/**   List에 Optional 사용   **/
/****************************/
List<Optional<String>> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    if (i % 2 == 0) {
        list.add(Optional.of("Value" + i)); // 값이 있는 Optional
    } else {
        list.add(Optional.empty()); // 값이 없는 Optional
    }
}

// 반복 작업에서 Optional 처리
long startTime = System.nanoTime();
long count = list.stream()
        .filter(Optional::isPresent) // Optional에 값이 있는 경우만 필터링
        .map(Optional::get)         // 값을 꺼냄
        .filter(value -> value.startsWith("Value")) // 조건 필터링
        .count();                   // 값 개수 계산
long endTime = System.nanoTime();

System.out.println("Count: " + count);
System.out.println("Execution time: " + (endTime - startTime) / 1_000_000 + " ms");

/**** 실행 결과 ****/
Count: 500000
Execution time: 200 ms

/*************************/
/**   일반적인 List 사용   **/
/*************************/
List<String> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    if (i % 2 == 0) {
        list.add("Value" + i); // 값이 있는 항목
    } else {
        list.add(null); // 값이 없는 항목
    }
}

// null 체크를 직접 수행
long startTime = System.nanoTime();
long count = list.stream()
        .filter(value -> value != null)          // null 체크
        .filter(value -> value.startsWith("Value")) // 조건 필터링
        .count();                                // 값 개수 계산
long endTime = System.nanoTime();

System.out.println("Count: " + count);
System.out.println("Execution time: " + (endTime - startTime) / 1_000_000 + " ms");

/**** 실행 결과 ****/
Count: 500000
Execution time: 50 ms

일반적인 List 사용이 4배나 더 빠른 것을 체크할 수 있다. 만약 사이즈가 더 커진다면 차이 또한 더욱 커질 것이다.

3. orElse() 보다는 orElseGet() 사용하기

String name1 = Optional.ofNullable(user.getName()).orElse("이름없음");
String name2 = Optional.ofNullable(user.getName()).orElseGet(() -> "이름없음");

orElse() 메서드는 값이 없으면 반환할 기본값을 의미한다. 즉, user.getName()에 값이 없다면 "이름없음"이 반환된다. 하지만 orElse()는 user.getName() 값이 존재하더라도 new String("이름없음")으로 객체를 미리 생성해둔다.

반면에 orElseGet()의 경우는 user.getName()이 존재하지 않는 경우에만 해당 람다식이 실행된다. 즉, orElse()는 값이 존재하는데도 불구하고 객체를 미리 할당하기 때문에 메모리 사용 측면에서 orElseGet()를 사용하는 것을 권장한다.

4. 매개변수에 사용

메서드 매개변수에 Optional을 사용하면 호출하는 쪽에서 불필요한 코드가 증가하고, API가 불명확해진다.

public void process(Optional<String> name) {
    name.ifPresent(System.out::println);
}

첫 번째 문제는 매개변수를 넘길 때 매번 Optional로 감싸줘야 하는 번거로움이 생긴다.

String name = "test";
process(Optional.ofNullable(name));

다음으로는 매개변수가 값을 제공해야 하는지, 없어도 되는지 모호해진다.

5. Optional을 무조건 반환

값이 항상 존재하거나 절대 null이 아니라고 보장되는 경우에도 무조건 Optional로 반환하는 것은 불필요하다.

public Optional<String> getName() {
    return Optional.of("유재석"); // 값이 항상 존재함에도 Optional로 반환
}

6. 중첩된 Optional 구조

Optional<Optional<String>> nested = Optional.of(Optional.of("유재석"));
System.out.println(nested.get().get());

이렇게 중첩된 Optional은 체이닝을 복잡하고 이해하기 어렵게 만든다. 만약 중첩 Optional을 만나게 된다면 가능한 flatMap()을 이용하여 평탄화 시킨 후 사용하는 것이 좋다.

0개의 댓글