Variable number of Arguments
가변 인자
인자의 갯수를 가변적으로 허용할 때 쓰이는 키워드이다.
아니, 인자 갯수를 왜 가변적으로 허용해야 하냐구?
바로 코드로 들어가보자
String Concatenate를 수행하는 Singleton Object를 생성하여
띄어쓰기로 구분된 합쳐진 문자열을 리턴하는 기능을 구현해본다.
class Concat {
companion object Concatenate{
fun concatWithStringArray (delimiter : String = " ", stringArray : Array<String>) : String {
val stringBuilder = StringBuilder()
for(str : String in stringArray) {
stringBuilder.append(str)
.append(delimiter)
}
return stringBuilder.toString()
}
}
}
import kotlin.*
fun main(args: Array<String>) {
// 띄어쓰기를 구분자로 사용.
val delimiter = " "
val firstString = "I"
val secondString = "\uD83D\uDC93"
val thirdString = "Kotlin"
// stringArray를 생성
val stringArray : Array<String> = arrayOf(firstString, secondString, thirdString)
// 인자로 string array 를 넘김
val result = Concat.concatWithStringArray(delimiter, stringArray)
println("======string concatenation using varargs result======")
print(result)
}
결과를 출력하는데는 아무 문제 없지만 이 부분이 맘에 들지 않는다.
val firstString = "I"
val secondString = "\uD83D\uDC93"
val thirdString = "Kotlin"
// stringArray를 생성
val stringArray : Array<String> = arrayOf(firstString, secondString, thirdString)
문자열 덩어리를 넘기기위해 main에서 배열을 따로 생성한다니..?
배열을 생성하고 인자로 넘기지 않고도 처리할 수 있는 방법,
인자 갯수가 가변적인 그놈 varargs를 사용해보자
class Concat {
companion object Concatenate{
fun concatWithDelimiter (delimiter : String = " ", vararg stringArgs: String) : String {
// StringBuilder creates a single internal buffer that contains the final string.
val stringBuilder = StringBuilder()
for(str : String in stringArgs) {
stringBuilder.append(str)
.append(delimiter)
}
return stringBuilder.toString()
}
}
}
fun main(args: Array<String>) {
// 띄어쓰기를 구분자로 사용.
val delimiter = " "
val firstString = "I"
val secondString = "\uD83D\uDC93"
val thirdString = "Kotlin"
val result = Concat.concatWithDelimiter(delimiter, firstString, secondString, thirdString)
println("======string concatenation using varargs result======")
print(result)
}
결과는 같지만 더 이상 메인에서
입력 인자 갯수를 신경쓸 필요가 없다.
👉 main에서 별도로 배열을 생성해서 넘길 필요가 없다.
➕💁🏼♂️코틀린 공식 문서에 의하면 다음과 같은 문구를 확인할 수 있다.
Inside a function a vararg-parameter of type T is visible as an array of T, i.e. the ts variable in the example above has type Array.
👉 varargs를 통해 받아온 인자들을 내부적으로 배열을 생성하여 처리한다.
아까 사용했던 코드를 다시 살펴보자.
fun concatWithDelimiter (delimiter : String = " ",
vararg stringArgs: String) : String {
// stringArgs 는 언뜻 보기에 String Type 으로 보이지만
// 내부적으로 Array<String> 을 생성하여 받는다.
// 따라서 다음과 같은 for loop 을 그대로 사용할 수 있다.
for(str : String in stringArgs) {
// 여기서 stringArgs 타입은 Array<String> 이다!
//(중략..)
}
//(중략..)
➕💁♂️ 배열도 인자로 넘길 수 있다.
When we call a vararg-function, we can pass arguments one-by-one, e.g. asList(1, 2, 3), or, if we already have an array and want to pass its contents to the function, we use the spread operator (prefix the array with * )
👉 spread operator '*'를 사용하면 배열도 인자로 넘길 수 있다.
fun main(args: Array<String>) {
// 띄어쓰기를 구분자로 사용.
val delimiter = " "
val firstString = "I"
val secondString = "\uD83D\uDC93"
val thirdString = "Kotlin"
val stringArray = arrayOf("it's", "real", "that")
// stringArray (배열)도 인자로 넘어간다.
val result = Concat.concatWithDelimiter(delimiter, *stringArray, firstString, secondString, thirdString)
println("======string concatenation using varargs result======")
print(result)
}
-
꿀처럼 보이는 varargs 에도 주의할 점이 있는데
바로 method overloading 할 때와 인자 순서이다.
- varargs 인자 순서를 맨끝으로 해야만 정상적으로 동작한다.
- method overloading 시 모든 인자의 타입이 같은 경우 에러가 발생한다
class Concat {
companion object Concatenate{
fun concatWithDelimiter (delimiter : String = " ", vararg stringArgs: String) : String {
val stringBuilder = StringBuilder()
for(str : String in stringArgs) {
stringBuilder.append(str)
.append(delimiter)
}
return stringBuilder.toString()
}
//@Overload
fun concatWithDelimiter (vararg stringArgs: String) : String {
//중략..
}
// (생략..)
Compile Error
🚨Overload resolution ambiguity. All these functions match.
public final fun concatWithDelimiter(vararg stringArgs: String): String defined in Concat.Concatenate
public final fun concatWithDelimiter(delimiter: String = ..., vararg stringArgs: String): String defined in Concat.Concatenate
컴파일러가 인자를 구분할 수 없다.
delimiter 및 stringArgs가 모두 String type 이므로 컴파일러 입장에서는 어디까지가 delimiter이고 어디가 stringArgs 인지 구분하지 못한다.
📝 이러한 원리로 컴파일러를 위해(?) varargs 인자는 맨 끝자리로 정의하고, 왠만하면 오버로딩을 지양해야한다.
https://kotlinlang.org/docs/reference/functions.html#variable-number-of-arguments-varargs