일반적으로 null을 반환하는 경우는 다음과 같다.
null을 반환하는 예( 금지 )
컬렉션이 비어있으면 null을 반환한다. - 따라하지 말 것!
private final List<Cheese> cheeseInStock = ...;
/**
* @return 매장 안의 모든 치즈 목록을 반환한다.
* 단, 재고가 하나도 없다면 null을 반환한다.
*/
public List<Cheese> getCheeses() {
return cheeseInStock.isEmpty() ? null
: new ArrayList<>(cheeseInStock);
}
재고가 없다고 해서 특별한 취급을 할 이유는 없다. 그럼에도 이 코드처럼 null을 반환해야 한다면, 이 null 상황을 처리하는 코드를 추가로 작성하도록 한다.
List<Cheese> cheese = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON))
System.out.println("좋았어, 바로 그거야");
cheese !=null에 주목하자. 컬렉션이나 배열 같은 컨테이너가 비었을 떄 null을 반환하는 메서드를 사용할 때면 항시 이와 같은 방어 코드를 넣어줘야 한다. null을 반환하려면 반환하는 쪽에서도 이 상황을 특별히 취급해줘야 해서 코드가 더 복잡해진다.
null 반환의 두가지 문제점
성능 분석 결과 이 할당이 성능 저하의 주범이라고 확인되지 않는 한(아이템 67), 이 정도의 성능 차이는 신경 쓸 수준
이 못된다. 또한 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다.
null 대신 빈 컬렉션과 배열을 반환하는 방법
빈 컬렉션을 반환하는 올바른 예
public List<Cheese> getCheeses() {
return new ArrayList<>(cheesesInStock);
}
빈 컬렉션 할당이 성능을 눈에 띄게 떨어뜨릴 수도 있다. 그럴 때는 매번 똑같은 빈 '불변' 컬렉션을 반환한다. 불변 객체는 자유롭게 공유해도 안전하다(아이템 17) Collections.emptyList가 그러하다. 경우에 따라 Collections.emptySet, Collections.emptyMap을 사용하면 된다. 최적화가 필요하다고 판단되면 수정 전과 후의 성능을 측정하여 실제로 성능이 개선되는지 꼭 확인한다.
최적화 - 빈 컬렉션을 매번 새로 할당하지 않도록 했다.
public List<Cheeses> getCheeses() {
return cheesesInStock.isEmpty()? Collections.emptyList()
: new ArrayList<>(cheesesInStock);
}
배열을 쓸 때도, 절대 null을 반환하지 말고 길이가 0인 배열을 반환한다.
길이가 0일 수도 있는 배열을 반환하는 올바른 방법
public Cheese[] getCheeses() {
return cheeseInStock.toArray(new Cheese[0]);
}
이 방식이 성능을 떨어뜨릴 것 같다면 길이 0짜리 배열을 미리 선언해두고 매번 그 배열을 반환하면 된다. 길이가 0인 배열은 모두 불변하기 때문이다.
최적화 - 빈 배열을 매번 새로 할당하지 않도록 했다.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheeses() {
return cheeseInStock.toArray(EMPTY_CHEESE_ARRAY);
}
따라서 cheeseInStock이 비었을 때면 언제나 EMPTY_CHEESE_ARRAY를 반환한게 된다. 단순히 성능을 개선할 목적이라면 toArray에 넘기는 배열을 미리 할당하는 건 추천하지 않는다. 오히려 성능을 떨어뜨린다는 연구가 있다.
나쁜 예 - 배열을 미리 할당하면 성능이 나빠진다.
return cheeseInStock.toArray(new Cheese[cheesesInStock.size()]);
결론
null이 아닌, 빈 배열이나 컬렉션을 반환하라. null을 반환하는 API는 사용하기 어렵고 오류 처리 코드도 늘어난다. 그렇다고 성능이 좋은 것도 아니다.
참고
<T> T[] List.toArray(T[] a) 메서드는 주어진 배열 a가 충분히 크면 a 안에 원소를 담아 반환하고, 그렇지 않으면 T[] 타입 배열을 새로 만들어 그 안에 원소를 담아 반환한다.
*느낀점
과제를 하면서 많이 고민했던 부분이다. null 자체를 반환하는 것은 상당히 많은 위험을 떠안는다. Collection과 List의 경우 적절하게 빈 형태를 반환하도록 하는 것이 최선의 방법이다. 이러한 부분에 대해서 얼마 전까지만해도 전혀 고려하지 못했는데, 이제 얼마나 매개변수와 반환값의 유효성이 중요한지 점차 깨닫고 있다.
'회고 > 이펙티브 자바 3판' 카테고리의 다른 글
[ 아이템 1 ] 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2021.03.10 |
---|---|
[ 아이템 55 ] 옵셔널 반환은 신중히 하라 (0) | 2021.02.11 |
[ 아이템 53 ] 가변인수는 신중히 사용하라 (0) | 2021.02.11 |
[ 아이템 52 ] 다중 정의는 신중히 사용하라 (0) | 2021.02.11 |
[ 아이템 51 ] 메서드 시그니처를 신중히 설계하라 (0) | 2021.02.11 |