Effective Java : 아이템 53. 가변인수는 신중히 사용하라

     

    아이템 53. 가변인수는 신중히 사용하라

    • 가변인수 메서드가 호출되면, 가변인수 개수와 동일한 배열이 생성되고 인수가 배열에 저장되어 넘겨짐. 
    • 가변인수가 적어도 1개 이상 필요한 메서드에서 가변인수는 안전하지 않을 수 있음.
      • 가변인수의 사이즈를 확인하는 전제조건 검사로 보완 가능.
      • 1개의 값을 명시적으로 받고, 나머지를 가변인수로 받아 더 깔끔하게 보완 가능. 
    • 가변인수 메서드가 호출될 때 마다 새로운 배열이 할당되므로 성능에 민감한 경우 병목구간이 될 수 있음. 
      • 케이스 분석 후, 오버로딩을 통해서 성능 최적화를 도모해 볼 수 있음. 

     


    가변인수는 안전할까?

    가변인수 메서드를 호출하면, 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수들을 배열에 저장해서 메서드에 넘겨준다. 메서드에서는 배열로 받기 때문에 Iterable하며 아래 코드처럼 사용할 수 있다. 아래 코드에서 가변인수는 안전한 것처럼 보인다. 가변인수는 '인자가 하나도 없는 상황'도 가능한데, 아래 코드에서는 인자가 하나도 없어도 안전하게 동작하기 때문이다.

    // 가변인수는 배열로 넘겨짐. 
    static int sum(int... args) {
        int sum = 0;
        for (int arg : args) {
            sum += arg;
        }
    
        return sum;
    }

    하지만 다음 코드에서는 인자가 전달되지 않았을 때, ArryIndexOutOfBoundsException이 발생한다. 인자가 전달되지 않으면 args 배열의 크기는 0이 된다. 그런데 args[0]으로 인자를 얻으려고 하기 때문에 배열보다 큰 인덱스에 위치한 값을 요청하기 때문에 에러가 발생한다. 즉, 가변인수는 안전하지 않을 수 있다.

    static int min(int... args) {
        // 가변인수에는 Null도 올 수 있음.
        // 이 경우 문제가 발생함.
        int min = args[0];
        for (int i = 1; i < args.length; i++) {
            min = Math.min(min, args[i]);
        }
    
        return min;
    }

     


    가변인수를 좀 더 지혜롭게 쓰는 방법은? 

     

    위의 예시에서 1개 이상의 인수가 반드시 전달되어야 하는 가변인수 메서드의 경우, 가변인수가 안전하지 않을 수 있다는 것을 확인했다. 이 코드를 안전하게 만들려면 아래처럼 가변인자의 크기가 0보다 크다는 전제조건을 확인해야한다. 아래 코드로 충분히 안전해 질 수는 있지만 깔끔해보이지는 않다. 

     

    static int minSafe(int... args) {
        if (args.length == 0) {
            throw new IllegalArgumentException("인수가 1개 이상 필요합니다.");
        }
    
        int min = args[0];
        for (int i = 1; i < args.length; i++) {
            min = Math.min(min, args[i]);
        }
    
        return min;
    }

    위와 같은 경우에서 가변인자를 더 깔끔하게 사용하려고 한다면, 첫번째 값을 명시적으로 받고 나머지 값을 가변인자로 받는 방법으로 분리할 수 있다. 아래 코드에서는 firstValue를 명시적으로 받고 나머지를 가변인자 args로 받아 계산했다. 코드가 더 깔끔해지는 것을 확인할 수 있다. 

    static int minRightSafe(int firstValue, int... args) {
        int min = firstValue;
        for (int arg : args) {
            min = Math.min(min, arg);
        }
    
        return min;
    }

     


    가변인수의 성능 최적화

    가변인수를 가진 메서드가 호출될 때 마다 새로운 배열이 하나 할당되고 초기화 된다. 성능에 민감한 상황이라면 병목구간이 될 수도 있다. 만약 가변인수를 사용하면서 성능 문제를 사용하고 싶다면 아래와 같이 메서드 다중 정의로 해결해 볼 수 있다. 

    public class OptimizationMutableVariable {
        // 배열 재할당 문제로 가변인수가 병목 구간이 될 경우, 
        // 아래처럼 오버로딩을 통해 해결할 수 있음. 
        public void foo() {}
        public void foo(int a){}
        public void foo(int a, int b){}
        public void foo(int a, int b, int c){}
        public void foo(int a, int b, int c, int... args) {}
    }

    위 코드는 foo() 메서드가 호출될 때, 인수가 0 ~ 3개가 전체의 95% 정도가 호출될 때를 가정한 코드다. 나머지 5%는 3개 이상의 가변인수가 호출될텐데 '배열을 초기화'하는 횟수가 95%만큼 감소되었기 때문에 성능 최적화 문제를 해결할 수 있게 된다. 

    이 기법도 대부분의 성능 최적화 기법처럼 별 이득은 없을 수도 있지만, 어쩔 수 없는 상황에서는 도움이 될 지도 모르기 때문에 이해하고 있으면 좋다. 

     

    댓글

    Designed by JB FACTORY