고차함수를 사용하는 것은 각 함수가 객체이고 클로져를 가져야 하기 때문에 런타임 패널티를 갖습니다.
클로져는 함수의 body에서 접근될 수 있는 변수의 스코프입니다. 함수 객체와 클래스들의 메모리 할당과 가상 호출은 런타임 오버헤드를 발생시킵니다.
이런 종류의 오버헤드는 자주 발생하지만 람다표현식을 인라인시킴으로써 제거할수 있습니다.
인라인 함수를 사용하지 않았을 때:
// kotlin code
fun add(a: Int, b: Int, expr: (Int, Int) -> Int): Int {
return expr(a, b)
}
fun main(args: Array<String>) {
val ret = add(1, 2) { x, y ->
x + y
}
println(ret)
}
// java byte code
public static final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
int ret = add(1, 2, (Function2)null.INSTANCE);
System.out.println(ret);
}
인라인 함수를 사용 했을 때:
// kotlin code
inline fun add(a: Int, b: Int, expr: (Int, Int) -> Int): Int {
return expr(a, b)
}
fun main(args: Array<String>) {
val ret = add(1, 2) { x, y ->
x + y
}
println(ret)
}
// java byte code
public static final void main(@NotNull String[] args) {
Intrinsics.checkNotNullParameter(args, "args");
byte a$iv = 1;
int b$iv = 2;
int $i$f$add = false;
int var7 = false;
int ret = a$iv + b$iv;
System.out.println(ret);
}
inline modifier는 함수 자신과 인자로 들어오는 람다식 둘 다 적용됩니다. 즉, 호출하는 함수와 인자로 들어온 람다식이 모두 호출부에서 인라인화 됩니다.
인라인은 많은 양의 코드를 만들 여지가 있습니다. 하지만 합리적으로 사용한다면, 특히 반복문 내에 있는 호출부에서 성능을 향상 시킬수 있습니다.
인라인 함수로 들어오는 람다식을 인라인 시키고 싶지 않다면, 그 함수의 파라미터 앞에 noinline modifier를 붙일 수 있습니다.
inline fun add(a: Int, b: Int, noinline expr: (Int, Int) -> Int): Int {
return expr(a, b)
}
인라인 가능한 람다표현식은 인라인 함수 내부에서 호출 되거나 인라인 가능한 인자로만 사용될 수 있습니다. 하지만 noinline 람다표현식은 보통의 람다식처럼 일반적으로 사용 가능합니다.
코틀린에서 함수 또는 익명 함수를 종료하기 위해서 return을 사용합니다. 람다식을 종료하기 위해서는 label이 있는 return을 사용합니다. label이 없는 return은 람다식 내에서 금지되어 있습니다.
fun temp() {
val lambda = {
return // Error: return is not allowed here
}
}
But if the function the lambda is passed to is inlined, the return can be inlined, as well. So it is allowed:
하지만 람다식이 인라인 함수의 인자로 사용된다면, return은 인라인 되며 사용할 수 있습니다.
inline fun temp(lambda: () -> Unit) {
lambda()
println("here")
}
fun main(args: Array<String>) {
temp {
return // available
}
}
람다표현식 내에 존재하지만 둘러쌓인 함수를 종료하는 return을 비지역 반환이라고 부릅니다.
fun haveTwo(lst: List<Int>): Boolean {
lst.forEach {
if (it == 2) return true
println(it)
}
return false
}
fun main(args: Array<String>) {
val lst = listOf(1, 4, 5, 2, 3)
println(haveTwo(lst))
}
Collections의 forEach 함수는 인라인 함수입니다. forEach함수는 컬렉션의 원소를 for문으로 순회하며 인자로 들어온 람다식을 수행합니다.
예제에서는 리스트의 원소가 2인지를 판별하는 람다식을 원소마다 수행합니다. 따라서 원소가 2인 경우에 return문을 만나서 비지역 반환이 발생해 haveTwo 함수가 종료되고 true가 반환됩니다.
몇몇 인라인 함수들은 인자로 들어온 람다표현식을 함수 본문에서 바로 호출하지 않고 객체 또는 중첩된 함수 같은 또 다른 실행 컨텍스트에서 호출합니다. 그런 경우에, 비지역 흐름 제어는 람다에서 허용되지 않습니다. 인라인 함수의 람다식 인자가 비지역 반환을 사용하지 않음을 나타내기 위해서 crossinline modifier를 인자 앞에 붙여서 사용할 수 있습니다.
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}