private final List<Cheese> cheeseInStock = ...;
public List<Cheese> getCheeses() {
return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheeseInStock);
}
위의 코드는 리스트에 값이 없으면 null
을 반환하는 코드이다. 주변에서 흔히 볼 수 있는 코드지만 null 을 반환한다면 이 메서드를 사용하는 측에서 다음과 같이 처리를 해주어야 한다.
List<Cheese> cheeses = shop.getCheese();
if (cheeses != null && cheeses.contains(Cheese.STILTON))
System.out.println("좋았어, 바로 그거야.");
이와 같이 컬렉션이나 배열 같은 컨테이너(container)가 비었을 때 null 을 반환하는 메서드를 사용할 때면 매번 방어적인 코드를 작성해야 한다. 클라이언트에서 방어 코드를 빠트리면 오류(NPE) 가 발생할 수도 있다. 또, null 을 반환하는 쪽에서도 값이 없는 경우를 특별취급하여 처리해주어야 한다.
때로는 빈 컨테이너를 할당하는 데에도 비용이 드니 null 을 반환하는 쪽이 낫다는 주장도 있다. 하지만 이는 틀린 주장이다.
public List<Cheese> getCheeses() {
return new ArrayList<>(cheesesInStock);
}
가능성은 낮지만, 사용 패턴에 따라 빈 컬렉션 할당이 성능을 눈에 띄게 떨어뜨릴 수도 있다. 이 경우엔 매번 똑같은 빈 불변 컬렉션을 반환한다. 불변 객체는 자유롭게 공유해도 안전하다.(아이템 17)
public List<Cheese> getCheeses() {
return cheesesInStock.isEmpty() ? Collections.emptyList()
: new ArrayList<>(cheesesInStock);
최적화하여 빈 컬렉션을 매번 새로 할당하지 않도록 하였다. 단, 최적화는 예상과는 다른 결과를 초래할 수 있으니 신중하는 것이 좋다.(아이템 67)
배열을 쓸때도 마찬가지로 null 이 아닌 길이가 0인 배열을 반환한다. 매번 새로운 빈 배열 객체를 생성하는 것이 부담스럽다면 다음과 같이 미리 선언해두고 재사용할 수 있다.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}