kotlin中的reified關(guān)鍵字

說kotlin中這個關(guān)鍵字之前先簡單說下Java中的泛型,我們在編程中足淆,出于復(fù)用和高效的目的尝胆,經(jīng)常使用泛型丧裁。泛型是通過在JVM底層采取類型擦除的機制實現(xiàn)的,Kotlin也是這樣含衔。

泛型

泛型是 Java SE 1.5 中的才有的特性煎娇,泛型的本質(zhì)是參數(shù)化類型,可分為泛型類贪染、泛型接口缓呛、泛型方法。在沒有泛型的情況的下只能通過對Object 的引用來實現(xiàn)參數(shù)的任意化杭隙,帶來的缺點就是要顯式的強制類型轉(zhuǎn)換哟绊,而強制轉(zhuǎn)換在編譯期是不做檢查的,容易把問題留到運行時痰憎,所以泛型的好處是在編譯時檢查類型安全票髓,并且所有的強制轉(zhuǎn)換都是自動和隱式的,提高了代碼的重用率铣耘,避免在運行時出現(xiàn) ClassCastException洽沟。

JDK 1.5 中引入了泛型來允許強類型在編譯時進行類型檢查;JDK 1.7 中泛型實例化類型具備了自動推斷的能力蜗细,譬如List<String> mList = new ArrayList<String>() 可以寫成 List<String> mList = new ArrayList<>()

類型擦除

泛型通過類型擦來實現(xiàn)裆操,編譯器在編譯時擦除所有泛型類型相關(guān)信息,即運行時就不存在任何泛型類型相關(guān)的信息炉媒,譬如 List<Integer> 在運行時僅用一個 List 來表示踪区,這樣做的目的是為了和 Java 1.5 之前版本進行兼容。


fun test() {

        val mList= ArrayList<String>()

        mList.add("123")

        Log.v("tag",mList[0])

    }

字節(jié)碼如下:


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存入集合中的

INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Objectlist實例中讀取出來Object然后轉(zhuǎn)換成String之后才能使用

CHECKCAST java/lang/String進行類型轉(zhuǎn)換

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

如果類型擦除和多態(tài)性發(fā)生沖突時就在子類中生成橋方法解決密强,接著如果調(diào)用泛型方法的返回類型被擦除則在調(diào)用該方法時插入強制類型轉(zhuǎn)換茅郎。

類型擦除的問題

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

  • 泛型讀取時會進行自動類型轉(zhuǎn)換問題或渤,所以如果調(diào)用泛型方法的返回類型被擦除則在調(diào)用該方法時插入強制類型轉(zhuǎn)換

  • 泛型類型參數(shù)不能是基本類型, 擦除后的Object 是引用類型不是基本類型

  • 無法進行具體泛型參數(shù)類型的運行時類型檢查系冗, instanceof ArrayList<?>

  • 不能拋出也不能捕獲泛型類的對象,因為異常是在運行時捕獲和拋出的薪鹦,而在編譯時泛型信息會被擦除掌敬,擦除后兩個 catch 會變成一樣的東西。不能在 catch 子句中使用泛型變量池磁,因為泛型信息在編譯時已經(jīng)替換為原始類型(譬如 catch(T) 在限定符情況下會變?yōu)樵碱愋?Throwable)奔害,如果可以在 catch 子句中使用,則違背了異常的捕獲優(yōu)先級順序


fun <T>Int.toCase():T?{

        return (this as T)

    }

上述代碼在轉(zhuǎn)換類型時地熄,沒有進行檢查华临,所以有可能會導(dǎo)致運行時崩潰,編譯器會提示unchecked cast警告,如果獲得的數(shù)據(jù)不是它期望的類型端考,這個函數(shù)會出現(xiàn)崩潰


fun testCase() {

        1.toCase<String>()?.substring(0)

    }

這就會出現(xiàn)TypeCastException錯誤雅潭,所以為了安全獲取數(shù)據(jù)一般都是需要顯式傳遞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的方式過于麻煩繁瑣尤其是傳遞多類型參數(shù),基于類型擦除機制無法在運行時得到T的類型信息却特,所以用到安全轉(zhuǎn)換操作符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")

        }

    }

