学习笔记-Java&kotlin泛型

157 阅读4分钟

泛型是一种参数化类型的特征。
优点:

  1. 类型检测:泛型将虚拟机运行期的类型检测提前到了编译期,使得开发者更容易排查错误
  2. 消除类型转换:使用泛型不需类型转换,可以避免因强转造成的不可预期的错误
  3. 增加代码的复用性:将类型以类似参数的形式传入

类型限制

使泛型类型可以传入满足限制条件的泛型,使得代码更加安全健壮  
  1. 协变:
例:
 class A<T> {
        private T t;
        public T getT(){
            return t;
        }
        public void setT(T t){}
    }
String a;
Object b;
String 为 Object 的子类 ,则a可以赋值给b , b = a 是正确的,。
但 A<String> 不属于 A<Object> 的子类,A<Object> = A<String> 会报错。
如果A<String> 属于 A<Object>的子类,即A<Object> = A<String> 成立,我们叫为协变

例:
A<? extends Object> c = new A<String>()

Java中使用 ? extends 上界通配符 使泛型具有协变性,extends 限制了传入类型的上界,使得传入的泛型必须满足 extends限制的条件 。 但此时 A<T> 只能读取不能修改

Object s = c.getT(); // 正确  此时c.getT() 类型为 <? extends Object> 向上转型是没问题的
c.setT(new Object()); // 报错  c.setT() 参数类型为 <? extends Object>

kotlin中使用 out来支持协变,等同于 Java 中的上界通配符 ? extends

  • 范围:
    可以传入的值包括满足 extends的直接子类和间接子类,也可以传入上界的类型本身。
    同时也有 implements 的意思,可以传入 interface
  1. 逆变:
如果A<Object> 属于 A<String>的子类,即A<String> = A<Object> 成立,我们叫为逆变

例:
A<? super String> c = new A<Object>()

Java中使用 ? super 下界通配符 。 super限制了?的子类型,称为下界。传入的泛型必须满足 super限制的条件。但此时的 A<T> 只能修改不能读取。

String s = c.getT(); // 错误  此时c.getT() 类型为 <? super String> 
c.setT(new String()); // 正确  c.setT() 参数类型为 <? super String>  向上转型

kotlin中使用 in来支持逆变,等同于 Java 中的下界通配符 ? super

  • 范围: 可以传入的值必须为满足 super的父类,也可以传入下界的类型本身。 同样可以传入 interface类型。

多重限制
Java 支持多重限定。

 <T extends A & B & B>

具有多个限定的类型变量是范围中列出的所有类型的子类型,如果范围之一是类,则必须首先指定它。例如:

Class A { /* ... */ } 
interface B { /* ... */ } 
interface C { /* ... */ } 
class D <T extends A & B & C> { /* ... */ }

如果未首先指定绑定A,则会出现编译时错误
kotlin 中的多重限制使用 where 关键字:

class D<T> where T : A, T : B{}

等同于 extends,中间用,连接。

类型擦除
Java中有泛型类型擦除机制,Java的泛型为伪泛型,只是程序在编译阶段提供的一种语法糖,Java jvm不支持泛型,在进入运行期之前,Java会替换代码中的泛型类型,所以,运行时需要知道泛型确切类型信息的操作都无法使用。
比如:

if (type instanceof T) { // IDE 会提示错误,illegal generic type for instanceof
   /* ... */
}

在运行时,Java会将泛型类型替换为其声明的上下边界 (例如:Object),并在调用时强转类型。因此,在运行时并不会产生额外的开销。

例如:
Class A<T> { /* ... */ } 
运行时会转换为
Class A<Object> { /* ... */ } 

Java里需要获取泛型确切类型的方式一般我们会提供一个 Class<T>的参数,例如:

<T> void typeCheck(Object type, Class<T> typeClz) {
    if (typeClz.isInstance(type)) {
        /* ... */
    }
}

kotlin 同样有类型擦除,在使用Java一样的方式解决外,kotlin提供了 关键字 reified 配合 inline
inline内联函数会在运行阶段将函数体替换到函数调用的地方,而被 reified标记的泛型也会被替换成具体类型。
因此,在kotlin就可以这样使用:

inline fun <reified T> typeCheck(type: Any) {
    if (type is T) { // 这里就不会在提示错误了
         /* ... */
    }
}