泛型的基本使用
泛型介绍
泛型(Generic),即“参数化类型”( parameterized type)。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类和泛型接口
接口和类差不多,所以这里偷个懒,只介绍泛型类
定义泛型类
直接在类名后加上一对尖括号,尖括号内的标识符即使泛型,在泛型类中可以把泛型当作正常的类型来使用,如果有多个泛型,则写在同一对尖括号里,泛型间用逗号隔开
public class Person<T> {
T name;
Person() {
}
Person(T name) {
this.name = name;
}
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
}
使用泛型类
在实例化时,类型后面加上尖括号,给泛型真实的类型,也就是说只有在实例化时,泛型的类型才真正的确定了,例如
Person<String> person = new Person<String>();
在java1.7后,最后面的String
可以省略不写
注意:如果在实例化时不指定泛型的类型,那么泛型会自动变成Object
,使用不太方便,所以推荐指定
也可以正常的调用泛型类中带泛型的方法:
person.setName("张三");
System.out.println(person.getName());
此时setName
里参数的类型就是String
,getName
的返回值也是String
泛型类有关继承的问题
主要可以分为两种形式:
-
子类不是泛型类:
此时需要在继承时给父类指定泛型,子类就不需要定义泛型了
public class Child extends Person<String> {}
-
子类也是泛型类
此时需要给子类定义泛型,然后再把泛型指定给父类
public class Child<E> extends Person<E> {}
如果两个泛型类是父子关系,且两者的指定的泛型是一样的,则其对象是可以相互赋值的,最常见的就是
List<Integer> list = new ArrayList<>();
注意事项
-
构造方法不需要写定义泛型
-
一般情况下(存在特殊情况),两个泛型对象,如果泛型类型不同(包括父子关系),就不能相互赋值,下面是错误示范
Person<Integer> person1 = null; Person<String> person2 = null; person1 = person2;// 语法错误
但是在运行时只有一个Person类被加载到JVM中
-
当泛型类作为一个方法的参数/返回值的类型时,就算其指定的泛型不同,也算是同一类型,不会构成重载,错误示范如下
// 会报错,因为两个方法头重复 public void fun(List<String> list) {} public void fun(List<Integer> list) {}
-
如果泛型不指定,泛型就会作为
Object
来处理,但不等价于Object
-
泛型不能用基本数据类型指定,可以使用包装类来指定
-
泛型不能再静态方法/属性中使用,因为泛型类只有在实例化时,泛型的类型才确定
-
异常类不能是泛型类,也泛型也不能指定为异常类
-
泛型数组不能直接初始化为
new T[10]
,可以这样写T[] arr = (T[]) new Object[10];
泛型方法
注意:泛型方法不是在泛型类里使用了泛型类定义的泛型的方法,他跟泛型类没有任何关系,你可以在普通的类里定义泛型方法
定义泛型方法
在返回值前面加上一对尖括号定义泛型,就可以在该泛型方法中当作正常的泛型来使用了
public <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
使用泛型方法
如果泛型作为参数的类型,则会在调用时自动指定类型,例如
Integer[] arr = {1, 2, 3, 4};
copyFromArrayToList(arr);
此时E
就是Integer
注意事项
-
泛型方法可以声明为
static
-
如果泛型不作为参数的类型,也就不能为其指定类型,所以泛型会作为
Object
类型 -
泛型方法中泛型不能直接初始化为
new E()
,可以E e = (E) new Object();
-
同样不能使用基本数据类型指定泛型
通配符?
当我们把泛型类作为参数/返回值的类型时,其泛型还未指定,此时如果我们给他指定一个具体的类型,则很有可能要写许多个方法来使用泛型的类型的变化,如果不给他指定,写起来不规范,用起来也不方便,这时就需要使用通配符了,例如
public void fun(List<?> list){}
这样只要实参时List
类型,无论其泛型是什么,都可以调用该方法
通配符?
,其可以匹配任何类型
注意事项
对于List<?>
的对象,不能向其中添加数据(除了null),可以读取数据,类型是Object
也就是说,对于使用通配符?的泛型类对象,你不能再使用其参数类型是泛型的方法,因为你根本无法确定其类型,也就不知道传什么类型才行(除了null)
限制条件
限制条件常和通配符配合使用,也可以在定义泛型时使用,主要有两种
? extends A
:继承于A类或者A类,即<=A? super A
:A类的父类或者A类,即>=A
注意事项
直接上代码,其中Father
是Child
的父类
List<? extends Father> list1 = new ArrayList<>();
List<? super Father> list2 = new ArrayList<>();
// 获取的可以是Father类型,因为list1中存放的对象类型都是Father的子类或者Father,而子类对象是可以赋值给父类对象的
Father f = list1.get(0);
// 报错,因为list1中可以存储Child的子类,而父类不能赋值给子类
list1.add(new Child());
// 报错,因为list2中可以存放Father的父类,而父类不能赋值给子类
Father f = list2.get(0);
// 因为list2中存放的对象类型都是Father的父类或者Father,而子类可以赋值给父类
list2.add(new Child());