아마도 Optional
을 처음 사용하게 된 것은 Spring Data JPA를 사용하면서 인 것 같다. findById
를 통해 엔티티를 가져오는 경우 엔티티를 바로 반환하는 것이 아니라 Optional
로 반환되기 때문에 Optional을 사용할 수 밖에 없었다. 그래서 보통은 엔티티를 조회한 후 존재하지 않는 엔티티를 조회했다면 orElseThrow
를 사용하여 적당한 예외를 던지는 방식으로 사용하였다.
솔직히 굳이 Optional
을 왜 사용해야 하는지 궁금할 때가 많았다. 그러다가 최근 다음과 같은 상황을 겪게 되었다. 어떤 객체의 멤버변수를 계속 조회해서 하위로 내려가야 하는 경우였다. 가령 특정 장치를 소유한 팀의 이름을 조회할 때 다음과 같이 객체 그래프를 탐색해야 한다고 가정해보자.
장치 -> 장치의 소유자 -> 소유자가 속한 팀 -> 팀의 이름
이런 경우 null
일 수 있는 곳은 다음의 네 곳이나 있다.
어느 것 하나라도 null
이라면 기본 값을 출력한다거나 특정 예외를 반환해야 한다면 확인해야 하는 부분이 네 곳이나 되는 것이다. 가령 장치로부터 팀의 이름을 구하고 null
인 값이 존재하면 IllegalStateException
예외를 던지는 getTeamNameFromDevice
메소드는 다음과 같을 것이다.
public String getTeamNameFromDevice(Device device){
if(device == null) throw new IllegalStateException();
User owner = device.getOwner();
if(owner == null) throw new IllegalStateException();
Team team = owner.getTeam();
if(team == null) throw new IllegalStateException();
String teamName = team.getName();
if(teamName == null) throw new IllegalStateException();
return teamName;
}
Optional
을 인자로 받는다고 해도 나는 다음과 같이 사용하고 있었다.
public String getTeamNameFromDevice(Optional<Device> optDevice){
Device device = optDevice.orElseThrow(IllegalStateException::new);
if(device == null) throw new IllegalStateException();
User owner = device.getOwner();
if(owner == null) throw new IllegalStateException();
Team team = owner.getTeam();
if(team == null) throw new IllegalStateException();
String teamName = team.getName();
if(teamName == null) throw new IllegalStateException();
return teamName;
}
Optional
객체를 인자로 전달받는다고 해도 사실상 처음에 orElseThrow
를 사용한 것을 제외하면 달라진 부분이 없다.
그런데 Optional
을 사용하면서 메소드 체이닝을 사용할 수 있다는 사실이 어렴풋이 기억났다. 처음 봤을 때는 Optional
에서 무슨 체이닝을 사용한다는 건지 이해를 못하고 넘어갔는데 혹시나 내가 겪고 있는 문제를 해결해 줄 수 있지 않을까 하는 생각이 들었다. 그리고 map
함수가 있다는 사실을 알게 되었다. 그리고 getTeamNameFromDevice
함수를 다음과 같이 바꾸어 보았다.
public String getTeamNameFromDevice(Optional<Device> optDevice){
return optDevice
.map(Device::getOwner)
.map(User::getTeam)
.map(Team::getName)
.orElseThrow(IllegalStateException::new);
}
복잡하게 if
문을 사용하지 않고도 동일한 결과를 얻을 수 있었다. 팀 이름이 null
이어도, 팀이 null
이어도, 소유자가 null
이어도, 장치가 null
이어도 모두 IllegalStateException
이 발생했다. 이제 Optional
을 왜 쓰는지 조금이나마 이해하게 되었다. 문서를 찾아보니 다음과 같이 나와 있었다.
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.
값이 존재하면 제공된 매핑 함수를 적용하고 결과가 null
이 아니면 결과에 대한 Optional
을 반환한다고 한다. 그렇지 않은 경우, 그러니까 매핑 함수의 적용 결과가 null인 경우 빈 Optional
을 반환한다고 한다. 그렇기에 메소드를 체이닝했을 때 중간에 null이 있어도 빈 Optional
로 계속해서 이어 나갈 수 있었던 것이다.
사람들이 이래서 Optional
을 쓰라고 했다는 사실을 깨달으며 많은 충격을 받았다. NullPointerException
으로부터 안전하다는 것은 알았지만 쓰기 불편해서 잘 쓰지 않게 되었는데 사용법을 제대로 알지 못하고 있었던 것이다. 이제부터는 적재적소에 잘 활용해야겠다.
이걸 왜 지금에야 저도 알았을까요.. 감사합니다!