那有沒有排除這種傳遞參數(shù)之外的優(yōu)雅實現(xiàn)扶供??裂明?

reified 關(guān)鍵字

reified關(guān)鍵字的使用很簡單:

  • 在泛型類型前面增加reified修飾

  • 在方法前面增加inline

    改進上述代碼

    
        inline fun <reified T> Int.toCase():T?{
    
            return if (this is T) {
    
                this
    
            } else {
    
                null
    
            }
    
        }
    
    

    testCase()方法調(diào)用轉(zhuǎn)成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替換結(jié)束

      var1 = var10000;

      System.out.println(var1);

  }

Inline的作用這里不再多說了椿浓,noinline和crossinline又是啥?這里可以看下漾岳。

泛型在運行時會被類型擦除轰绵,但是在inline函數(shù)中我們可以指定類型不被擦除, 因為inline函數(shù)在編譯期會將字節(jié)碼copy到調(diào)用它的方法里尼荆,所以編譯器會知道當(dāng)前的方法中泛型對應(yīng)的具體類型是什么左腔,然后把泛型替換為具體類型,從而達到不被擦除的目的捅儒,在inline函數(shù)中我們可以通過reified關(guān)鍵字來標(biāo)記這個泛型在編譯時替換成具體類型

示例

我們在用Gson解析json數(shù)據(jù)的時候液样,是如何解析數(shù)據(jù)拿到泛型類型 Bean 結(jié)構(gòu)的?TypeToken 是一種方案巧还,可以通過getType() 方法獲取到我們使用的泛型類的泛型參數(shù)類型,不過采用反射解析的時候鞭莽,Gson構(gòu)造對象實例時調(diào)用的是默認(rèn)無參構(gòu)造方法,所以依賴 Java 的 Class 字節(jié)碼中存儲的泛型參數(shù)信息麸祷,Java 的泛型機制雖然在編譯期間進行了擦除澎怒,但是Java 在編譯時會在字節(jié)碼里指令集以外的地方保留部分泛型的信息,接口阶牍、類喷面、方法定義上的所有泛型星瘾、成員變量聲明處的泛型都會被保留類型信息,其他地方的泛型信息都會被擦除惧辈,這些信息被保存在 class 字節(jié)碼的常量池中琳状,使用泛型的代碼處會生成一個 signature 簽名字段浴栽,通過簽名 signature 字段指明這個常量池的地址煤痕,JDK 提供了方法去讀取這些泛型信息的方法,利用反射就可以獲得泛型參數(shù)的具體類型碗脊,譬如:


(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)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末边翁,一起剝皮案震驚了整個濱河市翎承,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌符匾,老刑警劉巖审洞,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異待讳,居然都是意外死亡,警方通過查閱死者的電腦和手機仰剿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門创淡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人南吮,你說我怎么就攤上這事琳彩。” “怎么了部凑?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵露乏,是天一觀的道長。 經(jīng)常有香客問我涂邀,道長瘟仿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任比勉,我火速辦了婚禮劳较,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浩聋。我一直安慰自己观蜗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布衣洁。 她就那樣靜靜地躺著墓捻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坊夫。 梳的紋絲不亂的頭發(fā)上砖第,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天撤卢,我揣著相機與錄音,去河邊找鬼厂画。 笑死凸丸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袱院。 我是一名探鬼主播屎慢,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼忽洛!你這毒婦竟也來了腻惠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤欲虚,失蹤者是張志新(化名)和其女友劉穎集灌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體复哆,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡欣喧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梯找。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唆阿。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锈锤,靈堂內(nèi)的尸體忽然破棺而出驯鳖,到底是詐尸還是另有隱情,我是刑警寧澤久免,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布浅辙,位于F島的核電站,受9級特大地震影響阎姥,放射性物質(zhì)發(fā)生泄漏记舆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一呼巴、第九天 我趴在偏房一處隱蔽的房頂上張望氨淌。 院中可真熱鬧,春花似錦伊磺、人聲如沸盛正。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豪筝。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間续崖,已是汗流浹背敲街。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留严望,地道東北人多艇。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像像吻,于是被迫代替她去往敵國和親峻黍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容