...
가변 인수 (Variable Arguments)
가변 인수(varargs)란, 매개변수로 들어오는 값의 개수와 상관 없이 동적으로 인수를 받아 기능하도록 해주는 문법을 지칭한다.
예를들어 다음과 같이 매개변수 갯수가 일정치 않은 print() 메서드가 있다고 가정하자.
print("홍길동");
print("홍길동", "이순신");
print("홍길동", "이순신", "유성룡");
print("홍길동", "이순신", "유성룡", "강감찬");
print("홍길동", "이순신", "유성룡", "강감찬", "이도");
여러개의 파라미터가 들어올 수 있는 메서드를 구성하는 방법은 대표적으로 메서드 오버로딩(overloading)으로 처리가 가능하다.
하지만 전달할 매개변수가 몇개인지 일정치 않을때 일일히 메서드를 오버로딩하여 구현하는 것은 변거로우며 별로 효율적이지 못하다라고 할 수 있는데, 여기서 가변 인자(varargs)가 힘을 발한다.
가변 인수는 파라미터들을 통째로 배열로 받아들여 처리하기 때문에 동적으로 매개변수를 받을 수 있게 된다. 덕분에 메서드를 n번 오버로딩 하지않고 한번에 처리가 가능하다.
가변 인수는 JDK 1.5부터 추가되었으며, 자바에서는 System.out.printf() 메서드가 가변 인자를 사용한 대표적인 메서드라고 할 수 있다.
가변 인수 사용법
메서드 파라미터 부분에 타입... 매개변수명 으로 처리하면 사용이 가능하다.
가변 인수는 전달 인자를 0개부터 n개까지 넣을수 있다. 그리고 파라미터로 넘겨지는 값들을 모아서 컴파일시 배열로 처리된다. 주의할점은 인자들의 갯수에는 제한이 없지만, 배열 자료형은 매개변수 타입으로 명시된 것에 따라간다는 점이다.
public static void main(String[] args) {
print("홍길동");
print("홍길동", "이순신");
print("홍길동", "이순신", "유성룡");
print("홍길동", "이순신", "유성룡", "강감찬");
print("홍길동", "이순신", "유성룡", "강감찬", "이도");
}
public static void print(String... str) {
// 가변 인수인 str 매개변수는 String[] 배열 타입으로 받아들인다.
for(String s : str) {
System.out.print(s + ", ");
}
System.out.println();
}
만일 매개변수가 가변 인자 외에 다른 매개 변수들도 받는다면, 반드시 가변 인자를 메서드 파라미터 가장 마지막에 위치하도록 정의해야 한다.
그리고 매개변수가 넘겨지는 순서는, 인자들이 앞에 있는 파라미터 부터 차례대로 넘겨지고 남은 나머지 인자들이 가변 인자로 넘겨지게 된다.
public static void main(String[] args) {
print(1, true, "홍길동", "이순신", "유성룡");
}
// 매개변수가 여러개 있을 경우, 가변 인자(varargs)는 반드시 마지막에 위치
public static void print(int num, boolean bool, String... str) {
System.out.println("number : " + num);
System.out.println("bool : " + bool);
System.out.println("rest parameters : " + Arrays.toString(str));
}
자바스크립트의 가변 인수
자바스크립트 진영에도 가변 인수가 존재한다.
자바스크립트에선 가변 인수를 spread 연사자 혹은 나머지 매개변수 라고 칭한다.
function print(...data) {
console.log(data);
}
print(1,2,3,4,5); // [1, 2, 3, 4, 5]
가변 인수 주의점
1. 가변 인자 자체 성능 문제점
가변 인수는 위에서 보다시피 넘겨지는 인수 갯수가 정해지지 않았을때 유용히 사용 할 수 있다.
하지만 가변 인수 메서드는 호출될 때 마다 배열을 새로 하나 할당하고 초기화 하므로, 성능에 민감한 상황에서는 마이너스적인 요소가 될 수 있다.
하지만 꼭 자신의 프로그램 특성상 가변 인수를 써야할 상황이라면, 다음과 같이 유연적인 오버로딩으로 처리하는 것을 권한다.
예를들어 만약 어느 메서드를 호출할때 대부분의 상황에서 매개변수를 3개 이하로 받아 사용한다면, 그리고 아주 가끔 4개 이상의 매개변수를 받아야할 때가 있다면, 다음과 같이 구성하면 된다.
class Printer {
public void print(int a1) { }
public void print(int a1, int a2) { }
public void print(int a1, int a2, int a3) { }
public void print(int a1, int a2, int a3, int... rest) { }
}
실제로 EnumSet의 정적 팩터리도 위 기법을 사용해 열거 타입의 집합 생성 비용을 최적화하고 있다.
2. 가변 인수 자체를 오버로딩 X
하나의 클래스 내에 가변인자를 사용한 메서드를 오버로딩하는 것은 권하지 않는다.
컴파일러가 어떤 메소드를 사용해야 하는지 구분하지 못하기 때문에 컴파일 에러가 발생하기 때문이다.
class Printer {
public void print(String c, String... str) {
System.out.println("첫번째 메서드");
System.out.println("rest parameters : " + Arrays.toString(str));
}
public void print(String... str) {
System.out.println("두번째 메서드");
}
}
public class Main2 {
public static void main(String[] args) {
Printer p = new Printer();
// 이 메서드는 첫번째 메서드에도 두번째 메서드에서도 실행이 가능하기 때문에 컴파일러가 햇깔려 오류를 발생하게 된다.
p.print("-", "1", "2", "3");
}
}
3. 배열 타입 매개변수와 혼용 X
가변 인자는 내부적으로 배열을 생성해서 사용하는 것 같다.
따라서 다음과 같이 배열 타입의 매개변수와 동시에 정의해 사용이 불가능하다.
4. 제네릭 타입과 함께 쓸때에는 신중해라
이 부분은 Effective Java에 나오는 고급 내용이다.
가변 인수는 파라미터의 개수를 클라이언트가 조절할 수 있도록 해주지만 구현 방식에 허점이 존재한다.
위에서 말했듯이, 가변 인수 메서드를 호출하게 되면 가변 인수를 담기 위한 배열이 자동으로 하나 만들어지는데, 이때 제네릭 타입 파라미터와 같은 실체화 불가 타입은 런타임 시 타입 정보가 소거되기 때문에, 가변 인수에 제네릭이나 매개변수화 타입(List<String>)이 포함되고 이를 노출할 경우 문제가 일으킬수 있다.
예를들어 다음 toArray() 메서드는 제네릭 타입으로 가변 인수 배열을 받고 이를 그대로 반환한다.
이때 타입을 단언할 수 없기 때문에 모든 타입을 받을 수 있는 Object[] 타입 배열로 만들어 반환하게 되는데, 결국 메인 메서드에서 옳지 않은 다운캐스팅이 일어나서 ClassCastException 예외가 발생하게 된다.
class Printer {
// 제네릭 타입으로 받는 가변 인수
public <T> T[] toArray(T... args) {
return args; // 가변 인자들을 문제없이 담기 위해 어떤 타입도 담을 수 있는 가장 상위 타입인 Object 배열을 만들어 반환
}
public <T> T[] pick(T a, T b, T c) {
T[] arr = toArray(a, b, c); // Object[] 타입 배열로 리턴된다.
return arr;
}
}
public class Main {
public static void main(String[] args) {
Printer p =new Printer();
// 제네릭 메서드에 String 타입으로 전달
String[] s = p.pick("1","2","3"); // ! ERROR - Object[] 타입을 String[] 타입으로 다운캐스팅이 불가능하다.
}
}
따라서 제네릭과 가변 인수를 같이 사용할 일이 있을 경우, 배열 대신 리스트 제네릭으로 타입을 제한(extends / super)하는 식으로 메서드를 설계하거나, 제네릭 가변 인수 배열에 다른 메서드가 접근하도록 하지 않게 하는 것이 좋다.
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.