为什么需要泛型
使用泛型机制编写的代码要比那些杂乱的使用Object
变量,然后再进行强制类型转换的代码具有更好的安全性和可读性,也就是说使用泛型机制编写的代码可以被很多不同类型的对象所重用。
我们先来看看泛型程序设计的机制是如果演变的,这样可以让我们对泛型的作用有一个更深的认识。
泛型的演变
实际上在Java增加泛型机制之前就已经有一个ArrayList
类,这个ArrayList
类的泛型概念是使用继承来实现的。
public class ArrayList {
private Object[] elementData;
public Object get(int i) {....}
public void add(Object o) {....}
}
这个类存在两个问题:
当获取一个值的时候必须进行强制类型转换
ArrayList files = new ArrayList();
String filename = (String)files.get(0);
没有错误检查,可以向数组中添加任何类的对象
files.add(new File(""));
对于这个调用,编译和运行都不会出错,但是当我们在其他地方使用get方法获取刚刚存入的这个File
对象强转为String
类型的时候就会产生一个错误。
泛型对于这种问题的解决方案是提供一个类型参数。
ArrayList<String> files = new ArrayList<String>();
//在JavaSE 7及以后的版本中,构造函数可以省略泛型类型:
ArrayList<String> files = new ArrayList<>();
这样可以使代码具有更好的可读性,我们一看就知道这个数据列表中包含的是String
对象。
编译器也可以很好地利用这个信息,当我们调用get
的时候,不需要再使用强制类型转换,编译器就知道返回值类型为String
,而不是Object
:
String filename = files.get(0);
编译器还知道ArrayList<String>
中add
方法中有一个类型为String
的参数。这将比使用Object
类型的参数安全一些,现在编译器可以检查,避免插入错误类型的对象:
files.add(new File(""));
这样的代码是无法通过编译的,出现编译错误比类在运行时出现类的强制类型转换异常要好得多。
泛型类
一个泛型类就是具有一个或多个类型变量的类,对于这个类来说,我们只关注泛型,而不会为数据存储的细节烦恼。
public class Pair<T> {
private T first;
private T second;
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
Pair
类引入了一个类型变量T,用尖括号括起来,并放在类名的后面。泛型类可以有多个类型变量:
public class Pair<T, U> {...}
类定义中的类型变量是指定方法的返回类型以及域和局部变量的类型
//域
private T first;
//返回类型
public T getFirst() { return first; }
//局部变量
public void setFirst(T newValue) { first = newValue; }
类型变量使用大写形式,且比较短,这是很常见的,在Java库中,使用变量E表示集合的元素类型,K
和V
分别表示表的关键字与值得类型。T
表示“任意类型”。
使用具体的类型代替类型变量就可以实例化泛型类型:
Pair<String>
可以将结果想象成带有构造器的普通类:
Pair<String>()
Pair<String>(String, String)
和方法
String getFirst();
String getSecond();
void setFirst(String);
void setSecond(String);
泛型类可以看成是普通类的工厂
泛型方法
首先我们来看一个泛型方法的实例:
class ArrayAlg {
public static <T> T getMiddle(T...a){
return a[a.length / 2];
}
}
首先,这个方法是在普通类中定义的,而不是在泛型类中定义的。然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量是放在修饰符的后面,返回类型的前面。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String middle = ArrayAlg.<String>getMiddle("a","b","c");
在这种情况下,方法调用中可以省略<String>
类型参数,编译器会使用类型推断来推断出所调用的方法,也就是说可以这么写:
String middle = ArrayAlg.getMiddle("a","b","c");
公众号
扫码或微信搜索 Vi的技术博客,关注公众号,不定期送书活动各种福利~