说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)
复制代码