1.什么是泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
参数化类型:在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法
2.为什么使用泛型?
从下面的例子引入:
//解释
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("amber");
arrayList.add(99);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("item = " + item);
}
}
//报错:class java.lang.Integer cannot be cast to class java.lang.String
程序崩溃了,而崩溃的原因就是ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以 String 的方式使用,而Integer对象不能强转为String类型。
如何避免上述异常的出现?即我们希望当我们向集合中添加了不符合类型要求的对象时,编译器能直接给我们报错,而不是在程序运行后才产生异常。这个时候便可以使用泛型了。
//解释
public static void main(String[] args) {
List<String> arrayList = new ArrayList();//使用泛型
arrayList.add("amber");
arrayList.add(99);// 在编译阶段,编译器会报错
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("item = " + item);
}
}
3.泛型类
类型参数用于类的定义中,则该类被称为泛型类。
通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map 等
泛型类的基本语法:
class 类名称 <泛型标识> {
private 泛型标识 /*(成员变量类型)*/ 变量名;
.....
}
}
- 尖括号 <> 中的 泛型标识被称作是类型参数,用于指代任何数据类型。
- 泛型标识是任意设置的,Java 常见的泛型标识以及其代表含义如下:
T :代表一般的任何类。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
S :代表 Subtype 的意思,文章后面部分会讲解示意。
实例如下:
public class generic<T> {
// key 这个成员变量的数据类型为 T, T 的类型由外部传入
private T key;
// 泛型构造方法形参 key 的类型也为 T,T 的类型由外部传入
public generic(T key) {
this.key = key;
}
// 泛型方法 test 的返回值类型为 T,T 的类型由外部指定
public T test(){
return key;
}
}
在泛型类中,类型参数定义的位置有三处,分别为:
- 非静态的成员属性类型
- 非静态方法的形参类型(包括非静态成员方法和构造器)
- 非静态的成员方法的返回值类型
注意:泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数
泛型类不只接受一个类型参数,它还可以接受多个类型参数
public class MultiType <E,T> {
E value1;
T value2;
public E getValue1(){
return value1;
}
public T getValue2(){
return value2;
}
}
泛型类的使用
在创建泛型类的对象时,必须指定类型参数 T 的具体数据类型,即尖括号 <> 中传入的什么数据类型,T 便会被替换成对应的类型。如果 <> 中什么都不传入,则默认是 < Object >。
假设有个泛型类如下:
public class generic<T> {
private T value;
public generic(T value) {
this.value = value;
}
public T getValue(){
return value;
}
}
当创建一个generic< T >类对象时,会向尖括号<>中传入具体的数据类型
@Test
public void test() {
generic<String> generic = new generic<>();// 传入 String 类型
// <> 中什么都不传入,等价于 Generic<Object> generic = new Generic<>();
generic generic = new generic();
}
-
泛型类中的类型参数 T 被 <> 中的 String 类型全部替换了。
-
使用泛型的上述特性便可以在集合中限制添加对象的数据类型,若集合中添加的对象与指定的泛型数据类型不一致,则编译器会直接报错,这也是泛型的类型安全检测机制的实现原理。
4.泛型接口
和泛型类的定义差不多,基本语法如下:
public interface 接口名<类型参数> {
...
}
注意:泛型接口中的类型参数,在该接口被继承或者被实现时确定
1. 在泛型接口中,静态成员也不能使用泛型接口定义的类型参数
interface IUsb<U, R> {
int n = 10;
U name;// 报错!接口中的属性默认是静态的,因此不能使用类型参数声明
R get(U u);// 普通方法中,可以使用类型参数
void hi(R r);// 抽象方法中,可以使用类型参数
// 在jdk8 中,可以在接口中使用默认方法, 默认方法可以使用泛型接口的类型参数
default R method(U u) {
return null;
}
}
2. 定义一个接口IA继承了泛型接口IUsb,在接口IA定义时必须确定泛型接口 IUsb中的类型参数
// 在继承泛型接口时,必须确定泛型接口的类型参数
interface IA extends IUsb<String, Double> {
...
}
// 当去实现 IA 接口时,因为 IA 在继承 IUsb 接口时,指定了类型参数 U 为 String,R 为 Double
// 所以在实现 IUsb 接口的方法时,使用 String 替换 U,用 Double 替换 R
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double d) {
...
}
}
3. 定义一个类BB实现了泛型接口IUsb,在类BB定义时需要确定泛型接口IUsb中的类型参数
// 实现接口时,需要指定泛型接口的类型参数
// 给 U 指定 Integer, 给 R 指定了 Float
// 所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float afloat) {
...
}
}
4. 定义一个类CC实现了泛型接口IUsb时,若是没有确定泛型接口IUsb中的类型参数,则默认为Object
// 实现泛型接口时没有确定类型参数,则默认为 Object
// 建议直接写成 IUsb<Object, Object>
class CC implements IUsb {//等价 class CC implements IUsb<Object, Object>
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
...
}
}
5. 定义一个类DD实现了泛型接口IUsb时,若是没有确定泛型接口IUsb中的类型参数,也可以将DD类也定义为泛型类,其声明的类型参数必须要和接口IUsb中的类型参数相同
// DD 类定义为 泛型类,则不需要确定 接口的类型参数
// 但 DD 类定义的类型参数要和接口中类型参数的一致
class DD<U, R> implements IUsb<U, R> {
...
}