1、什么是泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
2、一个小例子
import java.util.ArrayList;
import java.util.List;
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("hallo");
list.add(1);
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println(str);
}
}
}
上面的程序:
编译阶段正常,而运行时会出现异常
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
存在的问题:
当把一个对象收入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,对象变成了Object类型,但其运行时类型任然为其本身类型。强制转换时,容易出现异常,正如程序中把Integer转为String失败。
解决方法:
通过使用泛型来使集合能够记住集合内元素各类型
将上面的代码用泛型来实现
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("hallo");
list.add(1);
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println(str);
}
}
}
这时,编译错误,无法运行
Error:(13, 13) java: 对于add(int), 找不到合适的方法
方法 java.util.Collection.add(java.lang.String)不适用
(参数不匹配; int无法转换为java.lang.String)
方法 java.util.List.add(java.lang.String)不适用
(参数不匹配; int无法转换为java.lang.String)
通过List,直接限定了list集合中只能含有String类型的元素,从而在取得集合中的元素时无须进行强制类型转换,编译器可以确定集合中存的都是String类型。
3、泛型的特性
Java中的泛型,只在编译阶段有效。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。
泛型信息不会进入到运行时阶段。
4、泛型的使用
在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
以几个小例子来分析一下
(1) 泛型类
public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
用泛型类声明与建立对象
ArrayList<String> list = new ArrayList<String>();
JDK7之后可以写成:
ArrayList<String> list = new ArrayList<>();
(2) 泛型接口
...
public interface Comparator<T>{
int compare(T o1, T o2);
...
}
使用方法
public class StringComparator implements Comparator<String>{
@Override
public int compare(String s1, String s2){
return -s1.compareTo(s2);
}
}
(3) 泛型方法
public static <T> T elemof(T[] objs,int index){
return objs[index];
}
使用方法
public static String elemof(String[] objs,int index){
return objs[index];
}
5、泛型的上下边界问题
可以使用extends与super关键字、?类型通配符来限制泛型,并且可以结合这三者来模拟共变性与逆变性。
共变性与逆变性
如果B是A的子类,而Node< B >可视为一种Node< A >,则称Node具有共变性(Covariance)或者有弹性的(Flexible)。
如果B是A的子类,而Node< A >可视为一种Node< B >,则称Node具有逆变性(Contravariance)。
Java泛型不支持共变性与逆变性
(1) 使用?与extends来模拟共变性
class Fruit{}
class Apple extends Fruit{}
class RedApple extends Apple{}
List< ? extends Fruit > list1 = new ArrayList<Fruit>();
List< ? extends Fruit > list2 = new ArrayList<Apple>();
List< ? extends Fruit > list3 = new ArrayList<RedApple>();
Fruit为上界,即List< ? extends Fruit >类型的对象可以引用持有Fruit及其子类的容器。
关于一个add方法:
void add(List< ? extends Fruit > list, Apple a){
list.add(a);
}
如果list引用的是持有RedApple类型的容器,那么将Apple类型向下转型是不安全的,所以用List< ? extends Fruit >,add方法是受到限制的,即无法向容器中添加任何实际的类型除了null。
(2) 使用?与super来模拟逆变性
List< ? super RedApple > list4 = new ArrayList<RedApple>();
List< ? super RedApple > list5 = new ArrayList<Apple>();
RedApple为下界,即List< ? super RedApple >类型的对象可以引用持有RedApple及其父类的容器。
此时add方法是没有限制的,但是也只能是添加Jonathan及其父类型。若事先不知道List< ? super RedApple >引用的对象所持有的类型,则可以做到向上转型,而向上转型是安全的。
6、小结
泛型的本质是:参数化类型
只在编译阶段有效,泛型使用无错误时,擦出泛型的相关信息,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法,泛型信息不会进入到运行时阶段。
泛型可以被用在类、接口和方法中,而且可以限制泛型的上下界。
虽然Java泛型不支持共变性与逆变性,但是?与extends可模拟共变性;?与super可模拟逆变性。
参考资料
《Java学习笔记》 林信良