kotlin中的reified關鍵字

說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/Objectlist實例中讀取出來Object而後轉換成String以後才能使用安全

CHECKCAST java/lang/String進行類型轉換bash

泛型擦除在編譯成字節碼時首先進行類型檢查,再進行類型擦除(即全部類型參數都用限定類型替換,包括類、變量和方法若是類型變量有限定則原始類型就用第一個邊界的類型來替換,譬如 class Test<T extends Comparable & Serializable> {} 的原始類型就是 Comparable)markdown

若是類型擦除和多態性發生衝突時就在子類中生成橋方法解決,接着若是調用泛型方法的返回類型被擦除則在調用該方法時插入強制類型轉換。函數

類型擦除的問題

類型擦除會有一系列的問題,這裏不展開了

  • 泛型讀取時會進行自動類型轉換問題,因此若是調用泛型方法的返回類型被擦除則在調用該方法時插入強制類型轉換
  • 泛型類型參數不能是基本類型, 擦除後的Object 是引用類型不是基本類型
  • 沒法進行具體泛型參數類型的運行時類型檢查, instanceof ArrayList<?>
  • 不能拋出也不能捕獲泛型類的對象,由於異常是在運行時捕獲和拋出的,而在編譯時泛型信息會被擦除,擦除後兩個 catch 會變成同樣的東西。不能在 catch 子句中使用泛型變量,由於泛型信息在編譯時已經替換爲原始類型(譬如 catch(T) 在限定符狀況下會變爲原始類型 Throwable),若是能夠在 catch 子句中使用,則違背了異常的捕獲優先級順序
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關鍵字的使用很簡單:

  • 在泛型類型前面增長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)
複製代碼
相關文章
相關標籤/搜索