Home Item53 (가변 인수는 신중히 사용하라)
Post
Cancel

Item53 (가변 인수는 신중히 사용하라)

가변 인수는 신중히 사용하라

가변 인수 메서드를 호출하게 되면 가장 먼저 인수의 개수와 길이가 같은 배열을 만든다. 그리고 인수들을 이 배열에 저장해 가변인수 메서드에 건네주게 된다.

동일한 타입의 여러 매개변수를 받을 수 있도록 설계된 것이다.

개수에 제한도 없고 사용도 자유롭다.

단, 이렇게 편리한 가변인수는 주의해서 사용해야 한다.

가장 심플하게 주의해야 할 점은 하나의 메서드에는 한 개의 가변 인수만 사용이 가능하다는 점이다. 이 외의 주의 사항들을 보자.

인수가 1개 이상이어야 하는 가변 인수 메서드

예를 들어 최솟값을 찾는 메서드인데 인수를 0개만 받을 수 있도록 설계하는 것은 좋지 않다.

인수 개수는 런타임에 생성된 배열의 길이로 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int min(int... args) {
	if (args.length == 0) {
		throw new IllegalArgumentException("인수가 1개 이상 필요합니다.");
	}

	int min = args[0];

	for (int i = 1; i < args.length; i++) {
		if (args[i] < min) { 
			min = args[i];
		}
	}
	return min;
}
  • 이 방식은 어떤 문제가 있을까?
  • 가장 심각한 문제는 인수를 0개만 넣어 호출했을 때 컴파일 타임이 아닌 런타임에 실패한다.
    • args 유효성 검사를 할 때 예외를 던지게 되기 때문에 호출 시점에 바로 알 수 없다.
    • 코드도 지저분하다.
  • min 초깃값을 Integer.MAX_VALUE로 설정하지 않고는 보다 명료하게 작성할 수 있는 for-each문도 사용할 수 없다.

인수가 1개 이상이어야 할 때 가변인수를 올바르게 사용하는 방법으로는 매개변수를 2개 받도록 하면 된다.

1
2
3
4
5
6
7
static int min(int firstArg, int... remainingArgs) {
	int min = firstArg;
	for (int arg : remainingArgs) {
		if (arg < min) min = arg;
	}
	return min;
}
  • 가변 인수 1개를 필수로 받아야 하는 경우 매개변수를 2개 받도록 하여 해결할 수 있다.

성능이 민감한 경우

성능에 민감한 상황에서도 가변 인수는 주의해서 사용해야 한다.

가변 인수 메서드는 호출될 때 마다 배열을 하나 할당하고 초기화한다고 했다. 이 비용이 크게 다가오는 경우 다중 정의를 통해 해결할 수 있다.

1
2
3
4
5
public void foo() { }
public void foo(int a1) { }
public void foo(int a1, int a2) { }
//...
public void foo(int a1, int a2, int a3, int... rest) { }
  • 이렇게 작성하면 인수가 4개 이상인 호출만 가변 인수가 사용되어 비용을 줄일 수 있다.
  • 일반적으로는 크게 이득이 되지 않지만 메서드가 많이 사용되는 경우 성능 개선을 가져올 수 있을 것이다.

이렇게 사용한 예시로 EnumSet의 정적팩토리를 들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static <E extends Enum<E>> EnumSet<E> of(E e) {  
   EnumSet<E> result = noneOf(e.getDeclaringClass());  
   result.add(e);  
   return result;  
}

public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {  
   EnumSet<E> result = noneOf(e1.getDeclaringClass());  
   result.add(e1);  
   result.add(e2);  
   return result;  
}

//...

@SafeVarargs  
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {  
    EnumSet<E> result = noneOf(first.getDeclaringClass());  
    result.add(first);  
    for (E e : rest)  
      result.add(e);  
    return result;  
}
  • 성능이 민감한 경우와 인수를 1개 이상 받아야 할 때 해결책들을 모두 적용한 것을 볼 수 있다.

정리

가변 인수를 사용할 때는 주의해서 사용해야 한다. 인수 개수가 일정하지 않은 메서드를 정의하려면 반드시 사용되기 때문에 사용하지 않을 수는 없다.

인수가 1개 이상 필요할 때, 그리고 성능을 고려할 때 생각할 부분들이 있다.

제네릭과 가변인수 사용 시 힙 오염 발생도 주의해야 한다.

This post is licensed under CC BY 4.0 by the author.

Item52 (다중 정의는 신중히 사용하라)

Item54 (null이 아닌, 빈 컬렉션이나 배열을 반환하라)