🎯 1주차 Unit 2.2 — 가변인자 (Varargs)

Psj·2026년 5월 6일

F-lab

목록 보기
24/230

🎯 Unit 2.2 — 가변인자 (Varargs)

F-lab Java 1주차 / Phase 2 / Unit 2.2 본격 학습 자료
9-섹션 마스터 프롬프트 형식으로 깊이 파헤친다.

선수 지식: Unit 2.1 (메서드의 구조)
다음 Unit: 2.3 — 상속과 생성자 체이닝


🌍 1. 세상 속 비유

가변인자 = "마트 계산대"

마트 계산대에서 결제할 때를 생각해보세요. 손님마다 사는 물건의 개수가 다릅니다:

  • 손님 A: 사과 1개만 (1개)
  • 손님 B: 사과 1개, 우유 2개 (2개)
  • 손님 C: 사과 1개, 우유 2개, 빵 3개, 치즈 4개, 라면 1개 (5개)
  • 손님 D: 그냥 둘러보다 그냥 감 (0개)

계산원이 일하는 방식:

  • 손님이 카트에 담아온 모든 물건 을 받음
  • 개수가 1개든 100개든 상관없음
  • 계산원에게 가서 "물건들 결제해주세요" 한 마디

만약 계산원이 이런 식이라면 끔찍할 것:

  • "1개짜리 계산대"
  • "2개짜리 계산대"
  • "3개짜리 계산대"
  • ... "100개짜리 계산대"

가변인자는 "개수에 상관없이 받아주는 계산대" 의 정신.


더 자바스러운 비유 — System.out.printf

자바를 써본 사람이라면 이미 가변인자를 매일 쓰고 있습니다:

System.out.printf("이름: %s%n", "Alice");
System.out.printf("이름: %s, 나이: %d%n", "Alice", 25);
System.out.printf("좌표: (%d, %d, %d)%n", 1, 2, 3);

같은 printf 메서드인데 인자 개수가 다 다름. 어떻게 가능할까요?

printf 의 시그니처:

public PrintStream printf(String format, Object... args) { ... }
//                                      ↑ 가변인자

Object... args"몇 개든 받아주겠다" 는 선언.

이게 가변인자(varargs).


비유의 핵심

비유 요소자바 가변인자
마트 계산대가변인자 메서드
카트의 물건들가변인자로 전달되는 인자들
1개든 100개든 OK0개부터 N개까지 자유
한 줄에 모든 손님 처리한 메서드로 모든 시나리오 처리

🔥 2. 탄생 배경

가변인자 없던 시절 — 오버로딩 지옥

자바 1.4 이전 (2004년 이전), 가변인자가 없었습니다. 인자 개수가 다양한 메서드를 만들려면?

방법 1 — 오버로딩 폭증:

public class Logger {
    public void log(String msg1) {
        System.out.println(msg1);
    }
    
    public void log(String msg1, String msg2) {
        System.out.println(msg1 + " " + msg2);
    }
    
    public void log(String msg1, String msg2, String msg3) {
        System.out.println(msg1 + " " + msg2 + " " + msg3);
    }
    
    public void log(String msg1, String msg2, String msg3, String msg4) {
        System.out.println(...);
    }
    
    // ... 5개, 6개, 7개... 끝없이 ❌
}

문제:

  • 사용자가 5개 메시지 전달하면? 메서드 없음 → 컴파일 에러
  • 100개 받는 메서드 만들 수도 없음
  • 같은 코드의 반복

방법 2 — 배열로 받기:

public class Logger {
    public void log(String[] messages) {
        for (String msg : messages) {
            System.out.println(msg);
        }
    }
}

// 사용 — 매번 배열을 만들어야 함 ❌
logger.log(new String[]{"hello"});
logger.log(new String[]{"hello", "world"});
logger.log(new String[]{"a", "b", "c", "d"});

문제:

  • 호출할 때마다 new String[]{...} 작성 — 번거로움
  • "그냥 인자만 넘기고 싶은데" 라는 자연스러운 욕구 불만

Java 5의 등장 (2004) — 가변인자 도입 ⭐

Java 5 에서 ... 문법 이 등장:

public class Logger {
    public void log(String... messages) {  // ← 가변인자
        for (String msg : messages) {
            System.out.println(msg);
        }
    }
}

