Java范型有点类似与C++中的模板类或者模板方法,对类型进行抽象。
1. 泛型是什么
举一个JDK中的例子:
这里创建了三个List,分别存储String、Integer和Double。
在调用List的add
和get
方法时,会对类型进行检查。
这里ArrayList将内存存储的类型参数化,各种类型的变量都可以组装成对应的List,而不必针对每个类型分别实现一个构建ArrayList的类。
2. 没有泛型会怎样
我们实现两个能够设置点坐标的类,分别设置Integer类型的点坐标和Float类型的点坐标:
那现在有个问题:大家有没有发现,他们除了变量类型不一样,一个是Integer一个是Float以外,其它并没有什么区别!那我们能不能合并成一个呢?
答案是可以的,因为Integer和Float都是派生自Object的,我们用下面这段代码代替:
即全部都用Object来代替所有的子类;
在使用的时候是这样的:
特别注意取值时需要强制类型转换。
但问题来了:注意,注意,我们这里使用了强制转换,我们这里setX()和getX()写得很近,所以我们明确的知道我们传进去的是Float类型,那如果我们记错了呢?
比如我们改成下面这样,编译时会报错吗:
不会!!!我们问题的关键在于这句:
强制转换时,编译不会出错。因为编译器也不知道你传进去的是什么,而floatPoint.getX()返回的类型是Object,所以编译时,将Object强转成String是成立的。必然不会报错。
而在运行时,则不然,在运行时,floatPoint实例中明明传进去的是Float类型的变量,非要把它强转成String类型,肯定会报类型转换错误的!
那有没有一种办法在编译阶段,即能合并成同一个,又能在编译时检查出来传进去类型不对呢?当然,这就是泛型。
3. 泛型类
我们先看看泛型的类是怎么定义的:
上在我们只定义了一个泛型变量T,那如果我们需要传进去多个泛型要怎么办呢?
只需要在类似下面这样就可以了:
4. 泛型接口
在接口上定义泛型与在类中定义泛型是一样的,代码如下:
4.1 使用方法一:非泛型类
|
|
要清楚的一点是InfoImpl不是一个泛型类!因为他类名后没有
然后在在这里我们将Info
4.2 使用方法二:泛型类
在方法一中,我们在类中直接把Info
5. 泛型函数
|
|
上面分别是静态泛型函数和常规泛型函数的定义方法,与以往方法的唯一不同点就是在返回值前加上
使用方法如下:
6. 使用Class传递泛型类Class对象
有时,我们会遇到一个情况,比如,我们在使用JSON解析字符串的时候,代码一般是这样的
这段代码的意义就是根据SuccessModel解析出List
那现在,我们把下面这句组装成一个泛型函数要怎么来做呢?
首先,我们应该把SuccessModel单独抽出来做为泛型变量,但parseArray()中用到的SuccessModel.class要怎么弄呢?
先来看代码:
注意到,我们用的Class
这是因为Class
7. 类型绑定
有时候,我们在泛型类或者泛型函数中希望泛型参数能够是某个接口的实现,这样我们可以调用接口方法。
7.1 extends
有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:
此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。
一定要非常注意的是,这里的extends不是类继承里的那个extends!两个根本没有任何关联。在这里extends后的BoundingType可以是类,也可以是接口,意思是说,T是在BoundingType基础上创建的,具有BoundingType的功能。目测是Java的开发人员不想再引入一个关键字,所以用已有的extends来代替而已。
我们假设,我们有很多种类的水果,需要写一个函数,打印出填充进去水果的名字:
为此,我们先建一个基类来设置和提取名字:
然后写个泛型函数来提取名字:
从这段代码也可以看出,类型绑定有两个作用:
- 对填充的泛型加以限定
- 使用泛型变量T时,可以使用BoundingType内部的函数。
有关绑定限定的用法,其实我们可以同时绑定多个绑定,用&连接,比如:
7.2 通配符
|
|
无边界通配符?
则只能用于填充泛型变量T,表示通配任何类型!!!!再重复一遍:?只能用于填充泛型变量T。它是用来填充T的!!!!只是填充方式的一种!!!
构造泛型实例时,如果省略了填充类型,则默认填充为无边界通配符!如下写法是对等的:
7.2.1 通配符?的extends绑定
从上面我们可以知道通配符?可以代表任意类型,但跟泛型一样,如果不加以限定,在后期的使用中编译器可能不会报错。所以我们同样,要对?加以限定。
同样,通配符?
可以用extends绑定范围。
此时,当将T填充为String和Object时,赋值给point就会报错!
这里虽然是指派生自Number的任意类型,但new Point
再重复一遍:无边界通配符只是泛型T的填充方式,给他加上限定,只是限定了赋值给它(比如这里的point)的实例类型。
如果想从根本上解决乱填充Point的问题,需要从Point泛型类定义时加上
注意:利用<? extends Number>定义的变量,只可取其中的值,不可修改。
正因为point的类型为 Point<? extends Number> point,那也就是说,填充Point的泛型变量T的为<? extends Number>,这是一个什么类型?未知类型!!!怎么可能能用一个未知类型来设置内部值!这完全是不合理的。也就是说,它可能是Integer,也可能是Float,那么就不能往里面写数据。
但取值时,正由于泛型变量T被填充为<? extends Number>,所以编译器能确定的是T肯定是Number的子类,编译器就会用Number来填充T。
7.2.2 通配符?的super绑定
如果说 <? extends XXX>指填充为派生于XXX的任意子类的话,那么<? super XXX>则表示填充为任意XXX的父类!
我们先写三个类,Employee,Manager,CEO,分别代表工人,管理者,CEO
然后,如果我这样生成一个变量:
它表示的意思是将泛型T填充为<? super Manager>,即任意Manager的父类;也就是说任意将List
new ArrayList
注意:super通配符实例内容:能存不能取。
由于list是List<? super Manager>类型,那么list中可以填充Manager和Manager的子类,它们一定是<? super Manager>的子类。
但是想添加Employee是不可以的,因为Employee不一定是<? super Manager>的子类。比如用Manager填充了T。
而从list中取出来的数据是Object类型的,需要强制类型转换。
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
- 如果你想从一个数据类型里获取数据,使用 ? extends 通配符(能取不能存)
- 如果你想把对象写入一个数据结构里,使用 ? super 通配符(能存不能取)
- 如果你既想存,又想取,那就别用通配符。