본문 바로가기

코틀린

inline 함수와 reified type

inline 함수란?
함수 호출 시 별도로 분리된 위치의 레이블로 점프하여 실행되는 일반 함수와는 달리 호출 부분을 함수 전체 코드로 치환하여 컴파일한다.
고차함수(함수를 인자로 전달받거나 함수를 결과로 반환하는 함수 : 람다)수를 사용할때 오버헤드 현상이 발생하지 않도록 해주는 함수다.
 
오버헤드(overhead)는 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간 · 메모리 등을 말한다.
예를 들어 A라는 처리를 단순하게 실행한다면 10초 걸리는데, 안전성을 고려하고 부가적인 B라는 처리를 추가한 결과 처리시간이 15초 걸렸다면, 오버헤드는 5초가 된다. 또한 이 처리 B를 개선해 B'라는 처리를 한 결과, 처리시간이 12초가 되었다면, 이 경우 오버헤드가 3초 단축되었다고 말한다
고차함수를 사용할때 함수가 호출될때 마다 새로운 객체가 생성된다. (메모리가 낭비됨)
fun doSomethingElse(lamda: () -> Unit){
    println("Do something else")
    lamda()
}

fun main(){
    doSomethingElse({ print("hello!!") })
}
이 코드의 문제점은 doSomethingElse 메소의 파라미로 새로운 객체를 생성해서 넘겨준다는 점이다.
doSomethingElse 가 호출될때마다 새로운 객체를 만들어서 넘겨주는데 이는 낭비다.
그런데 inline 함수를 사용하게 되면 위에서 언급 했듯이 호출 부분을 함수 전체 코드로 치환해서 컴파일 하니까 새로운 객체를 생성하지 않게 되는 것이다.
아래는 인라인 함수를 사용했을때의 예시다.
inline fun doSomethingElse(lamda: () -> Unit){
    println("Do something else")
    lamda()
}

fun main(){
    doSomethingElse({ print("hello!!") })
}
그냥 함수 앞에 inline 예약어만 넣으면 끝난다.
inline 함수가 적용된 doSomethingElse메소드는 자바에서 아래와 같이 바뀐다. 객체가 새로 생성된게 아니라 inline 함수에 호출한 내용 자체가 적용됐다.
내부에서 사용되는 함수가 호출하는 함수(doSomethingElse)의 내부에 삽입됐다.
public static final void doSomethingElse() {
    System.out.println("hello!!");
}
reified 이란?
reify - 구체화
reified - 구체화된
inline 함수에서만 사용할 수 있는 키워드다. reified 키워드를 사용하면 원래 런타임때 사용할 수 없는 제네릭(T)을 런타임때 사용할 수 있게 된다.
원래(reified 키워드가 없을때)제네릭은 컴파일타이밍에 사용 가능하고 런타임에는 참조하면 에러난다.
좀 구체적으로 코드를 보면서 설명해보자.
fun <T> genericFunc(c: Class<T>)
위에서 <T> 가 제네릭 타입을 의미한다.
Generic Type은 컴파일 시 타입을 엄격하게 체크 해줌과 동시에 컴파일 타이밍에 해당 타입과 맞는 함수를 미리 만들어 놓는다. 또한 Generic을 구현하면 Type Eraser도 적용 됩니다. 
type erasure - 컴파일에는 타입을 정의하고 런타임에는 타입 정의를 없앤다는 말이다. 그래서 제네릭 타입 런타임 시에는 쓸려고 하면 에러가 나는 것이다.
generic과 reified를 사용해서 각각 무슨 타입인지 알려주는 메소드를 몇개 살펴보자.
// 컴파일 에러 
fun <T> printWhatAmI(t: T) {
    when (T::class) {
        String::class -> {

        }
    }
}
컴파일 에러가 난다. 위에서도 언급했듯이 제네릭 타입을 런타임에서 사용하려고 해서 그렇다.
아래와 같이 바꾸는 방법이 있다. (일단 reified를 사용하지 않는 상황이다.)
fun main(){
    val tmp : String = "hello"
    printWhatAmI(tmp)
}
fun <T> printWhatAmI(t : T){
    if(t is Int){
        print("Int 형이네용")
    }
    else if(t is String){
        println("String 형이네용")
    }
}
제네릭  타입 사용.
fun <T> printWhatAmI(type: Class<T>){
    when (type) {
        String::class -> {
            println("String")
        }
        Int::class -> {
            println("Int")
        }
    }
}
아래는 reified를 적용한 코드다. 그냥 코드안에서 T를 맘대로 써도 된다.
fun main(){
    printWhatAmI<String>()
}

inline fun <reified T> printWhatAmI() {
    when (T::class) {
        String::class -> {
            println("String")
        }
        Int::class -> {
            println("Int")
        }
    }
}
출처 :
(inline 함수 참조)
(reified키워드 참조)