// 사용 — 자연스러움 ✅
logger.log("hello");
logger.log("hello", "world");
logger.log("a", "b", "c", "d");
logger.log();  // 0개도 OK!

효과:

  • 오버로딩 폭증 해결
  • 배열 명시적 생성 불필요
  • 한 메서드가 다양한 시나리오 처리

→ Java 5는 가변인자 외에도 제네릭, 어노테이션, enum 등 자바 역사의 분수령.


핵심 통찰

"가변인자는 '메서드의 입력 유연성' 을 극대화하기 위한 문법적 설탕(syntactic sugar)이다."

내부적으로는 여전히 배열 입니다. 단지 호출하는 사용자가 편하게 쓰도록 해준 것뿐.


💣 3. 없으면 생기는 문제

가변인자가 없을 때 어떤 불편함이 있는지 실제 시나리오 로 보겠습니다.

시나리오 1: ILIC 운임 검색 API

운임 견적 검색에서 다양한 조건을 받고 싶다고 합시다:

  • 최소 가격으로만 검색
  • 최소 + 최대 가격으로 검색
  • 최소 + 최대 + 통화 + 출발지 + 도착지로 검색
  • ... 다양한 조합

가변인자 없이 — 오버로딩 폭증

public class FareSearchService {
    
    public List<Fare> search(int minPrice) { ... }
    public List<Fare> search(int minPrice, int maxPrice) { ... }
    public List<Fare> search(int minPrice, int maxPrice, String currency) { ... }
    public List<Fare> search(int minPrice, int maxPrice, String currency, String origin) { ... }
    public List<Fare> search(int minPrice, int maxPrice, String currency, String origin, String dest) { ... }
    // ... 시나리오 추가될 때마다 메서드 추가 ❌
    
    public List<Fare> search(int maxPrice, boolean isMaxOnly) {
        // "최대 가격만으로 검색" 시나리오 — 매개변수가 같은 타입이라 별도 처리 ❌
    }
}

문제:
1. 메서드 폭증 — 조합마다 새 메서드
2. 타입이 같으면 모호함 (위 코드의 마지막 메서드 참고)
3. 유지보수 지옥 — 검색 로직 변경 시 모든 메서드 수정


가변인자 없이 — 배열 강제

public class FareSearchService {
    public List<Fare> search(SearchCriteria[] criteria) { ... }
}

// 사용 — 배열 매번 생성 ❌
service.search(new SearchCriteria[]{
    new SearchCriteria("price", ">=", 1000)
});

service.search(new SearchCriteria[]{
    new SearchCriteria("price", ">=", 1000),
    new SearchCriteria("price", "<=", 5000)
});

// "조건 없이 전체 검색"
service.search(new SearchCriteria[0]);  // 빈 배열 ❌ 어색함

문제:
1. 호출 시 노이즈new SearchCriteria[]{...} 매번
2. 빈 인자 표현 어색 — new SearchCriteria[0]
3. 가독성


가변인자 도입 — 깔끔한 해결

public class FareSearchService {
    public List<Fare> search(SearchCriteria... criteria) { ... }
}

// 사용 — 자연스러움 ✅
service.search();  // 조건 없음 → 전체 검색
service.search(priceMin);
service.search(priceMin, priceMax);
service.search(priceMin, priceMax, currency, origin, dest);

효과:

  • 메서드 1개로 모든 시나리오 처리
  • 호출이 자연스러움
  • 0개 인자도 가능

자바 표준 라이브러리의 가변인자 활용 ⭐

가변인자가 없으면 자바 자체가 매우 불편할 것입니다:

// 1. printf 계열
System.out.printf("이름: %s, 나이: %d", name, age);

// 2. String.format
String.format("Hello, %s!", "Alice");

// 3. List.of, Set.of, Map.of (Java 9+)
List.of("a", "b", "c");
Set.of(1, 2, 3, 4, 5);

// 4. Arrays.asList
Arrays.asList("x", "y", "z");

// 5. String.join
String.join(", ", "a", "b", "c");  // "a, b, c"

// 6. Stream.of
Stream.of(1, 2, 3, 4, 5);

모두 가변인자 덕분. 없었다면 이 모든 게 배열을 매번 만들어야 했을 것.


