說kotlin中這個關鍵字以前先簡單說下Java中的泛型,咱們在編程中,出於複用和高效的目的,常用泛型。泛型是經過在JVM底層採起類型擦除的機制實現的,Kotlin也是這樣。java
泛型是 Java SE 1.5 中的纔有的特性,泛型的本質是參數化類型,可分爲泛型類、泛型接口、泛型方法。在沒有泛型的狀況的下只能經過對Object 的引用來實現參數的任意化,帶來的缺點就是要顯式的強制類型轉換,而強制轉換在編譯期是不作檢查的,容易把問題留到運行時,因此泛型的好處是在編譯時檢查類型安全,而且全部的強制轉換都是自動和隱式的,提升了代碼的重用率,避免在運行時出現 ClassCastException。android
JDK 1.5 中引入了泛型來容許強類型在編譯時進行類型檢查;JDK 1.7 中泛型實例化類型具有了自動推斷的能力,譬如List<String> mList = new ArrayList<String>()
能夠寫成 List<String> mList = new ArrayList<>()
git
泛型經過類型擦來實現,編譯器在編譯時擦除全部泛型類型相關信息,即運行時就不存在任何泛型類型相關的信息,譬如 List<Integer>
在運行時僅用一個 List 來表示,這樣作的目的是爲了和 Java 1.5 以前版本進行兼容。github
fun test() { val mList= ArrayList<String>() mList.add("123") Log.v("tag",mList[0]) } 複製代碼
字節碼以下:編程
public final test()V L0 LINENUMBER 18 L0 NEW java/util/ArrayList DUP INVOKESPECIAL java/util/ArrayList.<init> ()V ASTORE 1 L1 LINENUMBER 19 L1 ALOAD 1 LDC "123" INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z POP L2 LINENUMBER 20 L2 LDC "tag" ALOAD 1 ICONST_0 INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object; CHECKCAST java/lang/String INVOKESTATIC android/util/Log.v (Ljava/lang/String;Ljava/lang/String;)I POP L3 LINENUMBER 21 L3 RETURN L4 LOCALVARIABLE mList Ljava/util/ArrayList; L1 L4 1 LOCALVARIABLE this Lcom/github/coroutinesdemo/Test; L0 L4 0 MAXSTACK = 3 MAXLOCALS = 2 複製代碼
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
list.add("123")
其實是"123"
做爲Object
存入集合中的json
INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object
從list
實例中讀取出來Object
而後轉換成String
以後才能使用安全
CHECKCAST java/lang/String
進行類型轉換bash
泛型擦除在編譯成字節碼時首先進行類型檢查,再進行類型擦除(即全部類型參數都用限定類型替換,包括類、變量和方法若是類型變量有限定則原始類型就用第一個邊界的類型來替換,譬如 class Test<T extends Comparable & Serializable> {} 的原始類型就是 Comparable)markdown
若是類型擦除和多態性發生衝突時就在子類中生成橋方法解決,接着若是調用泛型方法的返回類型被擦除則在調用該方法時插入強制類型轉換。函數
類型擦除會有一系列的問題,這裏不展開了
instanceof ArrayList<?>
fun <T>Int.toCase():T?{ return (this as T) } 複製代碼
上述代碼在轉換類型時,沒有進行檢查,因此有可能會致使運行時崩潰,編譯器會提示unchecked cast
警告,若是得到的數據不是它指望的類型,這個函數會出現崩潰
fun testCase() { 1.toCase<String>()?.substring(0) } 複製代碼
這就會出現TypeCastException錯誤,因此爲了安全獲取數據通常都是須要顯式傳遞class信息:
fun <T> Int.toCase(clz:Class<T>):T?{ return if (clz.isInstance(this)){ this as? T }else{ null } } 複製代碼
fun testCase() { 1.toCase(String::class.java)?.substring(0) } 複製代碼
但這須要經過顯示傳遞class的方式過於麻煩繁瑣尤爲是傳遞多類型參數,基於類型擦除機制沒法在運行時獲得T的類型信息,因此用到安全轉換操做符as或者as?
fun <T> Bundle.putCase(key: String, value: T, clz:Class<T>){ when(clz){ Long::class.java -> putLong(key,value as Long) String::class.java -> putString(key, value as String) Char::class.java -> putChar(key, value as Char) Int::class.java -> putInt(key, value as Int) else -> throw IllegalStateException("Type not supported") } } 複製代碼
那有沒有排除這種傳遞參數以外的優雅實現???
reified關鍵字的使用很簡單:
在泛型類型前面增長reified
修飾
在方法前面增長inline
改進上述代碼
inline fun <reified T> Int.toCase():T?{ return if (this is T) { this } else { null } } 複製代碼
testCase()方法調用轉成Java 代碼看下 :
public final void testCase() { int $this$toCase$iv = 1; int $i$f$toCase = false; String var10000 = (String)(Integer.valueOf($this$toCase$iv) instanceof String ? Integer.valueOf($this$toCase$iv) : null); // inline部分 String var1; if (var10000 != null) { // 替換開始 var1 = var10000; $this$toCase$iv = 0; if (var1 == null) { throw new TypeCastException("null cannot be cast to non-null type java.lang.String"); } var10000 = var1.substring($this$toCase$iv); Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).substring(startIndex)"); } else { var10000 = null; } // reified替換結束 var1 = var10000; System.out.println(var1); } 複製代碼
Inline的做用這裏再也不多說了,noinline和crossinline又是啥?這裏能夠看下。
泛型在運行時會被類型擦除,可是在inline函數中咱們能夠指定類型不被擦除, 由於inline函數在編譯期會將字節碼copy到調用它的方法裏,因此編譯器會知道當前的方法中泛型對應的具體類型是什麼,而後把泛型替換爲具體類型,從而達到不被擦除的目的,在inline函數中咱們能夠經過reified關鍵字來標記這個泛型在編譯時替換成具體類型
咱們在用Gson解析json數據的時候,是如何解析數據拿到泛型類型 Bean
結構的?TypeToken 是一種方案,能夠經過getType() 方法獲取到咱們使用的泛型類的泛型參數類型,不過採用反射解析的時候,Gson構造對象實例時調用的是默認無參構造方法,因此依賴 Java 的 Class 字節碼中存儲的泛型參數信息,Java 的泛型機制雖然在編譯期間進行了擦除,可是Java 在編譯時會在字節碼裏指令集之外的地方保留部分泛型的信息,接口、類、方法定義上的全部泛型、成員變量聲明處的泛型都會被保留類型信息,其餘地方的泛型信息都會被擦除,這些信息被保存在 class 字節碼的常量池中,使用泛型的代碼處會生成一個 signature 簽名字段,經過簽名 signature 字段指明這個常量池的地址,JDK 提供了方法去讀取這些泛型信息的方法,利用反射就能夠得到泛型參數的具體類型,譬如:
(mList.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
複製代碼
通常Gson解析:
inline fun <reified T> Gson.fromJson(jsonStr: String) =
fromJson(json, T::class.java)
複製代碼
若是用Moshi解析:
inline fun <reified T> Moshi.fromJson(jsonStr: String) = Moshi.Builder().add(KotlinJsonAdapterFactory()).build().adapter(T::class.java).fromJson(jsonStr)
複製代碼