본문 바로가기

독서찰기(讀書札記)/이펙티브 자바

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

[Why]

private final List<Cheese> cheesesInStock = ...;

/**
 * @return 매장 안의 모든 치즈 목록을 반환한다.
 *     단, 재고가 하나도 없다면 null을 반환한다.
 */
public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? null
        : new ArrayList<>(cheesesInStock);
}
List<Cheese> cheeses = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON)) {
    System.out.println("좋았어, 바로 그거야.");
}

컬렉션이나 배열 같은 컨테이너가 비었을 때 null을 반환하는 메서드를 사용할 때면 항상 방어 코드를 넣어줘야 한다.

  • 클라이언트에서 방어 코드를 빼먹으면 오류가 발생할 수 있다.
  • 실제로 객체가 0개일 가능성이 거의 없는 상황에서는 수년 뒤에야 오류가 발생하기도 한다.

빈 컨테이너를 할당하는 데도 비용이 드니 null을 반환하는 쪽이 낫다는 주장도 있지만, 이는 두 가지 면에서 틀린 주장이다.

  • 첫 번째, 이 할당이 성능 저하의 주범이라고 확인되지 않는 한(아이템67), 이 정도의 성능 차이는 신경 쓸 수준이 못 된다.
  • 두 번째, 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다.

 

[How]

아래 코드는 빈 컬렉션을 반환하는 전형적인 코드로, 대부분의 상황에서는 이렇게 하면 된다.

public List<Cheese> getCheeses() {
    return new ArrayList<>(cheesesInStock);
}
  • 가능성은 작지만, 사용 패턴에 따라 빈 컬렉션 할당이 성능을 눈에 띄게 떨어뜨릴 수도 있다.

그러면 매번 똑같은 빈 '불변' 컬렉션을 반환하면 된다.

public List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? Collections.emptyList()
        : new ArrayList<>(cheesesInStock);
}
  • 불변 객체는 자유롭게 공유해도 안전하다(아이템17).
  • 집합이 필요하면 Collections.emptySet을, 맵이 필요하면 Collections.emptyMap을 사용하면 된다.
  • 단, 이 역시 최적화에 해당하니 꼭 필요할 때만 사용하자.

 

배열을 쓸 때도 절대 null을 반환하지 말고 길이가 0인 배열을 반환하라.

public Cheese[] getCheeses() {
    return cheesesInStock.toArray(new Cheeses[0]);
}
  • 이 방식이 성능을 떨어뜨릴 것 같다면 길이 0짜리 배열을 미리 선언해두고 매번 그 배열을 반환하면 된다.
  • 길이 0인 배열은 모두 불변이기 때문이다.

아래의 최적화 버전의 getCheeses는 항상 EMPTY_CHEESE_ARRAY를 인수로 넘겨 toArray를 호출한다.

private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheeses() {
    return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
  • 단순히 성능을 개선할 목적이라면 toArray에 넘기는 배열을 미리 할당하는 건 추천하지 않는다.
  • 오히려 성능이 떨어진다는 연구 결과도 있다.
// 나쁜 예
return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);