✅ 4. 해결책 — 가변인자 문법

기본 문법

[접근제어자] 반환타입 메서드명(타입... 변수명) {
    // 변수명은 배열처럼 다룸
}

핵심 표시: ... (점 3개)


실제 예시

public class Logger {
    public void log(String... messages) {
        // messages는 String[] 배열로 다룸
        System.out.println("받은 메시지 개수: " + messages.length);
        for (String msg : messages) {
            System.out.println("- " + msg);
        }
    }
}

Logger logger = new Logger();
logger.log();                          // 0개
logger.log("hello");                   // 1개
logger.log("hello", "world");          // 2개
logger.log("a", "b", "c", "d", "e");   // 5개

출력:

받은 메시지 개수: 0

받은 메시지 개수: 1
- hello

받은 메시지 개수: 2
- hello
- world

받은 메시지 개수: 5
- a
- b
- c
- d
- e

메서드 안에서는 배열처럼 다룸

public void process(int... numbers) {
    // numbers는 int[]
    
    // 1. length 사용 가능
    System.out.println("개수: " + numbers.length);
    
    // 2. 인덱스 접근 가능
    if (numbers.length > 0) {
        System.out.println("첫 번째: " + numbers[0]);
    }
    
    // 3. for-each 가능
    for (int n : numbers) {
        System.out.println(n);
    }
    
    // 4. for 루프 가능
    for (int i = 0; i < numbers.length; i++) {
        System.out.println(i + ": " + numbers[i]);
    }
    
    // 5. Arrays 유틸 사용 가능
    int sum = Arrays.stream(numbers).sum();
}

호출하는 쪽은 인자 나열, 받는 쪽은 배열 처리.


가변인자 + 일반 매개변수 혼합 ⭐

가변인자는 다른 매개변수와 함께 사용 가능:

public class Logger {
    public void log(String level, String... messages) {
        for (String msg : messages) {
            System.out.println("[" + level + "] " + msg);
        }
    }
}

logger.log("INFO");                              // 0개 메시지
logger.log("INFO", "시작");                       // 1개
logger.log("ERROR", "DB 연결 실패", "재시도 중");   // 2개
logger.log("DEBUG", "a", "b", "c");              // 3개

핵심 규칙 ⭐ (면접 단골)

규칙 1: 가변인자는 마지막 매개변수 여야 함

public void log(String level, String... messages) { ... }  // ✅ 마지막

public void log(String... messages, String level) { ... }  // ❌ 컴파일 에러

왜?: 자바가 어디까지가 가변인자인지 구별 불가.

logger.log("INFO", "msg1", "msg2", "ERROR");
// "INFO"가 첫 인자? "ERROR"가 level? 자바가 모름

규칙 2: 가변인자는 메서드당 1개만 가능

public void method(String... s, int... i) { ... }  // ❌ 컴파일 에러

위 규칙 1과 같은 이유 — 어디까지가 첫 가변인자, 어디부터 두 번째인지 모름.


규칙 3: 0개부터 N개까지 자유

public void log(String... messages) { ... }

logger.log();           // 0개 OK
logger.log("a");        // 1개 OK
logger.log("a", "b");   // 2개 OK
// 100개도 OK

🏗️ 5. 내부 동작 원리

가변인자는 사실 배열이다 ⭐⭐ (면접 단골)

"가변인자는 컴파일러가 자동으로 배열로 변환해주는 문법적 설탕(syntactic sugar)"

실제 동작:

// 우리가 작성한 코드
public void log(String... messages) {
    System.out.println(messages.length);
}

logger.log("a", "b", "c");

컴파일러가 변환한 코드:

public void log(String[] messages) {  // ← 사실은 배열
    System.out.println(messages.length);
}

logger.log(new String[]{"a", "b", "c"});  // ← 호출 시 자동으로 배열 생성

JVM 입장에서는 가변인자와 배열이 완전히 동일.


메모리 관점

logger.log("a", "b", "c");

JVM 내부 흐름:

1. 호출 시점:
   - JVM이 "a", "b", "c"를 담은 String[] 배열을 Heap에 생성
   - 그 배열의 참조를 log 메서드의 messages 매개변수로 전달

2. 메서드 안에서:
   - messages는 그 배열을 가리키는 참조
   - 배열의 모든 메서드/속성 사용 가능

