함수 심화 — 확장 함수·고차 함수·람다·inline·reified
코틀린에서 함수는 일급 시민이다 — 변수에 담고, 인자로 넘기고, 반환한다. 여기에 확장 함수(기존 클래스에 메서드를 덧붙이듯)와 인라인(람다 오버헤드 제거)·reified(런타임 타입 접근)가 더해져, 안드로이드의 간결한 DSL·유틸리티·KTX 확장이 가능해진다. 이 장은 코틀린을 함수형으로 쓰게 하는 핵심들이다. (출처: Kotlin Docs — Functions / Lambdas / Inline / Kotlin in Action 2e Ch 5·10.)
확장 함수 — 클래스를 안 건드리고 메서드를 더한다
확장 함수는 이미 있는 클래스에 새 함수를 덧붙이는 것처럼 보이게 한다 — 소스를 수정하거나 상속하지 않고. 수신 타입을 함수 이름 앞에 적고, 본문에서 this(생략 가능)로 인스턴스를 가리킨다.
fun String.lastChar(): Char = this[length - 1] // String에 메서드 추가하듯
"Kotlin".lastChar() // → 'n'
fun Int.isEven() = this % 2 == 0
4.isEven() // → true
안드로이드의 KTX 라이브러리가 이걸로 도배돼 있다 — view.isVisible = true, context.getColorCompat(...), bundle.toMap() 같은 간결한 확장들. 단, 확장 함수는 정적으로 디스패치된다 — 멤버 함수가 아니라 컴파일 시점 타입으로 결정되므로, 오버라이드(다형성)되지 않는다. (멤버 함수와 이름이 겹치면 멤버가 이긴다.)
// 확장 프로퍼티 (백킹 필드는 없음 — getter만)
val String.firstChar: Char get() = this[0]
"Kotlin".firstChar // → 'K'
고차 함수와 함수 타입 — 함수를 주고받는다
함수를 인자로 받거나 반환하는 함수가 **고차 함수(higher-order function)**다. 함수 타입은 (파라미터) -> 반환으로 쓴다.
fun <T> List<T>.customFilter(predicate: (T) -> Boolean): List<T> {
val result = mutableListOf<T>()
for (item in this) if (predicate(item)) result.add(item)
return result
}
listOf(1, 2, 3, 4).customFilter { it > 2 } // 람다를 넘김 → [3, 4]
람다 — 코드 블록을 값으로
람다는 이름 없는 함수를 값으로 만든 것이다 — { 인자 -> 본문 }.
val sum = { a: Int, b: Int -> a + b } // 함수 타입 (Int,Int)->Int
sum(2, 3) // 5
// 인자가 하나면 it 으로 줄임
listOf(1,2,3).map { it * 2 } // [2,4,6]
// 마지막 인자가 람다면 괄호 밖으로 (trailing lambda)
button.setOnClickListener { onClick() } // (자바 익명 클래스 대비 극도로 간결)
**함수 참조(::)**로 기존 함수를 람다처럼 넘긴다 — list.map(::transform), list.filter(String::isNotEmpty).
클로저 — 람다가 바깥 변수를 기억한다
코틀린 람다는 *바깥 스코프의 변수를 캡처(closure)*한다 — 자바와 달리 var(가변)도 캡처·수정 가능하다.
fun counter(): () -> Int {
var count = 0
return { ++count } // count를 캡처 — 호출할 때마다 증가
}
val next = counter()
next(); next() // 1, 2 (상태가 살아있음)
inline — 람다 오버헤드를 없앤다
고차 함수는 람다마다 객체를 만들 수 있어 비용이 든다. inline 함수는 호출 자리에 본문과 람다를 그대로 펼쳐(인라인) 넣어 그 비용을 없앤다. 그래서 코틀린 표준 라이브러리의 map·filter·let·run 등은 대부분 inline이다.
inline fun measure(block: () -> Unit) {
val start = System.nanoTime()
block() // 호출부에 그대로 펼쳐짐 → 람다 객체 X
println("${System.nanoTime() - start}ns")
}
measure { heavyWork() }
inline의 또 다른 이점 — 비지역 반환(non-local return). inline 람다 안의 return은 바깥 함수를 빠져나간다(보통 람다에선 불가).
fun find(list: List<Int>): Int? {
list.forEach { if (it > 10) return it } // forEach는 inline → 바깥 find를 return
return null
}
남용 주의 — inline은 코드를 복제하므로 큰 함수에 마구 붙이면 바이트코드가 커진다. 람다를 받는 작은 고차 함수에 적합하다. 람다를 인라인하고 싶지 않은 파라미터엔
noinline, 비지역 반환을 막으려면crossinline.
reified — 런타임에 타입을 안다
JVM은 제네릭 타입을 런타임에 지운다(타입 소거). 그래서 보통 T::class를 못 쓴다. 하지만 **inline + reified**면 타입 인자가 인라인 시점에 박혀 런타임에 타입을 쓸 수 있다.
inline fun <reified T> Gson.fromJson(json: String): T =
fromJson(json, T::class.java) // reified 덕에 T::class 사용 가능
val user: User = gson.fromJson(json) // 타입 인자를 클래스로 안 넘겨도 됨
// 안드로이드 관용구 — 타입 안전한 startActivity
inline fun <reified T : Activity> Context.start() =
startActivity(Intent(this, T::class.java))
context.start<DetailActivity>() // 깔끔
reified는 Intent·Bundle·DI·직렬화 등 타입을 런타임에 넘겨야 하는 안드로이드 코드를 크게 간결하게 만든다.
정리 — 함수가 일급이면 코드가 짧아진다
코틀린 함수의 핵심 — 확장 함수로 클래스를 안 건드리고 기능을 더하고(KTX), 고차 함수·람다로 동작을 값으로 넘기고, inline으로 그 비용을 없애며, reified로 런타임 타입을 쓴다. 이 네 가지가 안드로이드의 간결한 API·DSL·콜백을 떠받친다. 다음 장의 스코프 함수도 결국 이 람다·확장의 응용이다.
한 줄 요약 — 확장 함수=기존 클래스에 메서드 덧붙이듯(정적 디스패치). 고차 함수=함수를 주고받음, 람다
{ it -> }·trailing lambda·::참조. 클로저는var도 캡처.inline=람다 비용 제거 + 비지역 return,reified=런타임 타입 접근(타입 소거 우회).
(출처: Kotlin Docs — Higher-Order Functions / Inline Functions / Kotlin in Action 2e Ch 5·10 / Android KTX.)