泛型
解决代码泛化问题的一种手段(继承、接口等)。
泛型就是类型参数化。
设计上提供很多灵活性。
泛型、继承和接口,设计接口更加灵活。
类型安全,利用泛型可以在编译期间检查这类问题。避免用Object时,运行时才能发现类型问题。
使用时指定类型。
泛型解决什么问题?
泛型是解决代码泛化问题的一种手段。
代码复用,代码泛化是程序设计永恒的追求,希望同一份代码可以应用在多种情况,解决多种问题。
面相对象程序设计中,利用多态来解决代码复用问题,这里的多态是指通过继承、抽象类、接口等技术来实现代码复用。我们只需要针对父类、抽象类或者接口写代码,就可以应用于他们的子类或者实现类中。
多态可以解决很多问题,但是其中不可避免的问题是,在代码中,具体的类型信息丢失了,因为实际的类型在代码中向上转型为父类、抽象类或者接口了。所以当我们从这些代码中取回对象时,就需要进行强制类型转换。
强制类型转化会出现运行时问题,出错会因为严重的运行时异常,这在代码编译时无法检查。例如在java中,实现通用的集合,集合内存储的是Object,而当取回集合内的内容是,一定会强制类型转换到需要的类型,这部分在编译时是无法检查的。
如果可以把类型参数化,在使用集合的使用确定类型,这样,编译器可以进行类型检查,可以解决类型安全问题。这就是泛型技术的直接原因。
1 | //基于多态 |
语法
类型参数为非基础类型。基础类型可以使用包装类。
泛型类
1 | //此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 |
使用时,也可以不传参数类型。
泛型接口
1 | //定义一个泛型接口 |
当实现泛型接口的类,未传入泛型实参时
1 | /** |
当实现泛型接口的类,传入泛型实参时
1 | /** |
泛型方法
使用时不需要是声明类型参数,编译器可以通过函数签名推断。
相较于泛型类,可以优先使用泛型方法。
1 | /** |
核心问题
擦除
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
无法通过反射取得类型信息。
可以不使用类型参数。
导致运行时instanceof,new无法使用。
不能创建泛型数组。
Class
Class
1 | //返回类型参数占位符 |
类型限定
限定泛型类型,相当于与传统多态相结合,方便调用限定类型的方法。这里只能是extends,不能用super。
1 | <T extends <class> & <class>> |
通配符
限定通配符
泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。
主要是解决由于类型擦除导致泛型类没法调用实际泛型类所拥有的方法。
1 | //保证泛型类型必须是 T 的子类来设定泛型类型的上边界 |
以上为赋值限制,下面是读写操作限制。
extends上界能读不能写
List<? extends E>表示该list集合中存放的都是E的子类型(包括E自身),由于E的子类型可能有很多,但是我们存放元素时实际上只能存放其中的一种子类型(这是为了泛型安全,因为其会在编译期间生成桥接方法
该方法中会出现强制转换,若出现多种子类型,则会强制转换失败)为了安全,Java只能将其设计成不能添加元素。
虽然List<? extends E>不能添加元素,但是由于其中的元素都有一个共性–有共同的父类,因此我们在获取元素时可以将他们统一强制转换为E类型,我们称之为get原则。
super下届能写不能读
对于List<? super E>其list中存放的都是E的父类型元素(包括E),我们在向其添加元素时,只能向其添加E的子类型元素(包括E类型),这样在编译期间将其强制转换为E类型时是类型安全的,但是,由于该集合中的元素都是E的父类型(包括E),其中的元素类型众多,在获取元素时我们无法判断是哪一种类型,故设计成不能获取元素(获取类型只能是Object),我们称之为put原则。
PECS
producer-extends,consumer-super。
所有的comparable和comparator都是consumer。
非限定通配符
1 | //用任意泛型类型来代替,因为泛型是不支持继承关系的,所以<?>很大程度上弥补了这一不足。 |
example:
1 | public class Test1 { |
但是这里可以不写泛型。
可以将List<?>当做List