kotlin中的reified关键字

说kotlin中这个关键字之前先简单说下Java中的泛型,我们在编程中,出于复用和高效的目的,经常使用泛型。泛型是通过在JVM底层采取类型擦除的机制实现的,Kotlin也是这样。

泛型

泛型是 Java SE 1.5 中的才有的特性,泛型的本质是参数化类型,可分为泛型类、泛型接口、泛型方法。在没有泛型的情况的下只能通过对Object 的引用来实现参数的任意化,带来的缺点就是要显式的强制类型转换,而强制转换在编译期是不做检查的,容易把问题留到运行时,所以泛型的好处是在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率,避免在运行时出现 ClassCastException。

JDK 1.5 中引入了泛型来允许强类型在编译时进行类型检查;JDK 1.7 中泛型实例化类型具备了自动推断的能力,譬如List<String> mList = new ArrayList<String>() 可以写成 List<String> mList = new ArrayList<>()

类型擦除

泛型通过类型擦来实现,编译器在编译时擦除所有泛型类型相关信息,即运行时就不存在任何泛型类型相关的信息,譬如 List<Integer> 在运行时仅用一个 List 来表示,这样做的目的是为了和 Java 1.5 之前版本进行兼容。


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

INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Objectlist实例中读取出来Object然后转换成String之后才能使用

CHECKCAST java/lang/String进行类型转换

泛型擦除在编译成字节码时首先进行类型检查,再进行类型擦除(即所有类型参数都用限定类型替换,包括类、变量和方法如果类型变量有限定则原始类型就用第一个边界的类型来替换,譬如 class Test<T extends Comparable & Serializable> {} 的原始类型就是 Comparable)

如果类型擦除和多态性发生冲突时就在子类中生成桥方法解决,接着如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换。

类型擦除的问题

类型擦除会有一系列的问题,这里不展开了

  • 泛型读取时会进行自动类型转换问题,所以如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换

  • 泛型类型参数不能是基本类型, 擦除后的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)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容