티스토리 뷰

kotlin

Reified란? (Type Eraser)

길 킴 2020. 9. 7. 16:11
728x90

inline 함수와 generic type에서 reified 를 사용하는 걸 종종 볼 수 있다. 

inline fun <reified T : ViewModel> Fragment.viewModel(factory: ViewModelProvider.Factory, body: T.() -> Unit): T {
    val vm = ViewModelProviders.of(this, factory)[T::class.java]
    vm.body()
    return vm
}

 

먼저 reified 의 단어의 뜻은 '구체화 된' 이다. (reify: <추상, 개념 등을> 구체화 하다. 생각하다.)
ex) reify a concept. (개념을 구체화하다.) (발음은 특이하게 '리어파이' 이다. 라이파이 아님 주의! )

어원 그대로 일반 Generic Type과 다르게 더 구체화를 한다고 보면 된다. 


먼저 Generic Type의 특성인 Type Erasure를 알아야 한다. 

Java Documentation

Generic Type은 컴파일 시 타입을 엄격하게 체크 해줌과 동시에 컴파일 타이밍에 해당 타입과 맞는 함수를 미리 만들어 놓는다는 특징이 있습니다. 또한 Generic을 구현하면 Type Eraser도 적용 됩니다. 

타입 이레이저는 새로운 클래스들이 파라미터화된 타입에선 생성되지 않음을 보장합니다. 결과적으로, 제네릭은 런타임 오버헤드를 발생시키지 않습니다. 

=> 정리 하자면, Generic 타입은 컴파일러가 어떤 타입인지 알고 체크를 합니다. 그러나! Runtime에는 타입 정보를 알 수 없습니다. 

Runtime에 타입 정보를 알고 싶다면, inline + reified 함수를 사용합니다. 


일반 Generic과 Refied를 각각 사용하여 무슨 타입인지 알려주는 함수를 작성해보도록 하겠습니다. 

  • 일반 Generic Type을 이용

// 컴파일 에러 
fun <T> printWhatAmI(t: T) {
        when (T::class) {
            String::class -> {
                
            }
        }
}

위의 코드로 작성하면 컴파일 에러가 납니다. (Generic Type의 타입 정보를 접근하려고 하기 때문입니다.)

아래와 같은 2가지 방법으로 접근할 수 있습니다. 

 fun <T> printWhatAmI(t: T) {
        if (t is String) {
            println("i'm String")
        } else if (t is Int) {
            println("i'm Int")
        }
 }
 
fun <T> printWhatAmI(t: T, type: Class<T>) {
        when (type) {
            String::class -> {
                println("i'm String") 
            }
            Int::class -> {
                println("i'm Int")
            }
        }
}

=> 결과적으로 파라미터로 Generic Type의 객체 또는 타입객체를 보내줘야 합니다. 뭔가 깔끔하진 않습니다. 

 

  • Reified 를 이용 

 inline fun <reified T> printWhatAmI() {
        when (T::class) {
            String::class -> {
                println("i'm String")
            }
            Int::class -> {
                println("i'm Int")
            }
        }
}

 


2가지 코드를 Java Decompile 코드로 비교해 보면 다음과 같습니다. 

  • 일반 Generic

... 호출부 시작

sample.printWhatAmI("dfdf", String.class);

... 호출부 종료 


... 메서드 코드 시작
 public final void printWhatAmI(Object t) {
      String var2;
      boolean var3;
      if (t instanceof String) {
         var2 = "i'm String";
         var3 = false;
         System.out.println(var2);
      } else if (t instanceof Integer) {
         var2 = "i'm Int";
         var3 = false;
         System.out.println(var2);
      }

   }

   public final void printWhatAmI(Object t, @NotNull Class type) {
      Intrinsics.checkNotNullParameter(type, "type");
      String var4;
      boolean var5;
      if (Intrinsics.areEqual(type, Reflection.getOrCreateKotlinClass(String.class))) {
         var4 = "i'm String";
         var5 = false;
         System.out.println(var4);
      } else if (Intrinsics.areEqual(type, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
         var4 = "i'm Int";
         var5 = false;
         System.out.println(var4);
      }
   }

... 메서드 코드 종료 


 

  • Reified 코드 

... 호출부 시작

KClass var4 = Reflection.getOrCreateKotlinClass(String.class);
String var5;
boolean var6;
if (Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(String.class))) {
            var5 = "i'm String";
            var6 = false;
            System.out.println(var5);
        } else if (Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
    var5 = "i'm Int";
    var6 = false;
    System.out.println(var5);
}

... 호출부 종료 


... 메서드 코드 시작
public final void printWhatAmI() {
        int $i$f$printWhatAmI = 0;
        Intrinsics.reifiedOperationMarker(4, "T");
        KClass var2 = Reflection.getOrCreateKotlinClass(Object.class);
        String var3;
        boolean var4;
        if (Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(String.class))) {
            var3 = "i'm String";
            var4 = false;
            System.out.println(var3);
        } else if (Intrinsics.areEqual(var2, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
            var3 = "i'm Int";
            var4 = false;
            System.out.println(var3);
        }
}
... 메서드 코드 종료 


Reified는 inline으로 감싸져 있기 때문에, 호출부에서 컴파일 시에 코드를 똑같이 복붙 해놓고 사용한다. 
(inline의 특성임) 

 



결론

Generic Type을 쓰면서, Type정보에 접근하는 깔끔한 코드를 작성하고 싶다면 => inline + Reified 코드를 사용한다. 


참고로, inline은 컴파일 시에 코드를 미리 복붙해서 만들어 놓기 때문에, 파일 크기가 커질 수 있습니다. (중복 코드가 길어지니까) 
대신에, Client 입장에선 깔끔한 코드를 작성할 수 있겠지만요. 
(inline 함수는 코드가 짧을 경우에 사용하는 것을 추천합니다.)

상황에 따라 판단을 하시면 됩니다!

'kotlin' 카테고리의 다른 글

map vs flatMap  (0) 2020.09.07
Kotlin - filter, map 은 알겠는데, Sqeuence 란?  (0) 2020.05.25
SAM Conversions  (0) 2019.12.30
Generic  (0) 2019.10.15
Kotlin - Nested / Inner class  (0) 2019.08.25
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함