3. 메서드 종료 후:
   - 배열은 더 이상 참조되지 않음
   - 다음 GC에서 수거됨

가변인자 호출은 매번 새 배열 생성 (성능 영향 가능 — 7번 섹션에서 자세히)


가변인자에 배열을 직접 넘길 수도 있다 ⭐

public void log(String... messages) { ... }

// 1. 일반적인 호출 — 인자 나열
logger.log("a", "b", "c");

// 2. 배열을 직접 전달
String[] arr = {"a", "b", "c"};
logger.log(arr);  // ✅ 가능

// 3. new로 만든 배열 직접 전달
logger.log(new String[]{"a", "b", "c"});  // ✅ 가능 (가변인자 없을 때 방식)

왜?: 어차피 내부적으로 배열이라서.


null 전달 시 함정 ⚠️

public void log(String... messages) {
    System.out.println(messages.length);  // ⚠️
}

// 1. null 직접 전달
logger.log(null);  // → messages가 null
                   // → messages.length → NullPointerException!

// 2. null 인자
logger.log("a", null, "b");  // → messages = ["a", null, "b"]
                              // → messages.length = 3 (OK)
                              // → messages[1] 사용 시 NullPointerException 가능

// 3. 빈 배열과 다름
logger.log(new String[0]);  // → messages = [] (빈 배열)
                            // → messages.length = 0

안전한 코드:

public void log(String... messages) {
    if (messages == null) {
        return;  // 또는 빈 배열로 처리
    }
    for (String msg : messages) {
        if (msg != null) {
            System.out.println(msg);
        }
    }
}

오토박싱과 가변인자 ⚠️

public void process(Integer... nums) { ... }

process(1, 2, 3);  // int → Integer 자동 박싱
                    // → new Integer[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)}
                    // → 3번의 박싱 발생 (성능 영향)

대안 — 기본형 가변인자:

public void process(int... nums) { ... }

process(1, 2, 3);  // → new int[]{1, 2, 3}
                    // → 박싱 없음 ✅

기본형이 가능한 시나리오는 기본형 가변인자 권장.


💻 6. 실전 코드 예시

ILIC 운임 시스템에서 가변인자를 다양하게 활용하는 예시들.

예시 1: 로깅 메서드 — 가장 흔한 활용

public class IlicLogger {
    
    public void info(String message, Object... args) {
        String formatted = String.format(message, args);
        System.out.println("[INFO] " + formatted);
    }
    
    public void error(String message, Throwable error, Object... args) {
        String formatted = String.format(message, args);
        System.err.println("[ERROR] " + formatted);
        error.printStackTrace();
    }
}

// 사용
logger.info("운임 생성 완료");
logger.info("운임 ID: %d", 100);
logger.info("고객 %s, 금액 %d원, 통화 %s", "Alice", 50000, "KRW");

logger.error("DB 연결 실패", exception);
logger.error("운임 %d 처리 실패: %s", exception, fareId, reason);

→ SLF4J, Log4j 모두 이런 패턴 사용.


예시 2: 검증 유틸리티

public class FareValidator {
    
    public static void validateNotNull(String fieldName, Object... values) {
        for (int i = 0; i < values.length; i++) {
            if (values[i] == null) {
                throw new IllegalArgumentException(
                    fieldName + "[" + i + "] 는 null일 수 없습니다"
                );
            }
        }
    }
    
    public static int sum(int... numbers) {
        int total = 0;
        for (int n : numbers) {
            total += n;
        }
        return total;
    }
    
    public static int max(int first, int... rest) {
        // 최소 1개는 필수, 나머지는 선택
        int max = first;
        for (int n : rest) {
            if (n > max) max = n;
        }
        return max;
    }
}

// 사용
FareValidator.validateNotNull("고객 정보", customerId, customerName, email);
int total = FareValidator.sum(1000, 2000, 3000);  // 6000
int largest = FareValidator.max(10, 5, 8, 12, 3);  // 12
FareValidator.max(10);  // 10 (rest는 빈 배열)

max(int first, int... rest) 패턴 ⭐ :

  • 첫 인자는 필수
  • 나머지는 선택
  • 컴파일 시점에 "최소 1개는 필요" 강제 가능

