ClassCastException - 왜 Optional 을 사용할 수 없을까?
왜 User 는 Optional 로 casting 될 수 없는걸까. 그냥 해주면 안되는걸까.
1. 처음보는 Exception
로그인한 유저를 컨텍스트 홀더에서 꺼내서 건내려는데…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@RequiredArgsConstructor
public class AuthUtil {
private final TokenUtil tokenUtil;
public Long getLoginUserIndex() {
Optional<User> user = getLoginUser();
Long userId = user.get().getId();
return userId;
}
public Optional<User> getLoginUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
return (Optional<User>) principal;
}
...
?!

1
2
java.lang.ClassCastException: class com.example.budget.domain.user.User cannot be cast to class java.util.Optional (com.example.budget.domain.user.User is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @2d7e1886; java.util.Optional is in module java.base of loader 'bootstrap')
at com.example.budget.global.util.AuthUtil.getLoginUser(AuthUtil.java:25) ~[classes/:na]
대략 user 를 찾을때 optional 인게 문제가 되는 것 같았습니다. 왜 그런걸까요?
2. 왜 Optional<T> 로 casting 될 수 없나요?
Optional<T> 은 null 이 올 수 있는 값을 감싸는 Wrapper 클래스 입니다. 이를 통해 NullPointerException 을 방지할 수 있습니다. 또한 각종 메서드를 제공하기도 하죠. 그럼 casting 은 뭘까요? casting 은 특정한 객체가 다른 유형의 instance 인 것처럼 처리되어야 함을 컴파일러에게 알리는 방법 입니다. 단, 이 프로세스는 실제로 cast 되는 객체가 대상 유형 혹은 하위 클래스의 instance 인 경우에만 작동합니다.
Opitonal<T> 안에는 객체가 들어가니까 casting 될 수 있는거 아닌가? 라고 생각할 수도 있습니다. Optional<T>의 T 또는 일반 유형은 모든 유형의 객체에 대한 자리 표시자 입니다. 객체 자체가 아니라 나중에 지정하게 될 유형을 나타내는 기호라는 점에서 우리가 일반적으로 생각하는 객체가 아닙니다.
즉, 객체는 구조와 목적이 근본적으로 다르기 때문에 Optional<T>로 직접 처리할 수 없습니다. 객체는 무엇이든 될 수 있지만 Optional<T>는 특히 T 유형 객체의 컨테이너 이기 때문이죠. (비유하자면 객체와 T 는 사과, Optional<T> 는 사과가 들어있는 박스라고 할 수 있습니다.) 그래서 아래와 같이 Optional 을 제외해주었고, 결과는 제대로 나왔습니다.

1
2
3
4
5
6
7
8
9
10
11
public Long getLoginUserIndex() {
User user = getLoginUser();
Long userId = user.getId();
return userId;
}
public User getLoginUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
return (User) principal;
}