가변인자
가변인자(Variable Arguments, 줄여서 varargs)는 자바 5에서 도입된 기능으로 메소드의 파라미터를 가변적으로 받을 수 있게 해주는 문법입니다. 메소드 오버로딩을 하기 위해서 파라미터를 바꿔가며 만드는 것이 아니라 하나의 메소드로 표현이 가능합니다.
가변인자 예시
Java의 API에서 가변인자를 사용하는 많은 메소드가 있습니다.
가장 흔한 예로는 배열을 쉽게 만드는 Arrays.asList(), 여러개의 문자열을 받아 포맷을 만드는 String.format()등의 메소드가 있습니다.
클래스에 들어가보면 메소드에서 파라미터 (...args) 형태로 인자를 받은 것을 볼 수 있습니다.
하나의 메소드에 가변인자를 적용함으로써 메소드를 하나만 적을 수 있고, 사용하는 곳에서 유연하게 인자를 처리할 수 있습니다.
가변인자 선언과 주의사항
가변인자를 선언 할 때에는 하나의 메소드에 하나의 가변인자만 사용가능합니다.
public void method(타입... 변수명) {
// 상세 메소드
}
파라미터가 하나인 경우에는 위와 같이 선언합니다.
String.format() 에서와 같이 변수와 가변인자를 모두 받아야 하는 경우, 반드시 가변인자를 제일 끝에 위치시켜야 합니다.
지켜지지 않으면 컴파일 오류가 발생합니다.
public void method(타입A 변수명1, 타입B... 변수명2) {
// 상세 메소드
}
또한 Null을 전달할 때에는 NPE에 주의하여야 합니다.
어떻게 동작하는 걸까?
메소드에 가변인자를 전달하면 메소드 호출시점에 힙(Heap) 메모리에 배열이 생성됩니다.
메소드 호출이 완료되면 더 이상 참조되지 않으므로 이 객체는 가비지 컬렉션의 대상이 됩니다.
public class VarargsMemoryExample {
public static void main(String[] args) {
exampleMethod(1, 2, 3, 4, 5);
// 메소드 호출 후 가변인자로 생성된 배열은 더 이상 참조되지 않음
// 가비지 컬렉션의 대상이 됨
}
public static void exampleMethod(int... numbers) {
// 'numbers'는 메소드 내부에서 배열로 취급됨
System.out.println("배열 길이: " + numbers.length);
// 메소드 종료 시 'numbers' 배열에 대한 참조가 사라짐
}
}
예를 들어, 위와 같이 exampleMethod 메소드가 실행될 때, int 배열 [1,2,3,4,5]을 만들어 힙 메모리에 생성합니다.
이 배열의 참조가 numbers 매개변수로 전달되고 exampleMethod 안에서 배열 사용이 가능합니다.
이후에 메소드가 종료되면 가비지 컬렉션의 대상이 되어 가비지 컬렉터가 다음에 실행될 때 힙 메모리에서 제거됩니다.
가비지 컬렉터의 실행 시점은 JVM이 정하므로 바로 삭제가 되지 않을수도 있기에 메모리 성능을 고려하여 적절하게 사용해야 합니다.\
힙 오염
일반적인 힙 오염
힙 오염이란 매개변수화된 객체가 다른 타입의 객체를 참조할 때 발생하는 현상입니다.
List rawList = new ArrayList<Integer>();
rawList.add("This is not an Integer");
List<Integer> intList = rawList;
Integer i = intList.get(0); // ClassCastException 발생
예를 들어 위와 같은 코드에서 List는 모든 List<T>의 상위타입으로 간주되므로 컴파일이 가능합니다.(IDE에서 경고를 주기는 함)
하지만 런타임에 모든 제네릭이 소거되고, intList.get()메소드를 호출할 때 ClassCastException이 발생합니다.
실제로는 String이 들어있기 때문에 Integer 값 변수에 할당할 수가 없는 것이죠.
제네릭과 가변인자 사용시
이를 확장해서 제네릭과 가변인자를 사용할 때에도 동일한 문제가 발생할 수 있습니다.
public class Main {
public static void main(String[] args) {
heapPollution(List.of("There is", "String List"));
}
static void heapPollution(List<String>... lists) {
Object[] objects = lists;
List<Integer> intList = Arrays.asList(1, 2, 3, 4);
objects[0] = intList; // heap pollution 발생
String s = lists[0].get(0); // ClassCastException 발생
}
}
- heapPollution 메소드를 호출하는 쪽에서 List<String>으로 값을 전달하면 컴파일러는 lists를 String타입의 리스트로 인식합니다.
- heapPollution 메소드 내에서 Object[] 객체를 만들어 lists를 참조하면 objects 객체의 주소값이 lists 를 바라보게 됩니다.
- Integer 타입의 리스트를 만들어 objects[0]에 전달하면, intList 참조를 바라보게 됩니다.
이 때 여전히 lists는 String타입의 리스트로 인식하고 있기 때문에 힙 오염(heap pollution)이 발생합니다.
이렇게 IDE를 통해 lists[0]의 요소를 들여다보면 String메소드를 제공하는 것을 알 수 있고,
get()을 이용해서 꺼내어 String에 담으려고 하면 ClassCastException이 발생하는 것을 확인할 수 있습니다.
@SafeVarargs 어노테이션
위의 예에서 lists 배열에 Object 배열을 참조하는 과정에서 힙 오염이 발생했습니다.
이를 예방하고 가변인자를 안전하게 사용하려면 메소드 내에서만 배열을 제한적으로 사용합니다.(타입,크기를 변경하지 않음)
혹은 읽기 전용으로만 사용하거나, 외부로 노출 시키지 않으면 됩니다.
이러한 원칙을 지켰다면 개발자가 @SafeVarags라는 어노테이션을 사용할 수 있습니다.
다시 말해 메소드가 가변인자 타입을 안전하게 사용한다고 확신할 때 사용하는데,
첫번째에 예시를 들었던 Arrays.asList()에서는 이러한 원칙을 지킨 것을 확인할 수 있습니다.
'Language > JAVA' 카테고리의 다른 글
JCF - 자바 컬렉션 프레임워크의 종류 (0) | 2024.07.24 |
---|---|
자바 제네릭(Generic) 타입에 대하여 (0) | 2024.07.23 |
String 클래스 파고들기 - 객체의 특성 및 더하기 연산의 버전별 차이 (0) | 2024.04.09 |
String 클래스 파고들기 - String 생성과 byte 변환시 주의점 (0) | 2024.04.05 |
자바가 돌아가는 동작 원리 (0) | 2024.03.06 |
남에게 설명할 때 비로소 자신의 지식이 된다.
포스팅이 도움되셨다면 하트❤️ 또는 구독👍🏻 부탁드립니다!! 잘못된 정보가 있다면 댓글로 알려주세요.