null이 아닌 빈 컬렉션이나 배열을 반환하라

빈 컨테이너에 null을 반환하는 습관을 버리자

Posted on 2019-05-02

null이 아닌 빈 컬렉션이나 배열을 반환하라


컬렉션

흔히 컬렉션이 비었으면 null을 반환하곤 한다.

class Shop {
    private final List<Cheese> cheesesInStock = ...;

    public List<Cheese> getCheeses() {
        return cheeseInStock.isEmpty() ? 
            null : new ArrayList<>(cheesesInStock);
    }
}

위 코드처럼 null을 반환한다면, 클라이언트는 아래 코드처럼 null 상황을 처리하는 코드를 추가로 작성해야 한다.

    ...
    List<Cheese> cheeses = shop.getCheeses();
    if (cheeses != null && cheeses.contains(Cheese.STILTON))
        // blah

컬렉션이나 배열 같은 컨테이너가 비었을 때 null을 반환하는 메서드를 사용할 때면 항시 이와 같은 방어 코드를 넣어줘야 한다. 클라이언트에서 방어 코드를 빼먹으면 오류가 발생할 수 있다.

빈 컨테이너를 할당하는 비용 때문에 null을 반환하는게 낫다는 주장은 옳지 않다.

  1. 빈 컨테이너 할당 정도의 비용은 신경 쓸 수준이 못 된다.
  2. 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다.

매번 똑같은 빈 불변 컬렉션을 반환하면 된다. 불변 객체는 자유롭게 공유해도 안전하다. ex) Collections.emptyList, Collections.emptySet, Collections.emptyMap

// 빈 컬렉션을 매번 새로 할당하지 않도록 최적화
public List<Cheese> getCheeses() {
    return cheeseInStock.isEmpty() ? 
        Collections.emptyList() : new ArrayList<>(cheeseInStock);
}


배열

배열의 경우에도 null을 반환하지 말고 길이가 0인 배열을 반환해야 한다.

// 빈 배열을 매번 새로 할당하지 않도록 최적화
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

// 원소가 하나라도 있다면 Cheese[] 타입의 배열을 새로 생성해 반환하고, 
// 원소가 없으면 EMPTY_CHEESE_ARRAY 를 반환한다.
public Cheese[] getGoodCheeses() {
    return cheeseInStock.toArray(EMPTY_CHEESE_ARRAY);
}

// toArray에 넘기는 배열을 미리 할당하는 것이 오히려 성능을 떨어뜨릴 수 있다.
public Cheese[] getBadCheeses() {
    return cheeseInStock.toArray(new Cheese[cheeseInStock.size()]);
}

<T> T[] List.toArray(T[] a) 메서드
주어진 배열 a가 충분히 크면 a 안에 원소를 담아 반환하고, 그렇지 않으면 T[] 타입 배열을 새로 만들어 그 안에 원소를 담아 반환한다.

핵심 정리

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


Reference
  • Effective Java