
1. 고차 함수
- 다른 함수를 인자로 받거나 함수를 반환하는 함수
- 코틀린에서 람다나 함수 참조를 사용해 함수를 값으로 표현할 수 있음
2. 고차 함수와 inline 의 필요성
- 코틀린에서는 람다 표현식이 객체로 변환되므로 성능 및 메모리 측면에서 오버헤드 발생할 수 있음
- 이를 최적화하기 위해 inline 키워드를 사용하면 성능이 향상
3. 인라인 함수 (inline)
- 컴파일 시점에 함수의 호출 코드가 해당 위치로 직접 복사되는 함수
- 이를 통해 함수 호출 오버헤드를 줄이고, 람다 객체 생성을 방지하여 성능을 최적화 할 수 있음

- noInlineTest는 객체를 생성
- inlineTest는 객체를 생성하지 않고 코드가 직접 삽입되어 실행 속도가 훨씬 빠름
4. reified 키워드
- 기본적으로 JVM의 제네릭은 타입 소거(Type Erasure)로 인해 런타임에 타입 정보를 알 수 없음
- 하지만 inline 함수에서 reified를 사용하면 타입 정보를 런타임에서도 유지 가능

- reified 키워드는 inline 함수와 함께 사용해야 함
- 컴파일러가 T를 실제 타입으로 대체하기 때문에 런타임에서도 타입을 유지할 수 있음

- IDE 힌트로 인라인화 하면 두번째 inline 함수처럼 변경되며, 에러 해결
5. inline 함수가 필요한 이유
1) 람다 표현식이 객체로 변환되는 오버헤드 방지
- 일반적인 고차 함수에서는 함수 타입 객체를 생성해야 함
- 하지만 inline을 사용하면 람다가 객체로 변환되지 않고 코드가 직접 삽입됨
2) 외부 변수를 캡처하는 경우 객체 생성 오버헤드 감소
- 람다 내부에서 외부 변수를 사용하면 변수를 포함하는 새로운 객체가 매번 생성됨 → 오버헤드 발생
- inline을 사용하면 객체를 생성하지 않고 코드 자체가 삽입되므로 오버헤드를 줄일 수 있음
6. 비 지역적 리턴(non-local return)
- 인라인 함수는 비지역 반환(return)이 가능
- 일반적인 함수에서는 return이 불가능하지만, 인라인 함수는 직접 작성한 코드처럼 동작하기 때문에 가능
- 비 지역 반환은 특정 블록이나 람다 표현식의 스코프를 벗어나 바깥쪽 함수나 스코프로 반환하는 것
책에 있는 예제
repeatNoninline(10) {
print(it)
}
fun main() {
repeatNoinline(10) {
print(it)
return // 허용되지 않음
}
}
// 인라인 함수: 함수가 main 함수 내부에 박힘
fun main() {
repeat(10) {
print(it)
return // 허용
}
}

noInlineTest: 컴파일 오류 발생 (return이 main으로 빠져나갈 수 없음)
inlineTest: 정상 동작 (main 함수에서 실행되므로 return이 main을 종료함)
// 해결1: return@label을 사용하여 명확하게 지정
fun main() {
noInlineTest {
println("test")
return@noInlineTest // 컴파일 오류 없이 정상 동작
}
println("이 코드는 실행됨") // 실행됨
}
// 해결2: crossinline을 사용하여 비지역 반환 방지
inline fun inlineTest(crossinline function: () -> Unit) {
function.invoke()
}
fun main() {
inlineTest {
println("test")
return // 컴파일 오류 발생 (비지역 반환 불가능)
}
}
7. crossinline과 noinline
1) crossinline
- 비지역 반환을 허용하지 않는 경우 사용
- 인라인 함수 내에서 람다가 여러 번 호출되거나, 별도 스레드에서 실행될 경우 crossinline이 필요
crossinline이 없으면 비지역 반환이 발생할 위험이 있기 때문에 오류 발생
inline fun requestNewToken(
hasToken: Boolean,
crossinline onRefresh: () -> Unit
) {
if (!hasToken) {
Thread {
onRefresh() // crossinline 없으면 컴파일 오류 발생
}.start()
}
}
2) noinline
- 람다를 인라인 하지 않도록 지정
- inline 함수 내에서 일부 람다만 인라인을 적용하지 않으려면 noinline을 사용
inline fun requestNewToken(
hasToken: Boolean,
crossinline onRefresh: () -> Unit,
noinline onGenerate: () -> Unit
) {
if (hasToken) {
onGenerate() // 인라인되지 않음 (객체 생성 필요)
} else {
onRefresh() // 인라인됨
}
}
3) 함께 사용된 코드
inline fun requestNewToken(
hasToken: Boolean,
crossinline onRefresh: () -> Unit, // crossinline: 비지역 반환 불가능
noinline onGenerate: () -> Unit // noinline: 인라인되지 않음
) {
if (hasToken) {
println("Using existing token")
onGenerate() // 객체가 생성됨
} else {
Thread {
onRefresh() // crossinline이므로 return 불가능
}.start()
}
}
fun main() {
requestNewToken(
hasToken = false,
onRefresh = {
println("Refreshing token...")
// return 불가능 (crossinline 때문에)
},
onGenerate = {
println("Generating new token...")
return // 가능 (noinline이므로 람다 내부에서 동작)
}
)
}
8. 인라인 함수의 단점
1) 재귀적으로 사용할 수 없음
- inline 함수는 코드를 직접 삽입하기 때문에 무한 재귀가 발생할 수 있음
- 따라서 재귀적으로 호출하는 함수에는 사용할 수 없음
2) 코드 크기가 증가할 수 있음
- inline은 코드를 호출하는 곳에 삽입하는 방식이므로, 너무 많은 곳에서 사용되면 코드 크기가 증가할 수 있음
- 큰 람다 함수가 사용될 경우, 코드 크기가 급격히 커질 수 있음
3) 클래스 내부에서 사용 시 주의가 필요
- inline 함수 내부에서는 private 멤버에 접근할 수 없음
- 일반적으로 탑레벨 유틸리티 함수(repeat, run, with), 헬퍼 함수(map, filter, flatMap 등)에서 사용됨
9. 정리
- 고차 함수를 사용할 때는 성능을 위해 inline을 적극 활용하는 것이 좋음
- 특정 조건에서는 crossinline, noinline을 적절히 사용하여 제어
- reified를 통해 런타임에서 타입 정보를 활용 가능
'Study OR Book > Book' 카테고리의 다른 글
[이펙티브 코틀린] 아이템43: API의 필수적이지 않는 부분을 확장 함수로 추출하라 (0) | 2025.02.18 |
---|---|
[이펙티브 코틀린] 아이템40: equals의 규약을 지켜라 (0) | 2025.02.10 |
[이펙티브 코틀린] 아이템32: 추상화 규약을 지켜라 (0) | 2025.02.03 |
[이펙티브 코틀린] 아이템29: 외부 API를 랩(wrap)해서 사용하라 (0) | 2025.01.21 |
[이펙티브 코틀린] 아이템19: knowledge를 반복하여 사용하지 말라 (0) | 2025.01.15 |