Kotlin Default Argument의 동작 원리
Default Arguments
함수를 호출할 때 명시적으로 인자(Argument) 지정할 필요가 없는 것을 Default Argument라고 한다.
인자를 전달하지 않고 함수를 호출하면 Default Argument가 Function Parameter로 사용된다. 다른 경우에는 함수 호출 중에 argument가 전달되면 전달된 argument가 Function Parameter로 사용된다.
Function의 매개 변수는 해당 인자를 건너뛸 때 사용되는 기본 값을 가질 수 있다.
이것은 오버로딩의 수를 줄여준다.
ex)
fun method(arg1: Int) { /*...*/ } fun method(arg1: Int, arg2: Int) { /*...*/ }
Default Parameter는
parameter name: type = value
로 아래와 같이 사용이 가능하다.fun method(arg1: Int, arg2: Int = 0) { // ... }
Default Argument가 사용되는 3가지 경우
Default Argument가 지정된 함수를 호출하는 동안
- 인자가 전달되지 않는 경우
- 함수를 호출하는 동안 인자가 전달되지 않으면 Default Argument가 함수 매개변수로 사용된다. 함수를 정의하는 동안 변수를 초기화해야 한다.
fun main() { method() } fun method(arg1: Int = 1, arg2: Int = 2) { /* ... */ println("arg1 = ${arg1}") println("arg2 = ${arg2}") println("인자가 전달되지 않는 경우") } // 출력 결과 arg1 = 1 arg2 = 2 인자가 전달되지 않는 경우
- 부분 인자가 전달되는 경우
여기서 부분 인자는 함수를 호출하는 동안 전달되고 이들은 함수 매개 변수로 사용된다. 함수 호출에서 값을 가져오지 않으면 해당 매개변수에 대해 Default Value가 사용된다.
fun main() { val arg1 = 3 method(arg1) } fun method(arg1: Int = 1, arg2: Int = 2) { /* ... */ println("arg1 = ${arg1}") println("arg2 = ${arg2}") println("부분 인자가 전달 되는 경우") } // 출력 결과 arg1 = 3 arg2 = 2 부분 인자가 전달 되는 경우
- 모든 인자가 전달되는 경우
여기서 함수 정의에 정의된 대로 모든 인자를 전달해야 하지만 실제 인자의 데이터 유형은 선언된 인자의 데이터 유형과 순서가 동일해야 한다.
fun main() { val arg1 = 3 val arg2 = 4 method(arg1, arg2) } fun method(arg1: Int = 1, arg2: Int = 2) { /* ... */ println("arg1 = ${arg1}") println("arg2 = ${arg2}") println("모든 인자가 전달 되는 경우") } // 출력 결과 arg1 = 3 arg2 = 4 모든 인자가 전달 되는 경우
Kotlin Default Argument 동작 원리
위에는 사용법을 설명한 것이고 동작 원리를 알아보도록 하자.
Example Code
fun main() {
method(
param3 = "5",
param4 = "6"
)
}
fun method(
param1: String? = "1",
param2: String? = "2",
param3: String? = "3",
param4: String? = "4",
param5: String = "5",
) {
}
ByteCode
INVOKESTATIC example/DefaultArgumentMethodTestKt.method$default
- int 값 19를 넣어주고 method명:default로 생성된 static method를 호출하는 부분이 있다.
- 19는 왜 넣어주는 것인가? decompile 한 코드를 확인해 보자
Decompile Code
main 메서드 보면 default method를 호출할 때 우리가 선언한 5개 인자뿐 아니라 2개의 인자를 더 넣어주고 있다.
method$default_((String)_null_, (String)_null_, "5", "6", (String)_null_, 19, (Object)_null_);
이렇게 넣어주고 있는데 19? 이게 무엇인지 궁금해할 것이다.
method$default를 한번 살펴보자 이게 Default Argument 동작 원리의 핵심이다.
6번째 인자인 int var5
를 가지고 비트 연산을 하여 default value를 대입해주는 것을 확인할 수 있다.
왜 1, 2, 4, 8, 16인가?
- 첫 번째 인자는 1
- 두 번째 인자는 2
- 세 번째 인자는 4
- 네 번째 인자는 8
- 다섯 번째 인자는 16
- 여섯 번째 인자는 32
- 일곱 번째 인자는 64
- …
이렇게 인자가 하나씩 증가할 때마다 2배씩 늘어난다.
비트 연산으로 default 인자가 들어왔는지 체크하는 로직인데 십진수 19
를 2진수로 변환하면 10011
가 나온다.
이걸 비트 연산을 수행하면 3번째 4번째 조건문을 타지 않기 때문에 호출 시 직접 넣어준 value가 대입된다
var2 = “3”, var3 = “4”
최종적으로 대입된 value들을 가지고 우리가 생성한 함수( method(var0, var1, var2, var3, var4);
)를 호출한다.
+호출 시 null인 인자들을 체크한다.
궁금증 method명$default 메서드를 만들면 어떻게 될까?
Example Code
fun main() {
DefaultArgumentMethodTest().method(
param3 = "5",
param4 = "6"
)
}
class DefaultArgumentMethodTest {
fun method(
param1: String? = "1",
param2: String? = "2",
param3: String? = "3",
param4: String? = "4",
param5: String = "5",
) {
}
fun `method$default`(
var0: DefaultArgumentMethodTest,
var1: String?,
var2: String?,
var3: String?,
var4: String?,
var5: String?,
var6: Int,
var7: Any?
) {
var var1 = var1
var var2 = var2
var var3 = var3
var var4 = var4
var var5 = var5
if (var6 and 1 != 0) {
var1 = "1"
}
if (var6 and 2 != 0) {
var2 = "2"
}
if (var6 and 4 != 0) {
var3 = "3"
}
if (var6 and 8 != 0) {
var4 = "4"
}
if (var6 and 16 != 0) {
var5 = "5"
}
var0.method(var1, var2, var3, var4, var5!!)
}
}
- 이렇게 실행 시 에러가 터진다. 이렇게 메서드명을 지을 리가 없지만 하지 않도록 하자!