Optional 클래스는 자바8 부터 도입된 클래스로 주요 목적은 null 값을 다루는 방식을 개선하고 명시적으로 처리할 수 있도록 하는 것입니다. 즉 Optional을 사용하면 값이 없을 수 있는 상황에서 코드의 안정성을 높일 수 있으며 코드를 명확하게 작성할 수 있습니다.
Optional은 값이 없을 수 있는 상황에서 코드의 안정성을 높일수 있는 유용한 클래스이지만 안티패턴이 존재합니다. 제가 기억하고 있는 Optional 안티패턴은 다음과 같습니다.
Optional은 Serializable를 구현하지 않기 때문에 필드로 사용하면 안된다.
isPresent()-get()으로 조건을 분기하지 않고 한줄로 줄여쓰자
Optional을 생성자나 메서드 인자로 사용하면, 호출될때마다 Optional을 생성해야한다. 즉 비싼 Optional을 사용하지않고 메서드를 호출하는 부분에서 null 체크를 하는것이 좋다.
무엇보다 파라미터로 사용하는 것은 Optional의 성격과 맞지 않다고 생각한다.
해당 포스팅의 주제입니다.
Optional의 안티패턴을 달달 외울 필요는 없지만 무엇이 있는지 대충은 알고 있어야 찾아보기 편한것 같습니다. 저는 Optional을 사용할때 주로 아래 내용을 참고합니다.
https://homoefficio.github.io/2019/10/03/Java-Optional-바르게-쓰기/
OAuth 2.0 인증 관련 작업을 진행하면서 다음과 같은 요구사항이 있었습니다.
위 내용을 코드로 풀어내면 아래와 같습니다.
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
		OAuth2User oAuth2User = super.loadUser(userRequest);		
		
		// 생략
		// handleUserAuthentication(oAuth2User)
}
private User handleUserAuthentication(OAuth2User oAuth2User) {
    Optional<User> optionalUser = userRepository.findByEmail(oAuth2User.getAttribute("email"));
    User newUser = optionalUser.orElseGet(() -> {
        User user = toUser(oAuth2User);
        return userRepository.save(user);
    });
    httpSession.setAttribute(newUser.getName(), new SessionUser(newUser));
	  return newUser;
}
handleUserAuthentication() 메서드는 email로 사용자정보를 조회한뒤 Optional을 반환합니다.
isPresent()와 get()을 사용한 코드
private User handleUserAuthentication(OAuth2User oAuth2User) {
    Optional<User> optionalUser = userRepository.findByEmail(oAuth2User.getAttribute("email"));
    if (optionalUser.isPresent()) {
        User existingUser = optionalUser.get();
        httpSession.setAttribute(existingUser.getName(), new SessionUser(existingUser));
        return existingUser;
    } else {
        User newUser = toUser(oAuth2User);
        newUser = userRepository.save(newUser);
        httpSession.setAttribute(newUser.getName(), new SessionUser(newUser));
        return newUser;
    }
}
orElseGet()을 사용한 코드
private User handleUserAuthentication(OAuth2User oAuth2User) {
    Optional<User> optionalUser = userRepository.findByEmail(oAuth2User.getAttribute("email"));
    User newUser = optionalUser.orElseGet(() -> {
        User user = toUser(oAuth2User);
        return userRepository.save(user);
    });
    httpSession.setAttribute(newUser.getName(), new SessionUser(newUser));
    return newUser;
}
isPresent() - get() 대신 orElseGet()을 사용해야 하는 이유는 다음과 같습니다.
Optional을 사용하여 더 간결하게 작성되었습니다. orElseGet() 메서드를 사용하면 값을 추출하고 대체 값을 생성하는 작업이 단일 줄에 처리됩니다. 반면 isPresent()와 get()을 사용한 대안 코드는 불필요하게 중복 코드가 많아 가독성이 떨어집니다.Optional을 사용하여 한 번의 optionalUser.orElseGet() 호출로 작업을 수행합니다. isPresent()와 get()을 사용한 대안 코드는 값이 존재하는 경우에도 두 번의 optionalUser.isPresent() 및 optionalUser.get() 호출이 필요하며, 이로 인해 성능이 약간 저하될 수 있습니다.Optional을 사용한 코드는 람다 표현식을 통해 대체 값을 생성할 수 있으므로, 값이 없는 경우에만 필요한 계산을 수행할 수 있습니다. 이것은 성능을 향상시키는 동시에 필요한 경우에만 추가 작업을 수행할 수 있도록 합니다.Optional을 사용하여 null을 다루는 위험을 피합니다. isPresent()와 get()을 사용한 대안 코드는 null 체크를 직접 수행하므로 오류가 발생할 가능성이 있습니다.orElse()와 orElseGet() 메서드는 Optional에서 값이 없을 때 대체 값을 제공하는 데 사용됩니다. orElse() 메서드는 대체 값이 이미 생성되어 있을 수 있고, orElseGet()은 대체 값을 생성하기 위한 Supplier 함수를 받습니다. 
public final class Optional<T> {
		private final T value;
		
