Effective Java : 아이템 53. 가변인수는 신중히 사용하라
- 프로그래밍 언어/JAVA
- 2023. 8. 21.
아이템 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%만큼 감소되었기 때문에 성능 최적화 문제를 해결할 수 있게 된다.
이 기법도 대부분의 성능 최적화 기법처럼 별 이득은 없을 수도 있지만, 어쩔 수 없는 상황에서는 도움이 될 지도 모르기 때문에 이해하고 있으면 좋다.
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
Effective Java : 아이템 58. 전통적인 for 문보다는 for-each 문을 사용하라. (0) | 2023.08.22 |
---|---|
Effective Java : 아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라. (0) | 2023.08.21 |
Effective Java : 아이템 73. 추상화 수준에 맞는 예외를 던져라. (0) | 2023.08.15 |
Effective Java : 아이템 49. 매개변수가 유효한지 검사하라. (0) | 2023.08.15 |
Effective Java : 아이템 45. 스트림은 주의해서 사용하라. (0) | 2023.08.12 |