Java 泛型

泛型 Genrics

Java 泛型是在jdk5引入的新特性。它指定了接收参数的类型,避免了调用者乱传参,保留了代码的通用性和独特性。

泛型类和泛型方法

一般使用大写字母声明泛型,例如<T>

类型擦除 Type erasure

思考:能否插入一个字符串元素到一个整型数组中?

答案:通过反射是可以的,原理就是类型擦除。

add(E e)方法在字节码文件中显示的是add(Object o)

什么是类型擦除?

Java泛型机制是在编译级别实现的,通过编译后,编译器就会将泛型给擦除掉。到了运行阶段时,对于JVM来说便没有了泛型类型的对象,其中的泛型参数都会被替换成它的第一个上界或Object,这个过程称为泛型擦除。

为什么要有类型擦除?

这是为了兼容jdk5以前的代码。

Java 泛型弊端

较之其他语言,Java的泛型存在的弊端有:

  1. 不支持基本类型:泛型最终会被擦除成具体类型,而Object不能存储基本数据类型
  2. 只有原始类型的class:只能对原始类型进行检测,无法判断带泛型的类型(jdk17中可以判断)
  3. 不能实例化泛型参数:因为运行时无法确定具体类型
  4. 不能实例化泛型数组:类型擦除会让实例化的数组成为Object[]

变型 Variant

协变 Covariant

如果B是A的子类,F(B)也是F(A)的子类。

逆变 Contravariant

如果B是A的子类,F(B)是F(A)的父类。

不变 Invariant

如果B是A的子类,F(B)和F(A)无关。

 

在Java中,泛型默认是不变的;而数组支持协变。那么是否可以让泛型支持逆变和协变呢?答案是可以的。

泛型的协变和逆变

协变和上界

使用通配符?和关键字extends,定义泛型的上界,来实现协变。

可以想象,协变存在类型安全隐患,泛型为了杜绝这种隐患采取一刀切的方式:只要声明了上界,就不允许插入null以外的元素。

逆变和下界

 使用通配符?和关键字super,定义泛型的下界,来实现逆变。

PECS原则

作为元素的生产者Producer,要用extends声明上界支持协变,以便获取元素;

作为元素的消费者Consumer,要用super来声明下界支持逆变,以便新增元素。

应用

一个典型的应用是Collections类的copy方法:

  • 协变适用于只读不写的场景。
  • 逆变适用于只写不读的场景。
  • 既要读有要写时,使用不变。

桥接方法

泛型方法重写

 

重写父类泛型方法时,在子类方法的入参类型和父类不一致,似乎违背了重写的原则(返回类型,方法名,参数列表都相同),但依然重写成功。

这是因为编译时生成了桥接方法setValue(Object)去调用子类方法setValue(String)

子类方法重写

顺便一提,当子类重写方法的返回类型是父类方法返回类型时的子类时,编译器也会自动生成桥接方法。

posted @ 2023-06-23 10:14  !ɹO  阅读(22)  评论(0编辑  收藏  举报