우아한테크코스 미션을 진행하면서,
그리고 이펙티브 자바를 읽으면서 null 반환의 위험성을 알게 됐다.
로또 미션을 진행하면서
로또 구매 방법에는 자동, 수동이 존재한다.
수동으로 구매할 것이라면 구매할 로또 번호들을 입력하고,
구매하지 않을 것이라면 생략해주자
수동 로또를 구매할 개수를 입력받을 때,
0이 아닌 특정 자연수값이라면 번호를 List에 담은 객체를 넘겨주면 되지만,
0이 입력됐을 경우, 두 가지 방법이 존재한다.
- null을 반환해준다.
- 빈 객체(Collections.emptyList())를 반환해준다.
1번 방법은 위험한 방법이다.
null을 반환해줄 경우, 이를 사용하는 프로덕션 메소드에서 null일 때에 따로 처리하는 로직을 추가로 만들어주어야 한다.
그렇지 않을 경우, 우리를 자주 괴롭히는 존재인 NPE(NullPointerException)을 마주하게 될 것이다.
이는 코드를 개발하는 개발자 뿐만 아니라, 우리의 코드를 다른 사람이 이어서 작업할 때에도 굉장히 번거롭게 작용할 수 있는 부분이다.
null이 아닌, 빈 컬렉션이나 배열을 반환하라
따라서 우리는 2번 방법, 빈 컬렉션을 반환해주는 방법을 사용해주어야 한다.
(아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라)
빈 컬렉션과 배열은 새로 할당하지 않고도 반환이 가능하기 때문에, 성능 차이에 거의 영향을 주지 않으며,
영향을 준다 하더라도, NPE로 인한 생산성 비용이 증가하는 것에 비해선, 거의 영향이 없는 것이나 마찬가지이다.
따라서 이 성질을 이용해서 아래와 같이 성능 최적화를 하는 것도 좋다.
public List<Books> generateBookshelf() {
if(books.isEmpty()) {
return Collections.emptyList();
}
return new ArrayList<>(books);
}
빈 객체일 경우 새로 할당하지 않고 emptyList를 반환해주자.
옵셔널 반환을 고려해보자
자바8로 올라오면서 우리는 또 다른 선택지가 생겼다.
바로 Optional을 반환하는 것이다.
(아이템 55. 옵셔널 반환은 신중히 하라)
Optional<T>를 사용하면,
보통의 경우 T를 반환해야 하지만 특정 조건에서 아무것도 반환하지 않아야 할 때, 빈 결과를 반환하게 만들 수 있다.
이는, API 사용자에게 반환 값이 없을 수도 있다는 사실을 명확하게 전달해줄 수 있어, API 사용자게 그에 따른 적절한 처리를 해줄 수 있도록 한다.
사용 방법은 간단하게, Optional의 정적 팩토리 메소드를 이용해주면 된다.
- Optional.of(value) : Optional<T> 반환형이며, value 값에 null을 넣으면 NPE를 던진다.
- Optional.ofNullable(value) : Optional<T> 반환형이며, value 값에 null을 허용해준다.
주의할 점은, 옵셔널을 반환하는 메소드에서 null을 반환하는 것은 옵셔널 취지를 완전히 무시하는 것이며,
Optional에서 get으로 값을 꺼내오는 것 또한, Optional 취지를 전혀 살리지 못하고 null 값을 가져와버리는 것이기 때문에 지양해야 한다.
orElseGet이나 orElse를 사용하도록 하자.
- orElseGet : Optional이 가지고 있는 값이 null일 때에만, orElseGet 뒤의 함수가 실행된다.
- orElse : Optional이 가지고 있는 값이 null인지 아닌지 상관없이, orElse 뒤의 함수가 불필요하게 실행될 수 있다.
orElseGet과 orElse 차이가 위 말만으로는 좀 헷갈릴 수 있다.
아래 글들이 도움이 될 것이다.
도움된 글 1: https://ysjune.github.io/posts/java/orelsenorelseget/
도움된 글 2: https://onduway.tistory.com/85
null 판단의 책임은 옵셔널에게만
특히, 자바 9의 Optional의 stream() 메소드에서의 filter나 map, ifPresent를 이용하여 값이 있을 때와 없을 때를 적절히 처리해줄 수 있으므로 잘 이용해주도록 하자.
또한, isPresent를 통해 값이 비었는지 확인한 후에 다른 함수를 실행하는 방안보단, filter, map, ifPresent로 충분히 처리가 가능하기 때문에 신중히 고민해보도록 하자.
애초에 optional을 사용한 시점에서 null 체크를 isPresent로 따로 확인해줄 필요가 없다.
따라서 optional 값이 null인지 확인해주는 작업이나 마찬가지인 isPresent를 사용하기보단,
null 체크는 Optional에게 책임을 위임해주어 orElse, orElseGet, filter, map, ifPresent를 사용해주는 방법을 사용해주도록 하자.
불필요한 옵셔널 사용을 지양하라
컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안된다.
차라리 빈 List를 반환하는 것이 좋다.
옵셔널은 Wrapper 클래스이기 때문에 값을 두 겹이나 감싸 생성 비용이 비싸다는 단점이 존재하고,
애초에 대체재로 OptionalInt, OptionalLong 등도 존재하므로 박싱된 기본 타입을 담은 옵셔널을 반환하지 않도록 하자.
도움된 글: https://tecoble.techcourse.co.kr/post/2021-06-20-optional-vs-null/
null을 반환하지 않기 위해 빈 객체를 반환하거나 Optional을 반환하는 데에도 많은 공부가 필요했다.
JAVA, 그리고 객체지향의 세계란 파도 파도 끝이 없는 듯.
orElseGet, orElse의 차이점도 오늘 새롭게 알게 됐고,
isPresent() 사용을 한다면 한번쯤 코드를 의심해보아야 한다는 사실도 알게 됐다.
이제 이 내용들을 다음에 실전에서 적용해볼 수 있길 바라며, 이번 포스팅을 마치도록 하겠다.
'JAVA > JAVA | Spring 학습기록' 카테고리의 다른 글
Collections.EMPTY_LIST vs Collections.emptyList() 무엇이 다를까? (6) | 2022.03.03 |
---|---|
[호호 스터디] 다형성과 추상 타입_ 객체지향과 디자인 패턴 Chapter 3 (2) | 2022.03.02 |
[220221] 호호 스터디_ 객체지향과 디자인 패턴 Chapter 2 (2) | 2022.02.21 |
[TDD 리팩토링] @ParameterizedTest을 이용한 테스트 메소드에서의 여러 값 검증 (0) | 2022.02.11 |
[ERROR] 스프링 어노테이션 인식이 안될 때 (2) | 2021.12.25 |