Kotlin - inline, noinline, clossline
일급함수
inline, noinline을 이해하기 위해서는 일급함수에 대한 이해가 필수적입니다.이미 알고있으신 내용이면 편하게 스크롤을 내려주세요.일급함수는 스스로 객체로써 취급되는 함수로 다른 함수를 파라미터로 전달받고 반환할 수 있는 함수를 뜻합니다. 이부분은 코드를 보는 편이 더 좋을 것 같습니다.
fun print(body:(Int,Int) -> Int) {
println(body(5,5))
}
print({a,b->a})
위 함수를 보겠습니다. 위 함수는 두개의 정수를 받아 하나의 정수값만을 출력하는 함수입니다. 위와같이 선언을 하게된다면 java에서 아래와 같은 코드로 변환을하여 사용할 수 있습니다.
public final void print(@NotNull Function2 body){
Intrinsics.checkParameterIsNotNull(body, "body");
Object result = body.invoke(5,5);
System.out.println(result);
}
위와같이 자동으로 컨버팅이 될 수 있기때문에 아래와 같은 유동적인 방법도 사용이 가능합니다.
class InlineText {
private fun printResult(body: (Int, Int) -> Int) = body(10, 5)
fun sum(a: Int, b: Int) = a + b
fun subtract(a: Int, b: Int) = a - b
@Test
fun printResultSumTest() {
Assert.assertEquals(15, printResult(::sum))
}
@Test
fun printResultSubtractTest(){
Assert.assertEquals(5,printResult(::subtract))
}
}
Inline과 noinline
fun doSometing(body: () -> Unit) {
body()
}
fun callFunction(){
doSometing { println("문자열출력!") }
}
이 함수를 자바로 표현한다면
public void doSometing(Function body){
body.invoke();
}
public void callFunction(){
doSometing(System.out.println("문자열출력"));
}
위 코드와 같습니다. 그리고 이 자바코드는 아래와 같이 변환되게됩니다.
public void callingFunction(){
doSometing(new Function(){
@Override
public void invoke(){
System.out.println("문자열출력")
}
});
}
문제는 위 sum이나 Substract처럼 조합하는 함수가 많아질수록 계속 N개만큼의 function 오브젝트가 생성됩니다. 이럴때 사용하게되는것이 inline 키워드입니다.
Decompile해보기
fun doSometing(body: () -> Unit) {
body()
}
fun callFunction(){
doSometing { println("문자열출력!") }
}
위 코드를 디컴파일 해보면 아래와 같은 결과를 볼 수 있습니다.
public final void doSometing(@NotNull Function0 body) {
Intrinsics.checkParameterIsNotNull(body, "body");
body.invoke();
}
public final void callFunction() {
this.doSometing((Function0)null.INSTANCE);
}
callFunction 함수에서 doSometing 함수를 호출하는것을 볼 수 있습니다
inline fun doSometing(body: () -> Unit) {
body()
}
fun callFunction(){
doSometing { println("문자열출력!") }
}
위 코드 처럼 인라인키워드를 추가하고 다시 디컴파일 해보면
public final void doSometing(@NotNull Function0 body) {
int $i$f$doSometing = 0;
Intrinsics.checkParameterIsNotNull(body, "body");
body.invoke();
}
public final void callFunction() {
int $i$f$doSometing = false;
int var3 = false;
String var4 = "문자열출력!";
System.out.println(var4);
}
CallFunction 함수 내부안에 바로 삽입되어 선언되게 됩니다.
여기서 주의할 점이 있는데 inline 함수는 private 키워드를 사용하여 정의할 수 없습니다. 하지만 대신 다른 접근한정자인 internal을 사용할 수 있습니다.
noinline
모든 람다함수에 inline을 쓰고싶지 않을 수 있습니다. 이 경우 아래와같이 해당 람다함수에 noinline 키워드를 추가하여줍니다.
inline fun callingLmabda(aLambda:()-> Unit,
noinline dontInlineLambda:()->Unit,
aLambd2a: () -> Unit){
/////////
//내부로직
////////
}
모든 함수를 inline을 사용하여 내부로 컨버팅 되어지길 원치않을 수 있습니다. 이 경우 위처럼 inline을 먼저 선언한 뒤 람다함수중 사용하지 않을 함수에 noinline키워드를 붙여줍니다
crossinline
일부 인라인 함수는 파라미터로 전달받은 람다를 호출할 때 함수 몸체에서 직접 호출하지 않고 다른 실행 컨텍스트를 통해(예, 로컬 객체나 중첩 함수,스레드) 호출해야 할 때가 있다. 이 경우 람다 안에서 비-로컬 흐름을 제어할 수 없다. 이를 지정하려면 람다 파라미터에crossinline
제한자를 붙이면 됩니다.
inline fun calc(crossinline body: () -> Unit): Long {
val start = System.nanoTime()
val f = object : Runnable {
override fun run() = body()
}
return System.nanoTime() - start
}
reified
다음은 reified
타입 파라미터인데 Kotlin 가이드 문서에 있는 내용으로 확인해 보겠습니다.
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p?.parent
}
@Suppress("UNCHECKED_CAST")
return p as T
}
위 함수는 노드가 특정 타입을 가졌는지 확인하기 위해 트리를 탐색하고 리플렉션을 사용하는 코드입니다 위 함수를 호출할 때 다음과 같이 호출할 것입니다. findParentOfType(SomeClass::class.java)
이렇게 파라미터로 보기 좋지 않은 값을 전달하게 된다. 우리는 조금 더 간결하게 findParentOfType<SomeClass>
이렇게 표현하고 싶다면, 그럴 경우 reified
를 이용하여 인라인 함수를 정의하면 됩니다.
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p?.parent
}
return p as T
}
위와 같이 인라인 함수를 정의하였다면 findParentOfType<SomeClass>()
이렇게 호출할 수 있습니다. 타입 파라미터에 reified
를 정의하면 마치 클래스처럼 타입 파라미터에 접근할 수 있습니다. 인라인 함수이므로 리플렉션이 필요 없고 is
나 as
와 같은 일반 연산자가 동작합니다. 참고로 인라인이 아닌 일반 함수에는 reified
를 사용할 수 없습니다.
댓글