[교재 EffectiveJava] 아이템 54. null이 아닌 빈 컬렉션이나 배열을 반환하라

반응형
728x90
반응형

null 처리

package com.java.effective.item54;

import java.util.ArrayList;
import java.util.List;

public class Main {
    private final static List<Cheese> cheesesInStock = new ArrayList<>();

    /**
     * 재고가 없다면 null 반환
     * @return
     */
    public static List<Cheese> getCheeses() {
        return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
    }

    public static void main(String[] args) {
       
    }

    enum Cheese {
        STILTON
    }
}

재고가 없다면 null이 반환된다. 이 null 처리는 해당 메서드를 호출하는 클라이언트의 몫이다.

 

package com.java.effective.item54;

import java.util.ArrayList;
import java.util.List;

public class Main {
    private final static List<Cheese> cheesesInStock = new ArrayList<>();

    /**
     * 재고가 없다면 null 반환
     * @return
     */
    public static List<Cheese> getCheeses() {
        return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
    }

    public static void main(String[] args) {
        List<Cheese> cheeses = getCheeses();

        if (cheeses != null && cheeses.contains(Cheese.STILTON)) {
            System.out.println("ok");
        }
    }

    enum Cheese {
        STILTON
    }
}

null 상황을 처리하는 방어 코드가 추가되었다. 클라이언트에서 방어 코드를 빼먹으면 오류가 발생할 수도 있다. 실제로 객체가 0개일 가능성이 거의 없는 상황에서는 숨겨진 오류가 존재하는 것이다. 

 

 

해결방안 1. 빈 리스트 반환

null이 아닌 빈 배열이나 빈 컬렉션을 반환하자. 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환이 가능하다.

/** 기존 코드 */
public static List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}
    
/** 변환 코드 */
public static List<Cheese> getCheeses() {
    return new ArrayList<>(cheesesInStock);
}

빈 컬렉션을 반환하는 코드로 변환하였다. 하지만 사용 패턴에 따라 빈 컬렉션 할당이 성능을 눈에 띄게 떨어뜨릴 수도 있다. 이때 해법이 존재하는데, 매번 똑같은 빈 '불변' 컬렉션을 반환하는 것이다. 불변 객체는 자유롭게 공유해도 안전하다. 

 

이 예로, Collections.emptyList 메서드를 확인해보자.

/** 기존 코드 */
public static List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}
    
/** 변환 코드 */
public static List<Cheese> getCheeses() {
    return cheesesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheesesInStock);
}

 

emtpyList() 코드를 한번 파악해보자.

public static final List EMPTY_LIST = new EmptyList<>();
...

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
...

이 코드는 최적화에 해당하므로, 꼭 필요할때만 사용해야한다. EMPTY_LIST는 불변 객체로, 안전하게 사용 가능하다. emptyList() 외에도 각 타입에 맞게 emptySet(), emptyMap()을 사용하면 된다.

 

 

해결방안 2. 빈 배열 반환

배열의 경우에도 절대 null을 반환하지 말고 길이가 0인 배열을 반환하자. 보통은 단순히 정확한 길이의 배열을 반환하기만 하면 된다. 그 길이가 0일수도 있다. 

  /** 변환 코드 */
public static Cheese[] getCheese() {
    return cheesesInStock.toArray(new Cheese[0]);
}

toArray 메서드에 건넨 길이 0짜리 배열은 우리가 원하는 반환타입을 알려주는 역할도 동시에 한다. 이 방식이 성능을 떨어뜨릴것 같다면 길이 0짜리 배열을 미리 선언해두고 그 배열을 반환하면 된다. 길이 0인 배열은 모두 불변이기 때문이다.

private final static Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
    
...
public static Cheese[] getCheese() {
    return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
...

단순히 성능을 개선할 목적이라면 toArray에 넘기는 배열을 미리 할당하지 말자. 아래의 코드는 나쁜 예시다.

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

 

 

 

 

반응형

Designed by JB FACTORY