■ 확장 함수 타입은 왜 필요할까요?
아래 예제에서 함수 addItems()의 형식인자 action은 일반 함수 타입을 갖습니다. 이 함수 타입을 갖는 변수 newItem을 addItems()의 실인자로 전달할 수 있습니다. 코드는 직관적이고 간단하지만, 람다 식 블록에서 매번 키워드 it를 사용해 StringBuilder 타입 객체를 참조해야 합니다. 그렇다고 it를 생략할 수는 없습니다.
fun addItems(action: (StringBuilder) -> Unit): String {
val sb = StringBuilder()
action(sb)
return sb.toString()
}
fun main() {
val newItem: (StringBuilder) -> Unit = {
it.append("Hong ")
it.append("25 ")
it.append("Incheon ")
}
val row = addItems(newItem)
println(row) // Hong 25 Incheon
}
아래처럼 수신 객체를 갖는 함수 타입, 즉 확장 함수 타입으로 고치면 어떨까요? 확장 함수 타입을 갖는 람다 식 블록에서는 action(sb)가 아니라 sb.action()으로 함수 action()을 호출합니다. action()은 StringBuilder 클래스에서 정의한 함수가 아님에도 불구하고, 확장 함수를 호출할 때와 똑같은 방식으로 호출할 수 있습니다.
fun addItems(action: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.action()
return sb.toString()
}
fun main() {
val newItem: StringBuilder.() -> Unit = {
this.append("Hong ")
append("25 ") // this는 생략 가능.
append("Incheon ")
}
val row = addItems(newItem)
println(row)
}
함수 addItems()는 범위 함수 apply()를 적용하면 코드를 확~ 줄일 수 있습니다. 함수 타입 형식 인자 action()을 apply()의 형식인자로 전달할 수 있기 때문입니다. 범위 함수 apply()는 자신의 수신 객체 StringBuilder()를 함수 action()의 형식 인자로 사용합니다.
fun addItems(action: StringBuilder.() -> Unit): String =
StringBuilder().apply(action).toString()
아래에 보인 것처럼 확장 함수 타입을 갖는 변수(newItem)를 StringBuilder 타입 객체 sb의 확장 함수로 호출할 수 있습니다. 또, 객체 sb의 진짜(?) 멤버 메소드 append()의 인자로 확장 함수를 전달할 수도 있습니다.
fun addItems(action: StringBuilder.() -> Unit): String =
StringBuilder().apply(action).toString()
fun main() {
val newItem: StringBuilder.() -> Unit = {
this.append("Hong ")
append("25 ")
}
val sb = StringBuilder()
sb.newItem()
sb.append(addItems { append("Incheon ") })
println(sb.toString())
}
■ 범위 함수(scope function)는 확장 함수
run, also, apply, let 등의 범위 함수는 모두 확장 함수입니다. 범위(scope)란 용어를 사용하는 것은 적용 범위를 확장 함수의 형식 인자인 람다 식 블록으로 제한하기 때문입니다. 범위 함수의 구조는 수신_객체.범위_함수 { ... } 입니다. 예들 들면, run() 함수의 형식 인자가 람다 식 { ... } 입니다. 람다 식 블록에 수신 객체(this) 또는 형식 인자(it)가 전달되는 형태입니다. 범위 함수에 관한 자세한 설명은 4.16절을 참고하기 바랍니다.
이제 라이브러리에서 정의한 범위 함수를 이해할 준비를 모두 마쳤습니다. 아래 코드에서 알 수 있는 것처럼 apply()는 수신 객체의 확장 함수로 선언되어 있습니다. 또, 형식인자 block도 확장 함수 타입으로 선언되어 있음을 알 수 있습니다. 람다 식 블록에서 수신 객체는 this로 참조할 수 있습니다. 코드에서 알 수 있는 것처럼 apply()는 수신 객체를 반환합니다.
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
아래 코드를 보면 범위 함수 with()가 확장 함수가 아님을 쉽게 알 수 있습니다. with()는 2개의 형식인자를 갖고 있습니다. 첫 번째 형식인자는 수신 객체 receiver이며, 두 번째 형식인자는 확장 함수 타입으로 선언한 함수(=람다 식) block입니다. with()는 함수를 호출해 실행한 결과를 반환합니다.
inline fun <T, R> with (receiver: T, block: T.() -> R ): R =
receiver.block()
'코틀린' 카테고리의 다른 글
코틀린: 생성자와 속성 초기화 - 보조 생성자 (0) | 2025.01.06 |
---|---|
코틀린: 생성자와 속성 초기화 - 주 생성자 (0) | 2025.01.06 |
코틀린: 확장 함수와 수신 객체를 갖는 함수 타입(2/3) (0) | 2025.01.06 |
코틀린: 확장 함수와 수신 객체를 갖는 함수 타입(1/3) (0) | 2025.01.06 |
코틀린: 3장 테스트에 도전해 보세요 (4번 정답 해설) (0) | 2025.01.06 |