예시 3: 빌더 + 가변인자 조합

public class FareQuery {
    private final List<String> conditions = new ArrayList<>();
    
    public FareQuery in(String field, Object... values) {
        if (values.length == 0) {
            return this;  // 무시
        }
        String inClause = field + " IN (" + 
            Arrays.stream(values).map(v -> "?").collect(Collectors.joining(",")) +
            ")";
        conditions.add(inClause);
        return this;
    }
    
    public FareQuery between(String field, Object min, Object max) {
        conditions.add(field + " BETWEEN ? AND ?");
        return this;
    }
    
    public String build() {
        return "WHERE " + String.join(" AND ", conditions);
    }
}

// 사용
FareQuery query = new FareQuery()
    .in("status", "DRAFT", "SUBMITTED", "PAID")  // 가변인자
    .in("currency", "KRW")                        // 1개도 OK
    .between("amount", 1000, 100000);

String sql = query.build();
// "WHERE status IN (?,?,?) AND currency IN (?) AND amount BETWEEN ? AND ?"

예시 4: 메시지 포맷팅

public class NotificationService {
    
    public void sendToCustomers(String template, Customer... customers) {
        for (Customer customer : customers) {
            String message = template.replace("{name}", customer.getName());
            send(customer, message);
        }
    }
    
    private void send(Customer customer, String message) {
        // 발송 로직
    }
}

// 사용
service.sendToCustomers("안녕하세요 {name}님", alice);
service.sendToCustomers("이벤트 안내 {name}", alice, bob, charlie);

// 또는 Customer 배열을 직접
Customer[] vips = customerRepository.findVips();
service.sendToCustomers("VIP {name}님께", vips);  // 배열 직접 전달 OK

예시 5: 자바 표준 API의 가변인자 활용

// String.format
String msg = String.format("이름: %s, 나이: %d, 직업: %s", "Alice", 25, "개발자");

// List.of (Java 9+) — 불변 리스트
List<String> tags = List.of("urgent", "shipping", "international");

// Arrays.asList
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Stream.of
Stream<String> stream = Stream.of("a", "b", "c");

// String.join
String joined = String.join(", ", "apple", "banana", "cherry");
// "apple, banana, cherry"

// Path.of (Java 11+)
Path p = Path.of("home", "user", "documents", "file.txt");

// Collections.addAll
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");

자바를 잘 쓰려면 가변인자는 필수.


⚠️ 7. 주의사항 & 흔한 실수

실수 1: 가변인자 위치를 잘못 둠

// ❌ 컴파일 에러
public void log(String... messages, String level) { ... }

// ✅ 마지막에 위치
public void log(String level, String... messages) { ... }

규칙: 가변인자는 무조건 마지막.


실수 2: 가변인자를 두 개 사용

// ❌ 컴파일 에러
public void method(String... names, int... ages) { ... }

// ✅ 다른 방법으로 해결
public void method(String[] names, int[] ages) { ... }
public void method(List<String> names, List<Integer> ages) { ... }

실수 3: 오버로딩과의 모호함 ⚠️

public class Service {
    public void process(int x) { System.out.println("int"); }
    public void process(int... nums) { System.out.println("varargs"); }
}

Service s = new Service();
s.process(1);  // ⚠️ "int" 출력 — 정확한 매칭이 우선
s.process(1, 2);  // "varargs"
s.process();  // "varargs"

규칙 ⭐ :

  • 정확한 시그니처가 가변인자보다 우선 매칭
  • 그래서 오버로딩과 가변인자를 섞으면 혼란

권장: 가변인자와 일반 메서드를 같은 이름으로 오버로딩하지 말 것.


실수 4: 배열을 "한 인자" 로 넘기고 싶을 때

public void log(Object... args) {
    System.out.println("개수: " + args.length);
}

String[] arr = {"a", "b", "c"};
logger.log(arr);  // ⚠️ args.length = 3 (배열의 요소들)

왜?: String[]Object[] 호환 → 배열 자체가 가변인자로 풀림.

해결 — 배열을 한 인자로 명시적 전달:

logger.log((Object) arr);  // 캐스팅으로 배열 자체를 한 인자로
// → args.length = 1, args[0] = arr

→ 헷갈리는 케이스. 면접에서도 가끔 출제.


