跳至主要內容

Java 中的泛型

JI,XIAOYONG...大约 4 分钟

Java 中的泛型实现了参数类型化的概念。

主要有以下形式:

class OneClazz<T>{
    T t;
    <Y> void fun(){}
}

本文主要记录 Java 泛型一些比较特殊的知识点。

泛型特性

泛型在 Java SE5 被引入,可以在类和方法中,将类型作为类型参数传入。

泛型类型参数会在实际运行时被擦除到他的第一个边界。如<T>会被擦除为Objet,而<T extends ClazzA>则会被擦除为ClazzA

需要注意的地方

不能有泛型数组

这是因为 Java 中 Object[]默认为所有数组的父类,如下代码虽然在编译期不会报错,但是在运行时会被检查出 objArr 指向的数组实际类型(String)和要赋予的类型(Integer)不一致而报错。

也就是说,数组只能存放定义的实际类型以及他们的子类型

Object[] objArr = new String[10];
objArr[0] = 1;

但是,如果支持泛型数组:由于泛型类型参数会在运行时被擦除,导致即使到了运行时也无法发现这个错误,从而会导致错误。

如下,加入支持泛型参数,则 objArr1 中实际保存的类型(Map<String,Integer>),在编译的时候由于 objArr1 和 objArr2 都是 Object 类型的数组,编译通过;在运行的时候,由于 Map 中的泛型参数类型已经被擦除,也无法区分 objArr1 和 objArr2 中实际指向的两个 Map<K,V>数组,也是合法的,这样原本定义的是 Map<String,Integer>数组,却可以保存任何类似的 Map,而这本来是不允许的。

Object[] objArr1 = new Map<String,Integer>[10];
Object[] objArr2 = new Map<Double,Integer>[10];
objArr1[0] = objArr2[0];

Collections 类通过一种别扭的方法绕过了这个问题,在 Collections 类编译时会产生类型未检查转换的警告。

ArrayList具体实现的构造函数如下:

class ArrayList<V>{
    private V[] backingArray;
    public ArrayList(){
        backingArray = (V[])new Object()[DEFAULT_SIZE];
    }
}

为何这些代码在访问 backingArray时没有产生 ArrayStoreException呢?无论如何,都不能将 Object数组赋给 String数组。因为泛型是通过擦除实现的,backingArray的类型实际上就是 Object[],因为 Object代替了 V

这意味着:实际上这个类期望 backingArray是一个 Object数组,但是编译器要进行额外的类型检查,以确保它包含 V类型的对象。

来源:https://www.ibm.com/developerworks/cn/java/j-jtp01255.htmlopen in new window

泛型容器

由于泛型的类型在运行时会被擦除,所以将类型检查放到了编译期。

List<Clazz> 泛型列表只能保存指定泛型类型T的数据,而不能保存其子类。

class Fruit{}
class Apple extends Fruit{}
class Jonathan extends Apple{}
//编译时报错,类型不兼容
List<Fruit> fruits = new ArrayList<Apple>();

但是能保存 Fruit 的容器应该也要能安全的保存 Apple,为了实现这一点,类似于数组中Object[] arr = Apple[]的向上转型,可以使用?引入协变。

协变

List<? extends T> 可以合法的指向一个List< SubT>,这个过程会完成自动向上转型,成为可以持有某个诸如 T 或者 T 的子类的 List,但是编译器不知道这个具体是什么,所以拒绝向其中传递任何类型对象,即使 Object 也不行。

可以这么想,<? extends T>表示的是 T 的子类,那么List<? extends T> 保存的便是T 的某个子类,所以不能保存 Object 或者 T 等类型,又由于列表不能保存不同的类型,所以也不能保存任何 T 的子类,即容器将数组在运行时才会有的类型检查放到了编译期(原因是运行时类型会被擦除)。

List<? extends Fruit> fruits = new ArrayList<Apple>();//可以安全的应用
fruits2.add(new Apple());//编译时报错,类型转化错误
fruits2.add(new Fruit());//编译时报错

逆变

List<? super T> 主动声明通配符?的超类型为T,即 List 保存的是T 的某个父类,那么 List 也可以安全的保存T 或者 T 的子类

void fun(List<? super Apple> apples){
    apples.add(new Apple());
    apples.add(new Jonathan());
    apples.add(new Fruit());//error 类型错误
}

参考资料

java 为什么不支持泛型数组? - ylxfc 的回答 - 知乎open in new window

Oracle Java 泛型原理open in new window

Java 理论和实践 - 了解泛型 - 识别和避免学习使用泛型过程中的陷阱open in new window

Java 泛型(二)协变与逆变open in new window

《Java 编程思想 第 4 版》

文章标题:《Java 中的泛型》
本文作者: JI,XIAOYONG
发布时间: 2018/11/03 11:01:34 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/b2cdb69e.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8