泛型
泛型的定义1
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在**实例化时作为参数指明这些类型**。
举个例子:在Java集合类中使用ArrayList
声明的时候,如果内部的元素是字符串类型,那么在定义的时候就是用ArrayList<String> arrayList = new ArrayList<>();
这就是泛型的一个典型的也是很重要的一个使用。这样声明之后,编译器在编译代码的时候就会按照泛型中定义的数据类型进行类型检查,相当于明确了插入数据的类型,更安全,可读性更高——在一个作用域内可以使用泛型参数代替数据类型。
泛型的应用
在集合类中的使用
集合类对于泛型的支持就很好,在jdk1.5之后集合框架中的全部类和接口都增加了堆泛型的支持,之前分析的源码中也有体现,上面的例子就是一个应用。
自定义泛型结构
自定义泛型类
public class GenericTest {
public static void main(String[] args) {
Human<Integer> human = new Human<>("PanicJaw",78);
Human<String> human1 = new Human<>("PanicJaw",78);//这里会报错,因为数据类型不符
}
}
class Human<T>{
String name;
T sth;
public Human(String name, T sth) {
this.name = name;
this.sth = sth;
}
}
注意几点:
-
泛型参数只能是类,基本数据类型的话使用包装类代替
-
泛型类的参数可以有多个,在
<T1,T2,T3...>
用逗号隔开即可 -
注意到泛型类的构造方法,类名后面是不加
<T>
的 -
泛型类中出现了泛型参数的地方的类型要一致,上面的报错就不满足类型要求造成的
-
泛型类的泛型参数在实例化对象的时候也可以不指定,这样编译检查的时候就是按照
Object0
类来检查数据类型了 -
静态方法中不能使用类的泛型,因为类的泛型在对象实例化时确定,静态方法是在类加载时就被加载了,这时泛型参数还未被指定。
-
父类是泛型类,子类必然是泛型类,同时也可以新增自己的泛型参数;
- 但是如果父类是泛型类,但是子类在继承的时候,泛型父类已经指明了泛型参数类型,那么这个子类就不再是泛型类了,就是一个普通类。
- 如果父类是泛型类,但是子类在继承的时候父类没有指定泛型参数,那么默认就擦除了,如果此时子类也没有定义泛型参数,那么子类也就是普通子类了
-
可以定义泛型上界,比如:
class Human<T extends Person>{...}
自定义泛型方法
注意:上面泛型类中如果有方法使用泛型参数
<T>
,这个方法也并不是泛型方法;同时,有无泛型方法和这个类是否是泛型类没有关系
//定义的时候在修饰符和返回类型之间声明泛型
public <T> T minArray(T[] array,int n){...}
泛型方法在具体被调用的时候传入参数类型
下面一些容易混淆的例子
public boolean add(E e){...}//这个ArrayList中的add方法就不是泛型方法。这个泛型参数E是泛型类声明的
public <T> T[] toArray(T[] a) {...}//是一个泛型方法,举例如下
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);arrayList.add(3);arrayList.add(5);
Integer[] a = new Integer[arrayList.size()];
arrayList.toArray(a);//这里隐式的自动推断,通过传进来实参推断<T>
-
同时,泛型参数也有作用域的问题,如果一个泛型类和类中的方法使用同一个泛型参数
T
那么在方法体内,方法的T
会覆盖类中的T
;所以一般定义方法的时候不要和类的泛型参数名字一样2 -
静态方法中可以使用泛型,在调用方法的时候传输参数即可。
-
可以定义上界:
public static <T extends Comparable<T>> T max(T[] arr){...}
泛型数组
在泛型类中定义数组时:
T[] t;//可以用来声明数组
T[] t = new T[5];//不能同来实例化数组
//正确用法
T[] t = (T[])new Object[5];
泛型的继承通配符
注意:泛型参数之间不存在继承关系:例如
ArrayList<List<Integer>> a1 = new ArrayLst<>();
ArrayList<ArrayList<Integer>> a2 = new ArrayList<>();
a1 = a2;//报错,不能够赋值,因为泛型参数内的List和ArrayList不是常规意义的父类子类关系
List<List<Integer>> a_1 = new ArrayLst<>();
ArrayList<List<Integer>> a_2 = new ArrayList<>();
a_1 = a_2;//这样是正确的
可以理解为“类型控制”,如果上述的赋值能够进行,那么赋值之后对a1
赋值时,类型检查中可能会添加进去List
子实现类而不是ArrayList
的情况,数据类型变了。
通配符
针对在泛型参数中多态消失的问题——这样会导致多态性方法调用的失效,不同泛型的方法都得重写。所以使用通配符?
——类A
是类B
的父类,G<A>
和G<B>
是没关系的,二者共同的父类是:G<?>
。
public void show(List<?> a1){...}
//调用方法的时候:
.show(a1);show(a2);//都正确
通配符一般只在函数参数部分使用,可以定义上下限:
public ArrayList(Collection<? extends E> c){...}//参数只能是E或者E的子类,类中非泛型方法的使用
通配符是只读的,对象不可修改,因为表示的只是一个范围,很多指定的数据类型都是?
的子类,都可以添加的话,就没有了类型控制的意义了。
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Double>();
list.add(3);//编译不通过
类型擦除
Java编译器将泛型转换为非泛型的普通代码,也就是说不论是List<String>
还是List<Integer>
在实际执行的时候都是List
数据类型,即将泛型类型擦除了。所以说泛型就是一个规范要求,在编译阶段检查用的,实际执行过程中是不体现的。