실수 5: null 처리 누락

public void log(String... messages) {
    for (String msg : messages) {  // messages가 null이면 NPE!
        System.out.println(msg);
    }
}

logger.log(null);  // 💥 NullPointerException

해결:

public void log(String... messages) {
    if (messages == null || messages.length == 0) {
        return;  // 또는 기본 동작
    }
    for (String msg : messages) {
        if (msg != null) {
            System.out.println(msg);
        }
    }
}

실수 6: 성능을 고려 안 함 ⚠️

가변인자는 호출할 때마다 새 배열 생성:

public void log(Object... args) { ... }

// 매 호출마다 new Object[]{...} 생성 (Heap 사용)
for (int i = 0; i < 1000000; i++) {
    logger.log("msg", i, "data");  // 100만 번의 배열 생성
}

고성능이 필요한 핫 패스(hot path)에서는:

  • 가변인자 대신 오버로딩 (자주 쓰는 케이스만)
  • 또는 String.format 대신 StringBuilder
// 자주 쓰는 케이스 오버로딩
public void log(String message) { ... }       // 1개 — 배열 생성 X
public void log(String m1, String m2) { ... } // 2개 — 배열 생성 X
public void log(String.<.. messages) { ... }   // N개 — 배열 생성

로깅 라이브러리 (SLF4J 등) 가 정확히 이 패턴을 사용.


실수 7: 가변인자에 의존한 API 설계

// ⚠️ 의도가 불명확
public Fare create(String... params) {
    // params[0] = customerId
    // params[1] = amount
    // params[2] = currency
    // ... 호출자가 순서를 외워야 함
}

fareService.create("1", "50000", "KRW");  // ❌ 의도 불명확

해결 — 객체로 명확히:

public Fare create(FareCreateRequest request) {
    // 명확한 필드명
}

fareService.create(new FareCreateRequest(1L, 50000, "KRW"));

가변인자는 "동질적 데이터" 일 때만. 의미가 다른 값들은 객체로.


🔗 8. 연관 개념 맵

직접 이어지는 학습

[Unit 2.1: 메서드의 구조]
        ↓
[Unit 2.2: 가변인자]  ← 지금 여기
        ↓
[Unit 2.3: 상속과 생성자 체이닝]
        ↓
[Unit 2.4: 다형성]

이 Unit의 개념이 활용되는 곳

1주차 내:

  • Phase 6 (컬렉션): List.of(...), Set.of(...), Arrays.asList(...) 모두 가변인자
  • Phase 7 (I/O): Path.of("home", "user", "file") 같은 패턴

미래 주차:

  • 3주차 (제네릭): <T> T method(T... values) 같은 제네릭 가변인자
  • 3주차 (람다/스트림): Stream.of(1, 2, 3), Collectors.toMap(...)
  • 5주차 (Spring): @RequestMapping(value = {...}, method = {...}) 같은 어노테이션 가변값
  • 6주차 (테스트): assertThat(list).contains("a", "b", "c") 같은 assertion
  • 15주차 (Spring MVC): @PathVariable, @RequestParam 의 다중 값 처리

가변인자 vs 컬렉션

언제 무엇을?

상황추천
호출 시 인자를 직접 나열가변인자 (method("a", "b", "c"))
이미 List/배열이 있음컬렉션 매개변수 (method(List<String>))
둘 다 자주 발생가변인자 (배열 직접 전달도 가능)
매우 빈번한 호출 (성능 중요)일반 매개변수 또는 오버로딩

유연한 패턴 — 가변인자가 둘 다 받음:

public void process(String... items) { ... }

process("a", "b", "c");           // 직접 나열
process(myList.toArray(new String[0]));  // 배열로 변환 후 전달

면접 단골 질문 매핑

질문이 Unit에서의 답
"가변인자란?"타입... 변수명 형식, 메서드 안에서는 배열로 다룸
"가변인자 위치 규칙?"마지막에 1개만
"내부적으로는?"컴파일 시 배열로 변환되는 syntactic sugar
"배열 매개변수와 차이?"호출 편의성 (호출자가 배열 안 만들어도 됨)
"0개 인자도 가능?"YES — 빈 배열로 처리됨

📝 9. 핵심 요약 — 3줄 정리

