What
什么是泛型,看表面的意思,泛型就是指广泛的、普通的类型。在 Java 中是指把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
ArrayList 就是使用泛型的典型案例,如下所示:
可以看到,通过菱形语法(’<>’)可以将 List 内元素的类型限定为 String、Integer 和 Double 类型。需要注意的是 <> 内的类型只能是引用类型,当然对于基本类型,可以使用对应的包装类型。
Why
没有泛型会怎样?举个例子:实现两个能够设置点坐标的类,分别设置 Integer 类型的点坐标和 Float 类型的点坐标,代码如下:
大家可以发现一个问题:它们除了变量类型不一样(一个是 Integer 一个是 Float)以外,其它并没有什么区别。那我们能不能合并成一个呢?
答案是可以的,因为 Integer 和 Float 都是派生自 Object 的,我们用下面这段代码代替:
即全部都用 Object 来代替所有的子类。在使用的时候是这样的:
在设置的时候,使用 new Integer(100) 来新建一个 Integer 对象,然后在取值的时候,进行强制转换。由于设置的类型和强转的类型是一致的,所以不会出错。
同理,FloatPoint 的设置和取值也是类似的,代码如下:
但是上面的代码很容易发生运行时异常,且编译是不会报错。比如我们改成下面这样:
因为编译器也不知道你传进去的是什么,而 floatPoint.getX() 返回的类型是 Object,所以编译时,将 Object 强转成 String 是成立的。必然不会报错。而在运行时,floatPoint 实例中明明传进去的是 Float 类型的变量,非要把它强转成 String 类型,肯定会报类型转换错误的!
那有没有一种办法在编译阶段,即能合并成同一个,又能在编译时检查出来传进去类型不对呢?当然,这就是泛型。如下所示:
如果使用不当,编译器会提示错误信息。
这样代码就不需要强制转换,而且只要在编译时期没有出现警告,那么运行时期就不会出现 ClassCastException 异常。
Type & How
1. 泛型类
泛型类也就是把泛型定义在类上,这样用户在使用类的时候才把类型给确定下来。
可以看到上面这个程序,在使用时如果定义了类型,那么在使用时就可以不用进行强制类型转换,直接就可以得到一个 T 类型的对象。
下面来看看泛型是怎么定义及使用的吧。
- Point<T>
即在类名后面加一个尖括号,括号里是一个大写字母。这里写的是 T,其实这个字母可以是任何大写字母,意义是相同的。 - T
这个 T 表示派生自 Object 类的任何类,比如 String、Integer、Double 等等。这里要注意的是,T 一定是派生于 Object 类的。为方便起见,大家可以在这里把 T 当成 String,即 String 在类中怎么用,那 T 在类中就可以怎么用!所以它可以定义变量,作为返回值,作为参数传入。
|
|
使用方式参考上面,不再表述。
2. 泛型变量
上在我们只定义了一个泛型变量T,那如果我们需要传进去多个泛型要怎么办呢?只需要类似下面这样就可以了:
也就是在原来的 T 后面用逗号隔开,写上其它的任意大写字母即可。想加几个就加几个,比如我们想加五个泛型变量,那应该是这样的:
举个例子,我们在 Point 上再另加一个字段 name,也用泛型来表示,那要怎么做?代码如下:
使用方式如下:
从上面的代码中,可以明显看出,就是在新添加的泛型变量 U 用法与 T 是一样的。上面提到任意一个大写字母都可以,他们的意义是完全相同的,但为了提高可读性,大家还是用有意义的字母比较好,一般来讲,在不同的情境下使用的字母意义如下:
- E — Element,常用在 java Collection 里,如:List
、Iterator 、Set ; - K, V — Key, Value,如:代表 Map 的键值对;
- N — Number,数字;
- T — Type,类型,如 String,Integer 等等。
使用约定俗成的大写字母,可以提高可读性。
3. 泛型接口
在接口上定义泛型与在类中定义泛型是一样的,代码如下:
使用方式一:非泛型子类
|
|
InfoImpl 不是一个泛型类,因为他类名后没有
使用方法二:泛型子类
在方法一中,我们在类中直接把 Info
在这个类中,我们构造了一个泛型类 InfoImpl
使用泛型类来继承泛型接口的作用就是让用户来定义接口所使用的变量类型,而不是像方法一那样,在类中写死。
那我们稍微加深点难度,构造一个多个泛型变量的类,并继承自 Info 接口:
在这个例子中,我们在泛型类中定义三个泛型变量 T、K、U,并且把第三个泛型变量 U 用来填充接口 Info。所以在这个例子中 Info 所使用的类型就是由 U 来决定的。代码如下:
4. 泛型函数
上面我们讲解了类和接口的泛型使用,下面我们再说说,怎么单独在一个函数里使用泛型。新建一个普通的类 StaticFans,然后在其中定义了两个泛型函数:
上面分别是静态泛型函数和常规泛型函数的定义方法,与以往方法的唯一不同点就是在返回值前加上
5. 泛型返回值
有时,我们需要将泛型变量返回,比如下面这个函数:
6. 泛型数组
在写程序时,大家可能会遇到类似 String[] list = new String[8]; 的需求,这里可以定义 String 数组,当然我们也可以定义泛型数组,泛型数组的定义方法为 T[],与 String[] 是一致的,下面看看用法:
通配符
1. 无界
“?”可以接收任何类型。
2. 上界
这种情况下能够接收 A 类或者 A 类的子类。
A 类也可以换成接口:
通配多个类:
3. 下界
接收 A 类或者 A 类的父类:
泛型通配符 < ? extends T > 来接收返回的数据,此写法的泛型集合不能使用 add 方 法, 而 < ? super T > 不能使用 get 方法,做为接口调用赋值时易出错。
当我们使用 extends 时,我们可以读元素,因为元素都是 T 类或子类,可以放心的用 T 类拿出。当使用 super 时,可以添加元素,因为都是 T 类或父类,那么就可以安全的插入 T 类。