阅读 218

Kotlin与Java泛型

  • 使用参数化类型的概念,允许在不指定具体类型的情况下进行编程,将类型当作参数传递给一个类或者是方法

1.基本使用

泛型类

  • 在类名后面使用 ,此语法结构是给类定义一个泛型
class MyClass<T> {
    fun method(params: T) {}
}
复制代码

泛型方法

  • 在方法名的前面加上 , 此语法结构给方法定义一个泛型
fun <T> method(params: T) {}
复制代码

泛型接口

  • 在接口名后面加上 此语法结构给接口定义一个泛型
interface MyInterface<T> {
    fun interfaceMethod(params: T)
}
复制代码

2.泛型边界

  • 在泛型的参数上设置限制条件,这样可以强制泛型可以使用的类型,更重要的是可以按照自己的边界类型来调用方法

单个边界

  • 使用 <T : Class> 这种语法结构,如果不指定泛型的边界,默认为 Any?
class MyClass<T : Number> {
    fun <T : Number> method(params: T) {}
}
复制代码

多个边界

  • 如果有多个边界,可以使用 where 关键字,
  • 中间使用 : 隔开,多个边界中只能有一个边界是类,且类必须放在最前面
open class Animal
interface Eat
interface Sleep
class MyAnimal<T> where T : Animal, T : Eat, T : Sleep {
    fun <T> method(params: T) where T : Animal, T : Eat, T : Sleep {}
}
复制代码

3.泛型擦除

  • 通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上

  • 泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉

    之所以要使用泛型擦除是为了兼容 JDK 1.5 之前运行时的类加载器,避免因为引入泛型而导致运行时创建不必要的类

JAVA泛型擦除的具体步骤

  1. 擦除泛型类型参数信息,遵循如下规则进行擦除

    <T> 擦除后变为 Object
    <T extends A> 擦除后变为 A
    <? extends A> 擦除后变为 A
    <? super A> 擦除后变为Object
    复制代码

    如果类型参数是有界的,则将每个参数替换为其第一个边界

    如果类型参数是无界的,则将其替换为 Object类型擦除的规则,此行为会使很多方法失效

  2. 如果需要则插入类型转换,以保持类型安全。

    public class JavaGenericWipe {
        public static void main(String[] args) {
            List<String> stringList = new ArrayList<>();
            stringList.add("erdai");
            stringList.add("666");
    
            for (String s : stringList) {
                System.out.println(s);
            }
        }
    }
    
    //编译时生成的字节码文件翻译过来大致如下
    public class JavaGenericWipe {
        public JavaGenericWipe() {
        }
        public static void main(String[] args) {
            List<String> stringList = new ArrayList();
            stringList.add("erdai");
            stringList.add("666");
            Iterator var2 = stringList.iterator();
            while(var2.hasNext()) {
                //编译器做了强转的工作
                String s = (String)var2.next();
                System.out.println(s);
            }
        }
    }
    复制代码

    会在某些情况下生成的字节码文件之中,对其进行类型转换,保证类型安全

  3. 如果需要则生成桥接方法以在子类中保留多态性

    class Node {
       public Object data;
       public Node(Object data) {this.data = data;}
       public void setData(Object data) { this.data = data;}
    }
    
    class MyNode extends Node {
       public MyNode(Integer data) {super(data);}
       public void setData(Integer data) {super.setData(data);}
    }
    
    //编译时生成的字节码文件翻译过来大致如下
    class MyNode extends Node {
       public MyNode(Integer data) {super(data);}
       // 编译器生成的桥接方法
       public void setData(Object data) {setData((Integer) data);}
       public void setData(Integer data) {
           System.out.println("MyNode.setData");
           super.setData(data);
       }
    }
    复制代码

    会在某些情况下生成的字节码文件中,生成桥接方法,实现多态

4.泛型实化

  • 泛型实化是Kotlin独有的,其实现原理是通过inline函数的特性,会对代码进行替换,即在inline函数内部的泛型,最终也会通过实际的类型进行替换
//通过inline实现startActivity的泛型实化
inline fun <reified T> Context.startActivity(block: Intent.() -> Unit) {
    val intent = Intent(this, T::class.java)
    intent.block()
    this.startActivity(intent)
}
复制代码

5.泛型协变,逆变,不变

协变

  • 语法规则为<out T> 类似于 Java 的 <? extends Bound>

  • 它限定的类型是当前上边界类或者其子类,如果是接口的话就是当前上边界接口或者实现类

  • 协变的泛型变量只读,不可以写,可以添加 null ,但是没意义

    限定的类型是当前上边界类或者其子类,它无法确定自己具体的类型,因此编译器无法验证类型的安全,所以不能写

class Simple<out T> {}
复制代码

逆变

  • 语法规则为<in T> 类似于 Java 的 <? super Bound>

  • 它限定的类型是当前下边界类或者其父类,如果是接口的话就是当前下边界接口或者其父接口

  • 逆变的泛型变量只能写,不建议读

    1.限定的类型是当前下边界类或者其父类,虽然它也无法确定自己具体的类型,但根据多态,它能保证自己添加的元素是安全的,因此可以写

    2.获取值的时候,会返回一个 Object 类型的值,而不能获取实际类型参数代表的类型,因此建议不要去读,如果你实在要去读也行,但是要注意类型转换异常

class Simple<in T> {}
复制代码

不变

  • 语法规则为,和Java一致
  • 指泛型实际类型没有任何继承关系,是不变的
class SimpleData<T>{}
复制代码

无界

  • 语法规则为 <*>,它等价于 <out Any>,类似于 Java 中的 <?>,
    • <?>实际上它等价于 <? extends Object>,也就是说它的上边界是 Object 或其子类,因此使用无界通配符的变量同样只读,不能写,可以添加 null ,但是没意义
  • 在定义一个类的时候你如果使用<out T : Number> ,那么 * 就相当于 <out Number>
val a: ArrayList<*> = ArrayList<Int>()
复制代码
文章分类
Android