一、泛型的概念
通过一个例子说明什么是泛型:
观察Java标准库提供的ArrayList类,假设我们自己来实现这个类。如果用ArrayList存储String类型,那么会安排以下属性与方法:
public class MyArrayList{
private String[] array;
private int size;
public void add(String a){...}
public void remove(int index){...}
public String get(int index){...}
}
这样存入的必须是String变量,取出的页一定是String变量,而如果想用ArrayList存储Integer类型,那么就得修改属性中的String[] array为Integer[] array,各个方法里的具体代码也得做相应修改,非常麻烦。而对于每一种集合类,我们都需要许多子类型,为了解决这种麻烦,我们将代码修改为如下这种模板:
public class MyArrayList<T>{
private T[] array;
private int size;
public void add(T a){...}
public void remove(int index){...}
public T get(int index){...}
}
其中T可以是任何类型,从而达到了一个模板满足多种子类型的目的,这就是泛型。
二、向上转型的问题
就像可以使用一个类的父类的引用类型来引用这个类,实现了泛型的类也可以用其父类的引用类型来引用这个类,例如可以用List<String>来引用ArrayList<String>。
但是不能用List<Number>来引用ArrayList<Integer>。因为假设把ArrayList<Integer>转型为ArrayLIst<Number>后,就可以接受Float类型,而ArrayList<Integer>不能接受Float类型,故会产生报错。因此,编译器不允许把ArrayList<Integer>转型为ArrayLIst<Number>。
另外,一个类可以继承一个泛型类。例如:public class IntPair extends Pair<Integer>{...}
三、不经定义的泛型
使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object。例如:
//编译器警告:
List list=new ArrayList();
list.add("Hello");
list.add("World");
String first=(String) list.get(0);
String second=(String) list.get(1);
此时只能把<T>当作Object使用,没有发挥泛型的优势。而且,必须对输出数据进行类型强制转换。
对于如下所示的定义:List<Number> list=new ArrayList<>();则没有问题,因为编译器能够自动推出泛型类型,就可以省略后面的泛型类型,编译器看到前面的<Number>就可以推出后面的<T>也b必定是<Number>。
四、静态方法中的泛型
编写泛型类时,要特别注意,泛型类<T>不能用于静态方法。例如
public class Pair<T>{
private T first;
private T last;
//对静态方法使用<T>:
public static Pair<T> create(T first,T last){...}
这样的代码会导致编译错误。稍作修改可以编译通过:
//可以编译通过:
public static <T> Pair<T> create(T first,T last){...}
但实际上,这个<T>和Pair<T>类型的<T>已经没有任何关系了,而是另一类独立的泛型,我们应该对它做区分处理:
//可以编译通过:
public static <K> Pair<K> create(K first,K last){...}
五、与Lab2的联系
在Lab2中,我们通过构造泛型Graph<L>,使得Graph类不仅能接受String类型,还能接受其他各种类型。因此我们不仅可以用Graph类来实现“诗意的散步”Poet类(其节点为String类型),还能用Graph类来实现“社交网络"FriendshipGraph类(其节点为Person类型)。
由此可以看到对于这种应用很广的通用模型类,使用泛型可以大大提升其可移植性与可复用性,是极好的!