두 개의 함수가 있다.
fun printHello() {
println("Hello")
}
fun main() {
printHello()
}
이 함수에서 굳이 printHlello라는 이름이 필요할까?
fun main() { println("Hellp") } 와 다를바가 전혀 없는 코드인데 말이다.
이 발상이 코드화 된 게 inline 함수이다.
inline fun printHello() {
println("Hello")
}
fun main() {
printHello()
}
코드의 모양 자체는 다르지 않다. 그러나 컴파일 된 코드의 모양은 다르다. 어떻냐면
fun main() {
println("Hello")
}
이렇듯 main에 println("Hello")를 그대로 복사해버린다. inline이 붙었기 때문이다.
왜 등장했을까?
- C 언어 (1980년대)
inline 키워드는 없었지만, 컴파일러 최적화 중 하나로 함수 인라인화(function inlining)를 자동으로 적용했다.
(예: 짧은 함수는 알아서 코드 복사) - C++ (1989년)
공식적으로 inline 키워드를 도입했다.
→ "컴파일러야, 이 함수는 호출하지 말고 복사해!"라고 명시하는 방법이었다. - Java (1995년)
inline 키워드 자체는 없지만,
JIT(Just-In-Time) 컴파일러가 **핫스팟(자주 쓰는 짧은 함수)**을 알아서 인라인 최적화했다. - Kotlin (2016년 출시)
Kotlin은 inline 키워드를 언어 차원에서 다시 적극적으로 지원했다.
특히 람다(고차 함수)를 효율적으로 처리하기 위해 명시적인 inline을 사용하게 만들었다.
Kotlin이 inline을 강조한 이유는 특별하다.
- Kotlin은 **람다식(익명 함수)**을 많이 쓰기 때문에,
- 람다 전달/호출 비용을 줄이기 위해 inline이 핵심 최적화 수단으로 자리 잡았다.
예제를 보자.
inline을 사용하지 않고, 일반적인 람다를 사용할 경우.
fun callLambda(lambda: () -> Unit) {
lambda()
}
callLambda { println("Hello") }
코틀린은 함수의 인자로 람다를 전달하면, 내부적으로 익명 클래스 객체를 하나 만든다.
이 코드는 실제로 컴파일될 때 Runnable 비슷한 익명 객체가 만들어지고, 그걸 callLambda에 넘긴다.
- 객체 생성(메모리 소모)
- 함수 호출 (성능 비용)
inline 함수를 사용한 람다
inline fun callLambda(lambda: () -> Unit) {
lambda()
}
이렇게 inline을 붙이면, 람다를 객체로 만들지 않는다.
그 대신 람다 코드 자체를 함수 본문에 복붙해버린다.
즉, 컴파일 시점에 이렇게 변환된다.
println("Hello")
→ 객체 생성 비용 0, 호출 비용 0
✅ 1. noinline
📌 상황
inline 함수 안에서 람다 파라미터를 람다 변수로 저장하거나 다른 함수로 전달할 경우
inline fun example(a: () -> Unit, b: () -> Unit) { val store = b // ❌ 컴파일 에러 }
위 코드는 b가 inline 람다여서, 컴파일 시점에 복붙되기 때문에
참조할 수 있는 객체가 없다.
📌 해결책 → noinline 붙이기
inline fun example(a: () -> Unit, noinline b: () -> Unit) { val store = b // ✅ OK }
→ noinline을 붙이면 b는 inline 되지 않고 객체로 유지됨→ 변수로 저장하거나 다른 함수로 전달 가능함
✅ 2. crossinline
📌 상황
inline 함수에 넘긴 람다 안에서 return을 사용하면
호출한 곳에서 return이 되어버림
inline fun doSomething(block: () -> Unit) { block() } fun run() { doSomething { return // ❌ 전체 run() 함수에서 빠져나감 } }
위처럼 return이 doSomething이 아니라 run()을 종료시켜버림
→ 이건 non-local return 이라고 부름
📌 해결책 → crossinline 붙이기
inline fun doSomething(crossinline block: () -> Unit) { val task = Runnable { block() // ✅ 이제 return 사용 불가 } task.run() }
→ crossinline을 붙이면 람다에서 return을 막고, 다른 쓰레드나 클래스에서 안전하게 호출할 수 있음
inline | 람다 복붙해서 성능 최적화 | 성능 중요할 때 사용 |
noinline | 람다를 객체로 유지함 | 람다를 변수로 저장하거나 넘길 때 |
crossinline | 람다 안에서 return 못하게 막음 | 다른 쓰레드나 클래스에서 실행할 때 |
noinline → 람다를 "복붙하지 말고" 객체로 남겨둬!
crossinline → 람다 안에서 "return 쓰지 마!"
'【 개발 이야기 】' 카테고리의 다른 글
WebClient의 .body(...)와 .bodyValue (1) | 2025.05.13 |
---|---|
[spring] web client 공식 문서 번역 (1) | 2025.05.08 |
[보안] Base64 인코딩과 iv (0) | 2025.04.25 |
[intelliJ] kotlin.stdlib cannot be found in the module graph (0) | 2025.04.17 |
[kotlin] corountine context는 뭔가... (0) | 2025.04.14 |