본문으로 바로가기
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.



아래와 같이 정의된 메서드는 어렵지 않게 만나볼 수 있다. 아래에서 cheese가 없을 경우에는 배열을 반환하는 것이 아니라 NUll을 반환하고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class Test {
    
    private final List<cheese> cheesesInStock = ...;
    
    public Cheese[] getCheeses() {
        if(cheesesInStocks.size() == 0) {
            
            return null;
        }
    }
    
    public static void main(String[] args) {
        //NULL을 반환할 경우
        if(cheese != null && Arrays.asList(cheeses).contains(Cheese.STILTON))
            
        //NULL을 반환하지 않을 경우
        if(Arrays.asList(cheeses).contains(Cheese.STILTON))
    }
}



사용하는 입장에서 NULL을 반환될 때를 대비한 코드를 만들어야 한다.

이런 메서드는 오류를 쉽게 유발한다. 클라이언트가 NULL처리를 잊어버릴 수 있기 때문이다.


배열 할당 비용이 있으니 NULL을 반환해야 바람직한 것 아니냐는 주장도 있으나 이 주장은 2가지 측면에서 틀렸다.


1. 프로파일링 결과로 해당 메서드가 성능저하의 주범이라는 것이밝혀지지 않는 한 그런 수준까지 성능 걱정을 하는 것은 바람직하지 않다.

2. 길이가 0인 배열은 변경이불가능하므로 아무 제약 없이 재사용할 수 있다는 것이다.


컬렉션에서 배열을 만들어 반환하는 올바른 방법

1
2
3
4
5
6
7
8
private final List<Cheese> cheeseInStock = ...;
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
/**
* @return 재고가 남은 모든 치즈 목록을 배열로 만들어 반환
**/
public Cheese[] getCheese() {
    return cheeseInStock.toArray(EMPTY_CHEESE_ARRAY);
}



위 코드에서 toArray()에 전달되는 빈 배열 상수는 반환값의 자료형을 명시하는 구실을 한다. 보통 toArray()는 반환되는 원소가 담길 배열을 스스로 할당하는데, 컬렉션이 비어 있는 경우에는 인자로 주어진 빈 배열을 쓴다. 그리고 인자로 주어진 배열이 컬렉션의 모든 원소를 담을 정도로 큰 경우에는 해당 배열을 반환값으로 사용한다. 따라서 위의 숙어대로 하면 빈 배열은 절대로 자동 할당되지 않는다.(이 부분 이해가 안된다면 List에서 toArray() 메서드를 확인하기 바란다.)


마찬가지로 컬렉션을 반환하는 메서드도 빈 컬렉션을 반환해야 할 때마다 동일한 변경 불가능한 빈 컬렉션 객체를 반환하도록 구현할 수 있다. Collections.emptySet emptyList, emptyMap 메서드가 그런 용도로 사용된다.


컬렉션 복사본을 반환하는 올바른 방법

1
2
3
4
5
6
7
public List<Cheese> getCheeseList() {
    if (cheeseInSrock.isEmpty()) {
    return Collections.emptyList(); // 언제나 같은 리스트 반환
  } else {
    return new ArrayList<Cheese>(cheeseInStock);
  }
}



요약하자면 NULL 대신에 빈 배열이나 빈 컬렉션을 반환하라는 것이다. NULL을 반환하는 것은 C 언어에서 전해진 관습으로, C에서는 배열의 길이가 배열과 따로 반환된다. C에서는 길이 0인 배열을 할당해서 반환하더라도 아무 이득이 없다.




추가 설명


아래 코드가 이해가 안갔다. 컬렉션이 비어 있는 경우에는 인자로 주어진 빈 배열을 쓴다. 아래 return 처리대로 하면 빈 배열은 절대로 자동 할당되지 않는다.

1
2
3
4
5
6
7
8
private final List<Cheese> cheeseInStock = ...;
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
/**
* @return 재고가 남은 모든 치즈 목록을 배열로 만들어 반환
**/
public Cheese[] getCheese() {
    return cheeseInStock.toArray(EMPTY_CHEESE_ARRAY);
}



무슨 의미인지 확인해 보기 위해 아래와 같이 코드를 작성해보았다. 별도의 Cheese클래스를 구현해보았고, 객체를 생성하여 t1.getCheese() == t1.EMPTY_CHEESE_ARRAY를 구현 비교해 보았더니 true가 나왔다.

이 규칙에서 하는 의도를 다시 적어보자면 null 처리는 대비하지 못한다면 프로그램상의 오류가 있다.

아래 소스와 같이 빈 배열을 전달해 준다면 null처리를 하지 못한 실수를 줄일 수 있고, 똑같은 빈 배열이 할당되기 때문에 자동할당되지 않아 성능저하를 일으키지 않는다.


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
36
37
38
39
40
41
import java.util.ArrayList;
import java.util.List;
 
class Cheese {
    String info;
    
    public Cheese(String info) {
        this.info = info;
    }
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return this.info;
    }
    
}
 
public final class Test {
    
    private final List<Cheese> cheesesInStock = new ArrayList<Cheese>();
    public static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
 
    
    public Cheese[] getCheeses() {
        if(cheesesInStock.size() == 0) {
            
            return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
        }
        
        return (Cheese[]) cheesesInStock.toArray();
    }
    
    public static void main(String[] args) {
        Test t1 = new Test();
        System.out.println(t1.getCheeses()); //[Lcom.ktko.init.Cheese;@15db9742
        System.out.println(t1.EMPTY_CHEESE_ARRAY); //[Lcom.ktko.init.Cheese;@15db9742
        
        System.out.println(t1.getCheeses() == t1.EMPTY_CHEESE_ARRAY); //true
    }
}