Varargs (가변인자)에 대해서

POII·2023년 2월 19일
0

Java

목록 보기
1/7
post-thumbnail

Varargs란?

varargs(가변인자)는 JDK 5에서 새로 도입된 기능이다.

단어에서 추측할 수 있듯 인자의 수, 매개변수의 수를 가변적으로 조절할 수 있게 해주는 기능이다.

인자와 매개변수의 차이

  • 매개변수: 메소드를 선언하는 부분에 있는 변수
  • 인자: 메소드를 호출할 때 전달되는 값

사용법

사용법은 간단하다. 매개변수를 여러개 정의하고 싶은 메소드를 선언할 때, 매개변수 부분에 … 을 붙여주면 된다.

public int getSum(int... numbers) {
        return Arrays.stream(numbers).sum();
}

varargs 는 내부적으로 배열을 반환하기 때문에 Arrays.stream()을 사용할 수 있다.

내부 동작

사실 위에서 짧게 설명한 것이 전부라고 할 수 있다.

varargs를 사용하면 컴파일타임에 Object 배열이 만들어지고 varargs에 할당된 인자들이 이 배열의 원소로 들어간다.

장점

위 코드를 보자마자 이미 어떤 장점이 있는지 눈치채셨을 분들도 있을 것이다.

가장 큰 장점은 메소드의 오버로딩 필요를 줄여준다는 것이다.

public int getSum(int number1, int number2) {
	return number1 + number2;
}
public int getSum(int number1, int number2, int number3) {
	return number1 + number2 + number3;
}
...

varags 를 사용하지 않았다면 위와 같이 여러개의 메소드를 새로 정의해주어야한다.

단점

성능

위 동작 과정에서 알 수 있듯 varargs가 사용된 메소드들은 함수 실행 직전에 항상 배열이 생성된다.

이 점 때문에 여러번 실행되는 경우 매번 배열을 만드는 오버헤드가 생기기때문에 성능 문제가 발생할 수도 있다.

타입 안정성

varargs는 제네릭과 같이 사용할 때 주의해야 한다.

제네릭 또한 컴파일 타임에 타입 데이터가 제거되고 Object로 변환된다. 그리고 데이터가 사용될 때 컴파일러가 지정된 타입으로 변환시켜 사용하게 되는 방식이다.

(제네릭이 없던 이전 버전 호환성을 위해)

<T> void function(T… data) {} 

때문에 다음과 같이 사용한다면 varargs의 동작에 의해 Obejct 형식이 제네릭에 할당되어 컴파일러에게 타입 변환의 도움을 받지 못하게 된다.

public static void main(String[] args) {
        int number1 = 10;
        int number2 = 20;
        int number3 = 30;
    }

private static <T> void unsafeFunction(T... numbers) {
    Object[] data = numbers;
    data[2] = "stringData";
}

위와 같은 코드가 컴파일러의 경고도 받지 못하고 실행되어 RuntimeException이 발생한다.

제네릭에 타입제한을 걸어 위와 같은 문제를 해결할 수 있다.

private static <T extends Integer> void safeFunction(T... numbers) {
		Integer[] data = numbers;
		data[2] = "stringData";
}

리스트를 사용할 때는 조금 더 복잡해지지만 기본적으로 문제가 발생하는 원인은 동일하다.

다음과 같은 코드는 힙 오염을 발생시킨다.

	
void run() {
		List<String> strings = new ArrayList<>();
		List<String> strings2 = new ArrayList<>();
 		strings.add("stringData");
		dangerousFunction(strings, strings2);
}

void dangerousFunction(List<String>... listOfStrings) {
        List<Integer> ints = List.of(1,2);
        Object[] datas = listOfStrings;
        datas[0] = ints;                                    
        String stringData = listOfStrings[0].get(0);
        System.out.println(stringData);
  }

대신 아래와 같이 varargs 대신 List 를 사용하면 컴파일러의 도움을 받을 수 있다. 다만 이렇게 되면 인자를 전달해 줄 때 List.of 같은 팩터리 메소드를 사용하여 전달해주어야 하는 불편함을 감수해야 한다.


void run() {
		List<String> strings = new ArrayList<>();
		List<String> strings2 = new ArrayList<>();
 		strings.add("stringData");
		safeFunction(List.of(strings, strings2));
}

<T> void safeFunction(List<List<T>> listOfStrings) {
        List<Integer> ints = List.of(1,2);
        List<List<T>> datas = listOfStrings;
        datas.add(0, ints);
        String stringData = listOfStrings.get(0).get(0);
        System.out.println(stringData);
    }

정리

varargs 는 잘 사용하면 정말 편리한 기능이 될 수 있지만, 내부적인 동작과정 때문에 사용하기 전 고려해야 할 부분이 꽤 있다.

특히 제네릭과 함께 사용할 때는 타입 안정성에 대해 한 번 더 생각하고 사용하도록 하자.

JDK 7 이후 부터는 제네릭과 가변인자를 함께 사용하면 발생하는 경고를 @SafeVarargs 어노테이션으로 보이지 않게 할 수 있는데, 당연히 이 어노테이션이 붙은 메서드는 타입 안정성이 보장되어야 한다.

이에 대한 사용 가이드는 밑 링크를 참고하면 좋을 것 같다.

Improved Compiler Warnings When Using Non-Reifiable Formal Parameters with Varargs Methods

profile
https://github.com/poi1649/learning

0개의 댓글