		public T orElse(T other) {
		    return value != null ? value : other;
		}
		
		public T orElseGet(Supplier<? extends T> supplier) {
		    return value != null ? value : supplier.get();
		}
}
orElse() 메서드와 orElseGet() 메서드 모두 Optional에 값이 존재하는지 여부를 확인합니다. 차이는 파라미터에 있습니다.
orElse():
orElse() 메서드는 Optional에 값이 존재하는지 여부를 확인합니다. 이를 위해 value 필드를 검사하며, value가 null이 아닌 경우 (즉, Optional이 값이 있는 경우) value를 반환합니다.orElse() 메서드는 대체 값인 other를 반환합니다. 이것은 Optional이 비어 있는 경우에 대체 값을 반환하는 데 사용됩니다.orElseGet()
orElseGet() 메서드는 Optional에 값이 존재하는지 여부를 확인합니다. 이를 위해 value 필드를 검사하며, value가 null이 아닌 경우 (즉, Optional이 값이 있는 경우) value를 반환합니다.orElseGet() 메서드는 대체 값을 생성하기 위한 Supplier 함수인 supplier를 호출합니다. supplier.get()를 호출하여 대체 값을 생성하고 반환합니다.orElseGet() 메서드를 사용하면 값이 없을 때만 supplier 함수가 호출되므로, 대체 값의 생성을 게으르게(lazily) 수행할 수 있습니다. 이것은 성능 향상을 가져올 수 있습니다.다음은 orElse()를 사용한 코드와 orElseGet()를 사용한 코드를 비교한 예입니다.
orElse()를 사용한 코드:
private User handleUserAuthentication(OAuth2User oAuth2User) {
    Optional<User> optionalUser = userRepository.findByEmail(oAuth2User.getAttribute("email"));
    User newUser = optionalUser.orElse(toUser(oAuth2User));
    httpSession.setAttribute(newUser.getName(), new SessionUser(newUser));
    return newUser;
}
orElseGet()을 사용한 코드:
private User handleUserAuthentication(OAuth2User oAuth2User) {
    Optional<User> optionalUser = userRepository.findByEmail(oAuth2User.getAttribute("email"));
    User newUser = optionalUser.orElseGet(() -> {
        User user = toUser(oAuth2User);
        return userRepository.save(user);
    });
    httpSession.setAttribute(newUser.getName(), new SessionUser(newUser));
    return newUser;
}
orElseGet(supplier)의 파라미터로 람다식, 즉 동작이 파라미터화 되어 전달된다. 따라서 값이 없을 때만 supplier 함수가 호출되므로 값을 게으르게 수행할 수 있습니다. 즉 성능향상을 가져올 수 있습니다.