[Kotlin] varargs

Falcon·2020년 12월 11일
1

varargs

Variable number of Arguments
가변 인자

When to use?

인자의 갯수를 가변적으로 허용할 때 쓰이는 키워드이다.

아니, 인자 갯수를 왜 가변적으로 허용해야 하냐구?
바로 코드로 들어가보자

Example Code

String Concatenate를 수행하는 Singleton Object를 생성하여
띄어쓰기로 구분된 합쳐진 문자열을 리턴하는 기능을 구현해본다.

[개선 전 코드]

Concat.kt

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()
        }
    }
}

main.kt

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를 사용해보자

[개선 후 코드]

Concat.kt

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()
        }
    }
}

main.kt

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 '*'를 사용하면 배열도 인자로 넘길 수 있다.

main.kt

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 할 때와 인자 순서이다.

  1. varargs 인자 순서를 맨끝으로 해야만 정상적으로 동작한다.
  2. 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 인자는 맨 끝자리로 정의하고, 왠만하면 오버로딩을 지양해야한다.


Reference

https://kotlinlang.org/docs/reference/functions.html#variable-number-of-arguments-varargs

profile
I'm still hungry

0개의 댓글

관련 채용 정보