1.概念
泛型就是将原来的类型参数化,将具体的某个类型变成一个参数类型。然后在调用时传入具体的类型。
在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2.为什么要使用泛型
下面看一个例子
public void test1(){
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add("456");
arrayList.add(789);
arrayList.add(3.14);
for(Object o : arrayList){
int ans = (Integer) o;
System.out.println(ans);
}
> 123
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
出现ClassCastException,强转时出现转换异常,因为String无法强转为Integer类型
然后我们加入泛型
可以看到我们加入泛型后如果加入不是Integer类型的数据时在编译的时候就无法通过了,在编译时就进行类型检查,保证数据类型一致
public void test1(){
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(123);
arrayList.add(789);
for(Integer s : arrayList){
int ans = s;
System.out.println(ans);
}
}
这样我们还不出现强转,同时编译器看到泛型类型 ArrayList<Integer> 就可以自动推断出后面的 ArrayList<T> 的泛型类型必须是 ArrayList<Integer>,因此可以省略后面的Integer
3.使用泛型
3.1 泛型类
泛型类型用于类的定义中,被称为泛型类。在List、Set、Map 中很常见
public interface List<E> extends Collection<E> {
...
...
Iterator<E> iterator();
...
E get(int index);
...
}
实例化时申明的E可以出现在该类的任何方法,变量上
这里的泛型标识E可以为任何字符,常见的如T、E、K、V等形式的参数常用于表示泛型,常见的含义
T | Type |
K | Key |
E | Element |
V | Value |
3.2 泛型方法
定义泛型方法,其格式是:
修饰符 <类型参数列表> 返回类型 方法名(形参列表) {
}
public class Generic<T> {
private T key;
public T method(T args){ //普通方法,参数类型和返回值为T类型
this.key = args;
return key;
}
public <W> W Genericmethod1(List<W> data){ //泛型方法,方法内使用的类型参数都为 W
return data.get(0);
}
}
泛型方法的定义和普通方法定义不同的地方在于需要在修饰符和返回类型之间加一个泛型类型参数的声明,表明在这个方法作用域中谁才是泛型类型参数
如果泛型方法的类型参数都为 T,即和普通变量的作用域一样,内部覆盖外部,外部的同名变量是不可见的。一般都不写同名的类型参数
3.2.1 静态方法
如果静态方法要使用泛型,必须把静态方法改为泛型方法
public static <W> W Genericmethod1(List<W> data){
return data.get(0);
}
3.3 泛型接口
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE();
}
定义类时指定泛型类型
class genericInterface implements MyGenericInterface<String> {
@Override
public void add(String s) {
}
@Override
public String getE() {
return null;
}
}
定义类时没有指定泛型类型
class genericInterface<T> implements MyGenericInterface<T> {
@Override
public void add(T s) {
}
@Override
public T getE() {
return null;
}
}
在声明类的时候,需将泛型的声明也一起加到类中
4.泛型通配符
public Number method1(ArrayList<Number> data){
return null;
}
@Test
public void test1(){
ArrayList<Integer> integers = new ArrayList<Integer>();
ArrayList<Number> integers1 = new ArrayList<Number>();
method1(integers);
}
Error:(22, 24) java: 不兼容的类型: java.util.ArrayList<java.lang.Integer>无法转换为java.util.ArrayList<java.lang.Number>
可以看出虽然 Integer 是 Number 的子类,但是 ArrayList<Integer> 并不是 ArrayList<Number> 的子类,因此无法作为参数传入
这样我们就需要一个ArrayList<Integer> ArrayList<Number> 的共同父类,<?> 通配符就起到这样的作用
4.1 不变,逆变,协变
逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;**
f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;**
f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系**
由此,可以对上诉代码进行解释.
数组是协变的,导致数组能够继承子元素的类型关系 : Number[] arr = new Integer[2]; -> OK
泛型是不变的,即使它的类型参数存在继承关系,但是整个泛型之间没有继承关系 : ArrayList list = new ArrayList(); -> Error
4.2 ? 通配符
在这里,可以把 ? 看成是所有类型的父类,表明他可以接受任意类型
public void method1(ArrayList<?> data){
data.add(3);//编译不通过
data.get(0);
data.add(null);
}
@Test
public void test1(){
ArrayList<Integer> integers = new ArrayList<Integer>();
ArrayList<Number> integers1 = new ArrayList<Number>();
integers.add(3);
method1(integers);
}
对于?通配符修饰的对象不能进行添加操作,只能读取,不过可以添加null
4.3 ? extends T 上边界通配符
? 是T的任意子类型
public void method1(ArrayList<? extends Number> data){
}
@Test
public void test1(){
ArrayList<Integer> integers = new ArrayList<Integer>();
ArrayList<Number> integers1 = new ArrayList<Number>();
integers.add(3);
method1(integers);
}
Integer 是 Number的子类,因此在范围里,符合条件
4.4 ? super T 下边界通配符
? 是T的任意父类型
public void method1(ArrayList<? super Integer> data){
}
@Test
public void test1(){
ArrayList<Integer> integers = new ArrayList<Integer>();
ArrayList<Number> integers1 = new ArrayList<Number>();
integers.add(3);
method1(integers1);
}
Number 是 Integer的父类,因此在范围里,符合条件
注意:在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界
public <K extends Number> void Genericmethod1(ArrayList<K> data){
}
@Test
public void test1(){
ArrayList<Integer> integers = new ArrayList<Integer>();
ArrayList<Number> integers1 = new ArrayList<Number>();
integers.add(3);
Genericmethod1(integers1);
}