[Effective Java]null이 아닌, 빈 컬렉션이나 배열을 반환하라

메소드, 여섯 번째 아이템

Posted by SungBeom on May 01, 2020 · 4 mins read

null 반환의 문제점

컬렉션이나 배열 같은 컨테이너(container)가 비었을 때 null을 반환하는 코드는 주변에서 흔히 볼 수 있다. 이와 같이 null을 반환하는 메소드를 사용할 때면 항시 이와 같은 방어 코드를 넣어줘야 한다. 클라이언트에서 방어 코드를 빼먹으면 오류가 발생할 수 있다. 한편, null을 반환하려면 반환하는 쪽에서도 이 상황을 특별히 취급해줘야 해서 코드가 더 복잡해진다.

빈 컬렉션을 반환하는 방법

빈 컨테이너를 할당하는 데도 비용이 드니 null을 반환하는 쪽이 낫다는 주장도 있지만, 이는 두 가지 면에서 틀린 주장이다. 첫 번째, 성능 분석 결과 이 할당이 성능 저하의 주범이라고 확인되지 않는 한, 이 정도의 성능 차이는 신경 쓸 수준이 못 된다. 두 번째, 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다. 다음은 빈 컬렉션을 반환하는 전형적인 코드로, 대부분의 상황에서는 이렇게 하면 된다.

1
2
3
4
// 빈 컬렉션을 반환하는 올바른 예
public List<Cheese> getCheeses() {
    return new ArrayList<>(cheesesInStock);
}
cs

사용 패턴에 따라 빈 컬렉션 할당이 성능을 떨어뜨릴 수도 있다. 이런 경우에 해법은 매번 똑같은 빈 '불변' 컬렉션을 반환하는 것이다. 불변 객체는 자유롭게 공유해도 안전하다. 단, 이 방식은 최적화에 해당하므로, 수정 전과 후의 성능을 측정하여 실제로 성능이 개선이 되는 경우에만 사용하자.

1
2
3
4
5
// 최적화 - 빈 컬렉션을 매번 새로 할당하지 않도록 했다.
public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? Collections.emptyList()
        : new ArrayList<>(cheesesInStock);
}
cs

빈 배열을 반환하는 방법

배열을 쓸 때도 마찬가지로, 절대 null을 반환하지 말고 길이가 0인 배열을 반환하라. 보통은 단순히 정확한 길이의 배열을 반환하기만 하면 되며 그 길이가 0일 수도 있을 뿐이다. 다음 코드에서 toArray 메소드에 건넨 길이 0짜리 배열은 우리가 원하는 반환 타입을 알려주는 역할을 한다.

1
2
3
4
// 길이가 0일 수도 있는 배열을 반환하는 올바른 방법
public Cheese[] getCheeses() {
    return cheesesInStock.toArray(new Cheese[0]);
}
cs

이 방식이 성능을 떨어뜨릴 것 같다면 길이 0짜리 배열을 미리 선언해두고 매번 그 배열을 반환하면 된다. 길이 0인 배열은 모두 불변이기 때문이다. 하지만 단순히 성능을 개선할 목적이라면 toArray에 넘기는 배열을 미리 할당하면 오히려 성능이 떨어진다는 연구 결과도 있기에 추천하지 않는다.

1
2
3
4
5
6
// 최적화 - 빈 배열을 매번 새로 할당하지 않도록 했다.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
 
public Cheese[] getCheeses() {
    return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
cs

핵심 정리

null이 아닌, 빈 배열이나 컬렉션을 반환하라. null을 반환하는 API는 사용하기 어렵고 오류 처리 코드도 늘어난다. 그렇다고 성능이 좋은 것도 아니다.