1️⃣ 가변인자는 "메서드의 입력 유연성을 위한 문법적 설탕" 이다.

타입... 변수명 으로 선언하면 0개부터 N개까지 자유롭게 인자를 받을 수 있다. 내부적으로는 배열로 변환 되므로, JVM 입장에서는 가변인자와 배열이 완전히 동일하다. 호출자의 편의를 위한 문법.

2️⃣ 2가지 핵심 규칙: 마지막에, 1개만.

가변인자는 매개변수 목록의 마지막 에만 올 수 있고, 메서드당 1개만 가능하다. 이 두 규칙은 자바 컴파일러가 어디까지가 가변인자인지 구별하기 위함이다. 이걸 어기면 컴파일 에러.

3️⃣ 편하지만 함정도 있다 — null, 성능, 모호함.

null 직접 전달 시 NPE, 매 호출마다 배열 생성으로 성능 영향, 오버로딩과 섞으면 매칭 우선순위 헷갈림. 의미가 다른 값들은 객체로 묶는 게 좋고, 동질적 데이터 일 때만 가변인자가 자연스럽다.


🎓 학습 자기 점검

기본 이해

  • 가변인자 문법 (타입... 변수명) 을 작성할 수 있다
  • 가변인자는 메서드 안에서 배열로 다룬다는 사실을 안다
  • 가변인자 2가지 핵심 규칙 (마지막, 1개만) 을 말할 수 있다
  • 0개 인자도 가능하다는 사실을 안다

실전 적용

  • ILIC 코드에서 가변인자가 적합한 메서드를 찾을 수 있다
  • String.format, List.of 등 자바 표준 API의 가변인자를 활용할 수 있다
  • 의미가 다른 값들과 동질적 값들을 구별해서 매개변수 설계할 수 있다

면접 대비 (1-2분 답변)

  • "가변인자란 무엇인가?" 답변 가능
  • "가변인자와 배열 매개변수의 차이?" 답변 가능
  • "내부 동작 원리?" 답변 가능 (배열로 변환되는 syntactic sugar)

자기 점검 질문 답변

Q1: void log(String... args)void log(String[] args) 의 차이는?

호출 측면 (가장 큰 차이):

// 가변인자 — 자연스러움
log("a", "b", "c");

// 배열 매개변수 — 매번 배열 생성 필요
log(new String[]{"a", "b", "c"});

메서드 안에서는 동일 — 둘 다 String[] 로 다룸:

public void log(String... args) {
    System.out.println(args.length);  // OK
    for (String s : args) { ... }      // OK
}

public void log(String[] args) {
    System.out.println(args.length);  // 동일
    for (String s : args) { ... }      // 동일
}

JVM 관점:

  • 컴파일 후 둘 다 String[] 으로 변환됨
  • 시그니처도 거의 같음 (가변인자는 ACC_VARARGS 플래그가 추가될 뿐)
  • 그래서 둘을 같은 메서드명으로 오버로딩 불가:
// ❌ 컴파일 에러 — 같은 시그니처로 인식
public void log(String... args) { ... }
public void log(String[] args) { ... }

결론:

  • 호출 편의성 차이만 있고
  • 본질은 같다 (배열)
  • 호출자가 인자를 나열할 수 있게 해주는 게 가변인자

Q2: 가변인자는 매개변수 목록의 어느 위치에 와야 하는가?

반드시 마지막 위치.

// ✅ OK
public void method(int a, String... rest) { ... }
public void method(int a, double b, Object... rest) { ... }

// ❌ 컴파일 에러
public void method(String... rest, int a) { ... }
public void method(int a, String... rest, int b) { ... }

왜?:

  • 자바가 어디까지가 가변인자인지 구별 못 함
  • 예: method("a", "b", 1) — "a", "b" 가 가변인자? 아니면 "a" 만?
  • 마지막에 두면 명확: 앞의 인자들은 일반 매개변수, 뒤는 모두 가변인자

또한 — 메서드당 가변인자는 1개만:

// ❌ 컴파일 에러
public void method(String... s, int... i) { ... }

같은 이유.


다음 Unit으로

  • 상속과 생성자 체이닝 을 학습할 준비 완료
  • extends, super() 같은 키워드의 의미가 궁금하다

profile
Software Developer

0개의 댓글