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

들어가며

빈 배열이나 컬렉션 대신 null을 반환하는 코드는 단점이 많다. 다음 코드를 보자.

1
2
3
4
5
private final List<Cheese> cheeseInStock = ...;

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

빈 객체 대신 null을 반환하는 메소드를 수행할때면 클라이언트 측에서 항시 방어적 코드를 작성해야 한다. 이를 빼먹으면 오류가 발생한다. 하지만 빈 컨테이너를 할당하는데도 비용이 드니 null을 리턴하는게 낫다는 주장도 있다. 이는 근거가 없다면 잘못된 말이다. 첫째로 빈 컨테이너를 할당하는 비용이 성능 저하의 주범이라고 확인되지 않았다면 이 정도의 성능 차이는 무시해도 된다. 둘째로 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다.

1
2
3
public List<Cheese> getCheeses() {
return new ArrayList<>(cheeseInStock);
}

하지만 빈 컬렉션 할당이 성능 저하의 주범이 된다면 다른 방법도 있다. 불변 빈 컬렉션을 반환하는 방법이다.

1
2
3
public List<Cheese> getCheeses() {
return cheesesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheeseInStock);
}

배열도 마찬가지다. null을 반환하지말고 길이 0짜리 배열을 반환하자. 성능 저하가 걱정된다면 위와 같은 방식으로 길이 0짜리 배열을 미리 선언해두면 된다.

1
2
3
4
5
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheeses() {
return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}

다만 다음과 같은 방법은 절대 쓰지말자. 성능 저하의 원인이 될 수도 있다.

1
return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);

다음은 toArray(T[] a)의 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Returns an array containing all of the elements in this list in proper
* sequence (from first to last element); the runtime type of the returned
* array is that of the specified array. If the list fits in the
* specified array, it is returned therein. Otherwise, a new array is
* allocated with the runtime type of the specified array and the size of
* this list.
*
* <p>If the list fits in the specified array with room to spare
* (i.e., the array has more elements than the list), the element in
* the array immediately following the end of the collection is set to
* <tt>null</tt>. (This is useful in determining the length of the
* list <i>only</i> if the caller knows that the list does not contain
* any null elements.)
*
* @param a the array into which the elements of the list are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose.
* @return an array containing the elements of the list
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in
* this list
* @throws NullPointerException if the specified array is null
*/
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
//이 부분에서 a의 크기가 size보다 작다면 새로운 배열을 "생성"한 뒤 리턴한다.
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
Author: Song Hayoung
Link: https://songhayoung.github.io/2020/08/18/Languages/Effective%20JAVA/item54/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.