티스토리 뷰

반응형

가변 인수(Varargs) 사용하기

Java 5 이전에는 메서드에 다른 개수의 인수들을 넣기 위해서는 메서드를 여럿 정의하거나 배열의 형태로 인수를 넘겨주어 처리해야 했다. Java 5의 가변 인수(Varargs)의 등장으로 Object... 와 같이 메서드의 인수를 0개 이상 받을 수 있게 선언이 가능해지며 이러한 불편함이 사라지게 되었다.

 

public void method(String... strings) {
    for (String string : strings) {
        System.out.println(string);
    }
}

// method("a", "b", "c") 와 같이 사용

 

이렇게 가변 인수를 통해 입력을 배열처럼 편하게 처리할 수 있는 지금과 달리

 

public void method(String[] strings) {
    for (String string : strings) {
        System.out.println(string);
    }
}

// 사용 시 직접 배열을 만들어 인수로 넣어주어야 함
// String[] strings = {"a", "b", "c"};
// method(strings);

 

배열을 입력으로 받게 하고 사용 시 직접 배열을 만들어 넘겨주거나,

 

public void method(String string1) {
    System.out.println(string1);
}

public void method(String string1, String string2) {
    System.out.println(string1);
    System.out.println(string2);
}
. . .

 

배열을 생성해 넘겨주는 것이 불편하다면 오버로딩을 활용해 직접 넘겨 줄 인수의 개수에 맞는 메서드를 만들어두어야 했다.

MessageFormatformat() 등 배열을 넘겨받았던 많은 Java API들이 이후 가변 인수 기능을 추가하였다.

 

 

규칙

 

API를 개발 또는 사용하는 사람이 무의미한 보일러플레이트 코드(Boilerplate Code)를 작성하지 않도록 하는 좋은 기능이지만 가변 인수를 사용하는 메서드를 안전하게 작성하기 위해선 두 가지의 규칙을 지켜야 한다.

  • 메서드당 하나의 가변 인수만 사용 가능하다.
  • 가변 인수를 포함한 여러 인수를 입력받는 경우, 가변 인수를 맨 마지막에 받아야 한다.

 

 

단점

 

0개부터 N개까지의 인수를 모두 처리할 수 있다라는 점... 뭔가 문제가 없는지 의심스럽지 않은가?

가변 인수를 사용하는 메서드는 호출 시 해당하는 자료형의 배열을 만들어 값들을 할당하고 넘겨주는 과정을 알아서 처리하는 기능이다. 따라서 성능에 민감한 상황에서는 가변 인수가 걸림돌이 될 수 있다.

또한 제네릭과 함께 가변인수를 사용하는 경우 힙 오염(https://en.wikipedia.org/wiki/Heap_pollution) 문제가 발생할 수 있다. 컴파일 시점에서 생성된 가변 제네릭 배열에 값을 대입하는 경우 타입에 따라 힙 오염이 발생하고 런타임에 ClassCastException이 발생할 수 있다.

 

public String method(List<String>... strings) {
    List<Integer> ints = Collections.singletonList(1);
    Object[] objects = strings;
    objects[0] = ints; // 힙 오염

    return strings[0].get(0); // 제네릭으로 인한 형변환이 런타임에 ClassCastException 발생
}

 

이런 상황을 방지하기 위해 웬만하면 메서드 내에서 가변 배열 자체에 값을 넣는 행위를 주의하고 메서드 외부로 해당 배열을 넘겨주는 코드를 작성하지 않아야 한다.

컴파일 시 제네릭 가변 배열을 사용하면 경고를 발생시키고, API 설계자가 직접 안전함을 보장하는 설계를 한 뒤 @SafeVarargs 를 붙여 없애도록 하자.

 

 

활용

 

'이펙티브 자바 아이템 53: 가변인수는 신중히 사용하라' 에서는 두 가지 가변인수 사용법을 소개한다.

 

1. 여러 값 중 최솟값을 구하는 메서드를 작성하고자 할 때, 최소한 하나의 입력이 보장되어야 한다.

메서드를 선언할 때 가변 인수만을 받도록 설정하면 0개 또한 받을 수 있는 가변 인수의 특성 상 런타임 에러가 발생할 수 있다는 문제가 존재한다.

 

이러한 경우 아래처럼 하나의 인수를 무조건 받고, 추가적인 인수의 입력이 가능하도록 해 해결 가능하다.

 

public int min(int firstArg, int... otherArgs) {
    int min = firstArg;
    for (int arg : otherArgs) {
        if (arg < min) {
            min = arg;
        }
    return min;
}

 

2. 성능적 한계를 보완하기 위해 메서드 오버라이딩을 통해 자주 사용되는 개수의 인수들은 가변 인수가 아닌 메서드로 처리할 수 있다.

흔히 사용하는 List.of()와 같은 메서드들이 성능을 위해 이러한 전략을 취하고 있다.

 

public void method() { }
public void method(int int1} { }
public void method(int int1, int int2} { }
public void method(int int1, int int2, int3} { }
public void method(int int1, int int3, int... remainder} { }

 

위와 같은 설계에서는 인수를 4개 이상 사용할 때 비로소 가변 인수를 활용하므로 많은 경우에 단순 가변 인수보다 성능 상 이점을 취할 수 있다.

 

 

참고

https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html

https://www.baeldung.com/java-varargs

 

 

반응형