本文將會爲你們介紹 Kotlin 的 "reified" 關鍵字,在介紹 "reified" 以前,咱們得先提一下泛型 (Generics)。泛型在編程領域中是一個很重要的概念,它提供了類型安全,並幫助開發者在編程時不須要進行顯示的類型轉換。泛型對編程語言的類型系統進行了擴展,從而容許一個類型或方法在保證編譯時類型安全的前提下,還能夠對不一樣類型的對象進行操做。可是使用泛型也會有一些限制,好比當您在泛型函數中想要獲取泛型所表示類型的具體信息時,編譯器就會報錯,提示說相關的信息不存在。而 "reified" 關鍵字,正是爲了解決此類問題誕生的。java
沒法獲取泛型所表示的類型編程
這些類型信息丟失是由於 JVM 實現泛型的方式所致使的 (提示: 類型擦除,咱們會在以後討論這個問題)。解決這一問題的一個方法,是將泛型實際表明的類型信息做爲一個參數傳遞給函數。安全
fun <T> printType(classType: Class<T>) { print(classType::class.java) }
這樣的代碼看起來也沒有那麼不可接受,可是在 Kotlin Vocabulary 系列的文章 中咱們就一直在強調,Kotlin 中儘可能不要出現樣板代碼,這樣可讓代碼保持簡潔。爲了達到這一目標,Kotlin 提供了一個特別的關鍵字 reified,使用它就能夠在泛型函數中獲取所需的類型信息。只要您對泛型的實現方式有所瞭解,就可能會不由驚呼: 這怎麼可能!下面就來看看這是如何在 Kotlin 中實現的。app
在 Java 5.0 版本以前並未支持泛型,那時 Java 中的 collection 是沒有類型信息的。也就是說一個 ArrayList 並不會聲明它內部所包含的數據類型究竟是 String、Integer 仍是別的類型。編程語言
List list = new ArrayList(); list.add("First String"); // 正常處理,沒有錯誤 list.add(6);
在沒有泛型支持時,任什麼時候候想訪問 collection 中的對象,都要作一次顯式的類型轉換。另外也沒有相應的錯誤保障機制來防止出現非法的類型轉換。函數
String str = (String)list.get(1); // 須要顯示地進行轉換和拋出異常
爲了解決這個問題,Java 從 Java 5 開始支持泛型。有了這一特性支持,您能夠將 collection 關聯一個指定的類型,當您向 collection 中添加非指定類型的數據時,編譯器就會發出警告。同時,您也不須要進行顯式的類型轉換了,這也會減小運行時異常的狀況發生。性能
List<String> list = new ArrayList<>(); list.add("First String"); // 編譯錯誤 list.add(6); // 無需進行類型轉換 String str = list.get(0);
泛型是經過一種叫 類型擦除 (type erasure) 的技巧實現的。因爲 Java 5 以前沒有關聯類型信息,編譯器會先將全部類型替換爲基本的 Object 類型,而後再進行必要的類型轉換。經過將類型信息提供給編譯器,類型擦除能夠作到既保證編譯時類型安全,又能夠經過保持字節碼同以前的 Java 版本相同來實現向後兼容。可是,當在泛型函數中須要獲取類型信息時,類型擦除的實現方式就顯得力不從心了。spa
Reified 關鍵字必須結合內聯函數一塊兒使用,它能讓本該在編譯階段就被擦除的類型信息,可以在運行時被獲取到。若是您還不熟悉內聯函數,能夠閱讀《Kotlin Vocabulary | 內聯函數的原理與應用》。code
簡單地解釋一下內聯函數,若是一個函數被標記爲 inline,那麼 Kotlin 編譯器會在全部使用該函數的地方將函數調用替換爲函數體。這樣作的好處是,編譯器能夠隨意地在調用處對函數體進行修改,由於修改的函數體是被複制的,因此修改後不會影響到其他調用一樣函數的地方。如果要在參數中使用 reified,那首先須要將函數標記爲 inline,而後在泛型參數以前添加 reified 關鍵字便可。對象
inline fun <reified T> printType() { print(T::class.java) } fun printStringType(){ // 用 String 類型調用被 reified 修飾的泛型函數 printType<String>() }
讓咱們反編譯一下 Java 代碼來探索其中的奧祕。從反編譯後的代碼中能夠發現,當調用 reified 修飾的內聯函數時,編譯器會複製該函數體,並將泛型類型替換爲實際使用的類型。這樣,您就能夠不用將類傳遞給函數也可以獲取到相應類型信息了。
// 從字節碼轉換爲 Java 的內聯函數 public static final void printType() { int $i$f$printType = 0; Intrinsics.reifiedOperationMarker(4, "T"); Class var1 = Object.class; boolean var2 = false; System.out.print(var1); } // 從字節碼轉換爲 Java 代碼的調用方 public static final void printStringType() { int $i$f$printType = false; Class var1 = String.class; boolean var2 = false; System.out.print(var1); }
Reified 關鍵字只能同內聯函數一塊兒使用,所以內聯函數使用的規則也一樣適用於被 reified 修飾的函數。另外請牢記,Java 代碼中不能訪問被 reified 修飾的函數。Java 不支持內聯,也就意味着在 Java 中的泛型參數不能逃脫被編譯器擦除類型的命運。
Reified 一樣還支持重載函數返回泛型類型,例如,如下函數能夠返回 Int 或者 Float:
inline fun <reified T> calculate(value: Float): T { return when (T::class) { Float::class -> value as T Int::class -> value.toInt() as T else -> throw IllegalStateException("Only works with Float and Int") } } val intCall: Int = calculate(123643) val floatCall: Float = calculate(123643)
通常來講,具備相同輸入參數和不一樣返回類型的函數是不可以被重載的。使用內聯函數,編譯器能夠在複製函數體時,一樣將泛型返回類型替換爲實際所表示的類型。若是您查看反編譯後的 Java 代碼,能夠發現編譯器在 intCall 變量中實際使用的是 Integer 類型,在 floatCall 變量中實際使用的是 Float 類型。
public final void call() { float value = 123643.0F; int $i$f$calculate = false; KClass var5 = Reflection.getOrCreateKotlinClass(Integer.class); Integer var10000; if (Intrinsics.areEqual(var5, Reflection.getOrCreateKotlinClass(Float.TYPE))) { var10000 = (Integer)value; } else { if (!Intrinsics.areEqual(var5, Reflection.getOrCreateKotlinClass(Integer.TYPE))) { throw (Throwable)(new IllegalStateException("Only works with Float and Int")); } var10000 = (int)value; } //這裏用到了 Integer int intCall = ((Number)var10000).intValue(); int $i$f$calculate = false; KClass var6 = Reflection.getOrCreateKotlinClass(Float.class); Float var8; if (Intrinsics.areEqual(var6, Reflection.getOrCreateKotlinClass(Float.TYPE))) { var8 = value; } else { if (!Intrinsics.areEqual(var6, Reflection.getOrCreateKotlinClass(Integer.TYPE))) { throw (Throwable)(new IllegalStateException("Only works with Float and Int")); } var8 = (Float)(int)value; } float floatCall = ((Number)var8).floatValue(); //這裏用到了 Float }
Reified 容許您在使用泛型來進行編程的同時,還可以在運行時獲取到泛型所表明的類型信息,這在以前是沒法作到的。當您須要在內聯函數中使用到類型信息,或者須要重載泛型返回值時,您可使用 reified。使用 reified 不會帶來任何性能上的損失,可是若是被內聯的函數過於複雜則,仍是可能會致使性能問題。由於 reified 必須使用內聯函數,因此要保證內聯函數的簡短,而且遵循使用內聯函數的最佳實踐,以避免讓性能受到損失。