——写出来就是为了内化

泛型的定义

什么是泛型,泛型的本质就是参数化类型。也就是说,泛型就是将所操作的数据类型作为参数的一种语法。

public class Play<T>{
    T play();
}

泛型的作用

写出更灵活通用的代码

泛型代码就好像模板,生产时根据输入的不同材料,就可以返回不同的结果。这也是泛型的最初宗旨。

将代码安全性检查提前到编译期

在没有泛型之前,容器(比如List)无论加入什么数据,存储的数据类型都是向上转型Object,从而在取的时候都不知道是否合法,从而引发ClassCastException。使用泛型后,能让编译器在编译的时候借助传入的类型参数检查对容器的插入,获取操作是否合法,从而解决容器的类型安全。

省去了类型的强制转换

同上的理由一样,在没有泛型之前,获取到的数据都是通过强制制转换来操作。加入泛型后,由于编译器知道了具体的类型,因此编译期会自动进行强制转换,使得代码更加优雅。

泛型的实现

泛型类、泛型接口、泛型函数

//泛型类
public class Data<T>{
    private T data;

    public T getData(){
        return data;
    }
}

//泛型接口
public interface IData<T>{
    void deal(T t);
}

//泛型函数
public <T> T getData(T t){
    return t;
}

泛型的通配符

泛型中有三种通配符形式:

  1. <?>无限制通配符
  2. <? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
  3. <? super E> super 关键字声明了类型的下界,表示参数化类型可能是指定类型,或者是此类型的父类。

extends和super,也确定了泛型的边界

泛型的擦除

当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)。

擦除的原理

泛型就是个语法糖,Java编译器会将泛型擦除变成原始的数据类型,接着会在相应的地放加入类型的转换,转换成想到的数据类型。这些操作都是编译器后台进行,可以保证类型安全。

擦除也导致泛型的不可变性,Java 中数组是协变的,泛型是不可变的。

  • 协变:如果 A 是 B 的父类,并且 A 的容器(比如 List< A>) 也是 B 的容器(List< B>)的父类,则称之为协变的(父子关系保持一致)
  • 逆变:如果 A 是 B 的父类,但是 A 的容器 是 B 的容器的子类,则称之为逆变(放入容器就篡位了)
  • 不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变

泛型的规则

  • 泛型的参数类型只能是类(包括自定义类),不能是简单类型。
  • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
  • 泛型的类型参数可以有多个
  • 泛型的参数类型可以使用 extends 语句,习惯上称为“有界类型”
  • 泛型的参数类型还可以是通配符类型,例如 Class
  • 静态资源不认识泛型。