Kotlin

코틀린에서 vararg 활용하기

JParkBro 2025. 4. 2. 21:32

코틀린에서 함수를 작성할 때 가변 인자(variable arguments)를 처리해야 하는 경우가 있다.

이럴때 유용하게 사용할 수 있는 것이 바로 vararg 키워드다.

오늘은 코틀린의 vararg 에 대해 자세히 알아보려고 한다.

 

vararg란?

vararg 는 variable arguments의 줄임말로, 함수가 임의의 개수의 인자를 받을 수 있게 해주는 기능이다.

자바의 가변 인자(...)와 유사한 개념으로, 코틀린에서는 vararg 키워드를 사용해 구현한다.

 

1. 기본사용법

fun printSorted(vararg items: Int) {
    items.sort()
    println(items.contentToString())
}

fun main() {
    printSorted(6, 2, 10, 1) // [1, 2, 6, 10]
}

 

2. 스프레드 연산자(*)

이미 가지고 있는 배열을 vararg 파라미터로 전달할 때는 스프레드 연산자(*)를 사용한다.

val numbers = intArrayOf(6, 2, 10, 1)
printSorted(*numbers)
printSorted(numbers) // Error: passing IntArray instead of Int

 

스프레드는 배열을 복사하다는 점에 유의한다. 따라서 파라미터 배열의 내용을 바꿔도 원본 원소에는 영향을 미치지 않는다.

fun main() {
    val a = intArrayOf(6, 2, 10, 1)
    printSorted(*a)              // [1, 2, 6, 10]
    println(a.contentToString()) // [6, 2, 10, 1]
}

 

하지만 이때 얕은(shallow) 복사가 이뤄진다. 얕은 복사는 객체의 참조값만 복사하는 복사 방식으로, 원본 객체와 복사된 객체가 같은 메모리 위치를 참조하게 된다. 이로 인해 한쪽에서 데이터를 변경하면 다른 쪽에도 영향을 미치게 된다.

fun change(vararg items: IntArray) { 
    items[0][0] = 100 
}

fun main() {
    val a = intArrayOf(1, 2, 3)
    val b = intArrayOf(4, 5, 6)
    change(a, b)
    println(a.contentToString()) // [100, 2, 3]
    println(b.contentToString()) // [4, 5, 6]
}

 

  1. a와 b는 각각 정수 배열을 담고 있는 변수이다.
  2. change 함수를 호출할 때 a, b를 vararg 매개변수로 전달한다.
  3. vararg 는 내부적으로 배열로 처리되므로, items는 IntArray 타입의 배열이 된다. 즉 items는 [a참조, b의 참조] 를 담고 있는 배열이다.
  4. vararg 매개변수로 전달될 때 원본 배열 객체의 복사본이 생성되는 것이 아니라, 원본 배열에 대한 참조(메모리 주소)만 복사된다.
  5. items[0][0] = 100 은 a[0] = 100 과 동일한 효과를 가진다. 즉, 원본 배열 a의 첫번째 요소가 변경된다.
  6. 반면, b는 items[1]에 해당하며, 원래 값을 유지한다.

원본 배열의 모든 요소를 새 배열로 복사하는 깊은 복사는 copyOf(), toTypedArray() 등의 메서드를 사용하여 수행할 수 있다.

 

3. 다른 파라미터와 함께 사용

둘 이상을 vararg 파라미터로 선언하는 것은 금지된다. 하지만 vararg 파라미터에 콤마로 분리한 여러 인자와 스프레드를 섞어서 전달하는 것은 괜찮다. 호출 시 이런 호출은 원래의 순서가 유지 되는 단일 배열로 합쳐진다.

printSorted(6, 1, *intArrayOf(3, 8), 2) // 6, 1, 3, 8, 2 배열이 전달, [1, 2, 3, 6, 8] 반환

 

일반적으로 vararg 파라미터는 마지막에 위치시킨다. 만약 마지막에 있는 파라미터가 아니라면, vararg 파라미터 이후의 파라미터는 이름 붙은 인자로만 전달할 수 있다. vararg 파라미터도 디폴트 값과 비슷하게 마지막에 위치시키는 것이 좋은 코딩 스타일이다. vararg 파라미터를 이름 붙은 인자로 전달 할 수 없다. 단, 이름 붙은 인자에 스프레드를 사용해서 가변 인자를 전달 할 수 있다.

printSorted(items = *intArrayOf(1, 2, 3))
printSorted(items = 1, 2, 3) // Error: assigning single elements to varargs in named

 

디폴트 값이 있는 파라미터와 vararg 를 섞어 쓰는 것은 어렵다. 디폴트를 vararg 보다 앞에 두면 vararg 파라미터에 첫 번째로 전달돼야 하는 값이 디폴트가 지정된 파라미터에 전달될 값으로 간주된다. 이를 피하려면 vararg 파라미터를 이름 붙은 인자와 스프레드를 사용해 전달해야 한다. 하지만 이런 식으로 전달하는 코드는 원래 vararg 를 도입했던 목적에 위배된다.

fun printSorted(prefix: String = "", vararg items: Int) { }
fun main() {
    printSorted(6, 2, 10, 1) // Error: 6 is taken as value of prefix
    printSorted(items = *intArrayOf(6, 2, 10, 1)) // 정상

 

반면 vararg 파라미터 뒤에 디폴트 파라미터가 있는 경우에는 디폴트 파라미터를 이름 붙은 인자로 호출해야 사용할 수 있다.

fun printSorted(vararg items: Int, prefix: String = "") { }
fun main() {
    printSorted(6, 2, 10, 1, "!") // Error: type mismatch: inferred type is String but Int was expected
    printSorted(6, 2, 10, 1, prefix = "!") // 정상
}

 

4. 예제

enum class LogLevel { DEBUG, INFO, WARNING, ERROR }

class Logger(private val name: String) {
    fun log(level: LogLevel, vararg messages: String) {
        val timestamp = System.currentTimeMillis()
        for (message in messages) {
            println("[$timestamp] [$level] [$name] - $message")
        }
    }
}

val logger = Logger("UserService")
logger.log(LogLevel.INFO, "User created", "Email sent")

/**
 * 출력 결과
 * [1712043267890] [INFO] [UserService] - User created
 * [1712043267890] [INFO] [UserService] - Email sent
 */

 

sealed class UiText {
    data class DynamicString(val value: String): UiText()
    class StringResource(
        @StringRes val resId: Int,
        vararg val args: Any
    ): UiText()
    
    @Composable
    fun asString(): String {
        return when(this) {
            is DynamicString -> value
            is StringResource -> stringResource(resId, *args)
        }
    }
    
    fun asString(context: Context): String {
        return when(this) {
            is DynamicString -> value
            is StringResource -> context.getString(resId, *args)
        }
    }
}

 

String Resource 를 쉽게 관리 하는 방법이 없을까 검색을 해보다 해당 영상을 접하고, vararg 에 대해서 공부를 해보게 되었다.

 

https://www.youtube.com/watch?v=mB1Lej0aDus