介绍

受限于 Java 字节码的限制,kotlin 依旧遵循着 Java 的 泛型 规范,在编译期间判断一个元素是否是某个泛型的子类,如下是 Kotlin 泛型例子,总体来说,大似相同,只是 Kotlin 将泛型的上界约束在最后进行声明

  1. interface Edge<T : Number> {
  2. }
  3. class DirectEdge<T>(val from: T, val to: T) : Edge<T>
  4. where T : Comparable<T>, T : Number {
  5. }

但是,与 默认Java的编译器相比较,Kotlin 编辑器为了在编译期间和运行期间获取类型做出了更多的努力,使得能够进行一些类型判断和类型引用,不过总体来说,提升不大

实例化参数

编译期间

  1. public static void main(String[] args) {
  2. List<DirectEdge<Double>> directEdges = new ArrayList<>();
  3. // 编译器错误,Illegal generic type for instanceof
  4. if (directEdges instanceof List<DirectEdge<Double>>){}
  5. }
  6. fun main() {
  7. val directEdges = ArrayList<DirectEdge<Double>>()
  8. // kotlin 在编译期间通过类型推断允许改判断,但字节码层面,仍然是 instanceOf List
  9. // 增加了编译期间判断的可靠性
  10. if (directEdges is List<DirectEdge<Double>>) {
  11. }
  12. }

运行期间

为了拜托泛型擦除导致的类型擦除,Kotlin 依靠 inline 可以声明一个带有具体实参的函数

inline fun <reified T> isType(value: Any): Boolean = value is T

fun main() {
    isType<String>("type").apply(System.out::println)
    isType<String>(12).apply(System.out::println)
}

kotlin 中 inline 在编译期间会把每一次函数调用都换成具体函数细节的实现,接下来看看编译后的字节码
第一行代码的解释:

   L0
    LINENUMBER 19 L0
    // push "type" to L0
    LDC "type"
    ASTORE 0
   L2-3
   // push 1 to L0
    LINENUMBER 22 L2
    ICONST_1
    ISTORE 0
   L4
   // push PrintStream to L1
    LINENUMBER 19 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ASTORE 1
   L5-8
   // push 0 to L2 L3 L4 L4 
    ICONST_0
    ISTORE 2
    ...
   L9
    ALOAD 1
    ILOAD 4
   L10
    LINENUMBER 19 L10
    INVOKEVIRTUAL java/io/PrintStream.println (Z)V

public final static main(){
    boolean r4=true;
    System.out.println(r4);
}
 public final static main()V
   L15
   // push 12 to L0, 0 to L1
    LINENUMBER 20 L15
    BIPUSH 12
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
    ASTORE 0
    ICONST_0
    ISTORE 1
   L17
    LINENUMBER 23 L17
    // L0 instanceOf String to L0
    ALOAD 0
    INSTANCEOF java/lang/String
   L18
    ISTORE 0
   L19
   // push PrintStream to L1
    LINENUMBER 20 L19
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ASTORE 1
   L22
   // push L0 to L4
    ILOAD 0
    ISTORE 4
   L24
    ALOAD 1
    ILOAD 4
   L25
    LINENUMBER 20 L25
    INVOKEVIRTUAL java/io/PrintStream.println (Z)V

public final static main(){
    int L0=12;
    boolean L0= L0 instanceOf String;
    PrintStram L1=System.out.PrintSteam()
    boolean L4=L0
    printSteam.Println(L4)
}

通过字节码的判断,对于 isType(“type”) Kotlin 能够直接简化成 true,但是 isType(12)就将原封不动的进行内联,执行具体的代码,将 泛型T 实例化为String

得出结论,Kotlin 依旧无法摆脱泛型的擦除,只是依赖于字节码的重编写,将具体函数内联过程时,将 泛型类型 T 替代为具体类型 String, 所以类型参数的实例化只适用于下面这种场景,在编译期间,能够让编译器感知到这个具体类型是什么

:::warning Note1: 在泛型类的内部,声明了内联函数却是无法调用的,Kotlin 规定 isType() 中 T 是 reified 的,那么泛型类型必须是一个明确的实体类
Note2: 实例化参数只试用与获取具体的类型和进行类型判断,并不能用来创建实例 ::: 通过 reified 的妙用,在编写工具函数时,能够代替具体类 Class 的引用 ,如

inline fun <reified T> loadService(){
    return ServiceLoader.load(T::class.java);
}

子类型

谈起泛型,必不可少的不能不讨论类型参数的泛化,Kotlin 对泛化做出了一些不同Java 的变化,有如下概念:

:::info 协变与逆变只是 kotlin 在编译期进行类型判断,根据字节码可以判断出,最后都是泛型擦除到 Object,无其他元素添加 ::: 如下展示一个逆变类的错误例子与正确用法

// 错误例子,逆变类说明的是 Consumer<B> 是 Consumer<A> 的子类型, 但对于具体 Consumer中的泛型 T,传入参数
//时仍然要保持类型匹配,这跟协变与逆变无关
fun main(){
    val aConsuumer=Consumer<A>()
    val bConsumer=Consumer<B>()
    val a=A()
    val b=B()
    aConsumer(a)
    // 错误,类型是不匹配
    aConsumer(b)
    bConsumer(a)
    bConsumer(b)
}

fun doConsumer(consumer:Consumer<A>){}
fun main() {
    doConsumer(aConsumer)
    doConsumer(bConsumer)
}

:::info 总的来说,利用的是Java 继承的一套,可以将 斜边类返回的是一个父类A,而具体返回 B ,C 都没有关系,因为都是继承与A,都含有A的方法 :::

interface Transformer<T>{
    fun transform(in:T) : out: T{
    }
}