아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라.
- 빈 컬렉션 대신 null을 반환하면 내부 구현 + 사용하는 쪽에서도 null 처리를 해야하는 번거로움이 발생함.
- 번거로움이 발생함에도 프로그램 자체에 아무런 이득이 없으므로 빈 컬렉션 / 배열을 반환하는것이 나음.
- 빈 컬렉션을 계속 반환해서 '객체 생성'으로 인해 '성능 문제 발생'이 명확하다면, 불변 객체를 반환하는 방법으로 개선할 수 있음. (대부분은 필요없는 방법임)
빈 컬렉션을 Null로 특별 취급할 필요가 없다.
아래 코드에서 cheessInStock이라는 Collection이 비어있는 경우 null을 반환하는 코드를 볼 수 있다. 그런데 빈 컬렉션을 굳이 'null'로 특별 취급할 필요가 있을까? 빈 컬렉션을 특별 취급할 필요가 없으며, 특별 취급하게 되면 오히려 더 복잡한 코드가 된다.
아래에서는 삼항 연산자를 이용했는데, 빈 컬렉션을 null로 특별 취급하지 않았다면 한 줄로 끝날 코드였다.
private final List<Cheese> cheesesInStock = new ArrayList<>();
public List<Cheese> getCheeses() {
return cheesesInStock.isEmpty() ?
null :
new ArrayList<>(cheesesInStock);
}
문제는 내부 구현 코드에서 그치지 않는다. 빈 컬렉션을 반환하는 메서드를 사용하는 클라이언트에서도 null 처리를 해야한다는 단점이 있다. 아래에서는 cheeses != null을 이용해서 null 체크를 하게 된다.
public static void main(String[] args) {
Shop shop = new Shop();
List<Cheese> cheeses = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON)) {
System.out.println("it's good");
}
}
빈 컬렉션을 특별취급 하지 않게 된다면 오히려 아래처럼 코드가 더 간단해지고, 단단해진다.
public List<Cheese> getCheeses() {
return new ArrayList<>(cheesesInStock);
}
public static void main(String[] args) {
ShopEmptyNotSpecial shop = new ShopEmptyNotSpecial();
List<Cheese> cheeses = shop.getCheeses();
if (cheeses.contains(Cheese.STILTON)) {
System.out.println("it's good");
}
}
빈 컬렉션을 반환하는 것은 성능 문제가 될 수 있다?
빈 컬렉션은 객체다. 이런 객체가 매번 생성되는 경우라면 만의 하나로 성능 문제가 발생할 수도 있다. (하지만 대부분은 발생하지 않는다). 만약 정말로 빈 컬렉션을 반환해서 성능 문제가 발생하는 경우라면, 해결 방법으로 '빈 불변 컬렉션'을 반환할 수 있다. 이렇게 하면 객체 하나를 계속 반환하기 때문에 '객체가 계속 생성되면서 발생'하는 문제들이 해결된다. 위 코드는 아래처럼 emptyList() 메서드로 바꿀 수 있다.
public List<Cheese> getCheesesForOptimization() {
return cheesesInStock.isEmpty() ?
Collections.emptyList() :
new ArrayList<>(cheesesInStock);
}
emptyList() 메서드를 살펴보면 아래와 같다. 새로운 EmptyList를 불변 객체(public static final)로 하나 만든 다음에 이 객체를 계속 반환한다. 그런데 이 객체는 add, remove 같은 연산이 허용되지 않으므로 진정한 의미의 '불변' 객체다. 따라서 여러 쓰레드가 공유해서 사용해도 큰 문제가 없다.
public static final List EMPTY_LIST = new EmptyList<>();
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
배열을 사용하는 경우에도 마찬가지로 처리해 볼 수 있다. 아래 코드는 다음을 의미한다.
- toArray() 메서드는 주어진 배열 EMPTRY_CHEES_ARRAY가 충분히 크면, 그 배열에 컴포넌트를 담아서 반환함.
- 충분히 크지 않으면, 새로운 배열을 생성해서 컴포넌트를 담아서 반환함.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
public Cheese[] getCheesesWithArray() {
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
위의 경우 EMPTY_CHESSE_ARRAY는 크기가 0인 배열이기 때문에 cheesInStock List에 컴포넌트가 있을 때라면 항상 새로운 배열이 만들어져서 반환된다. 그렇지 않은 경우라면 EMPTY_CHESSE_ARRAY 배열을 이용해서 '어떤 타입'인지 알려주는 타입 토큰처럼 '빈 불변 배열을 재사용' 할 수 있게 된다.
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Effective Java : 아이템 57. 지역변수의 범위를 최소화하라. (0) | 2023.08.22 |
---|---|
Effective Java : 아이템 58. 전통적인 for 문보다는 for-each 문을 사용하라. (0) | 2023.08.22 |
Effective Java : 아이템 53. 가변인수는 신중히 사용하라 (0) | 2023.08.21 |
Effective Java : 아이템 73. 추상화 수준에 맞는 예외를 던져라. (0) | 2023.08.15 |
Effective Java : 아이템 49. 매개변수가 유효한지 검사하라. (0) | 2023.08.15 |