1. 泛型程序设计
泛型与面向对象一样,也是Java程序设计中一个重要的思想,它可以被用在类、接口和方法中。泛型程序设计究竟指什么呢?简单的来说就是:
所编写的代码可以被多种不同类型的对象所重用。
相较于杂乱的使用Object变量,泛型机制编写的程序具有更好的安全性和可读性,在效率上也会有所提升。
Java在JDK 1.5 之后增加了泛型机制,我们所熟知的 ArrayList 类就是泛型程序设计的一个典型例子。
1.1 使用泛型类的例子:ArrayList
ArrayList 类中有一个类型参数,用于指示其所存储元素的类型,此参数类型被放在类名后的尖括号“<>”中,如:
ArrayList<String> stringList = new ArrayList<String>();
上述代码中的 String
就是类型参数,它指明了 ArrayList 中存储的数据类型为 String
类型。也就是说,对于 stringList 而言,我们只能往里面添加 String
类型的对象。
String str = "";
Integer a = 0;
stringList.add(str); // ok
stringList.add(a); // error
如上述例子所示,当我们向 stringList 添加其它类型的对象时,就会产生错误。
需要注意的是,类型参数不能是八大基本类型,如果的确需要用到它们,可以用其包装类型替代(如用 Integer 代替int)。
JDK 1.7 之后,构造函数中的类型参数可以被省略,省略的类型可以从变量的类型推断得出:
ArrayList<String> stringList = new ArrayList<>();
1.2 类型变量
在构建泛型类、泛型接口和泛型方法时,可以通过类型变量来“泛型化”数据。例如:
public class Pair<T> {
// ...
}
Pair<T>
中的 T 就是类型变量,被放在泛型类类名后面的尖括号里。
关于类型变量,在Java库中,常使用E、K、V、T等大写字母表示:
E: 表示集合的元素类型
K:表示关键字
V:表示值(常与K对应)
T:表示“任意类型”(需要时还可以用邻近的字母U和S)
类型变量可以定义数据域的类型、方法的参数类型以及返回值的类型。
2. 泛型类
利用类型变量,可以构建自己的泛型类。
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 定义了数据域的类型、方法的参数类型以及返回值的类型。在使用到Pair类时,只需要在实例化对象时用具体的类型参数替换类型变量即可。
泛型的实现利用了Java虚拟机的类型擦除机制。为便于理解,可以将类型变量T理解成“某种变量”,在实例化泛型类时需要具体指明其类型,并用指定的类型“替换”它,以得到一个“具体的”对象。
3. 泛型接口
同样的,我们可以构建自己的泛型接口。这里需要注意的是, 接口中的数据域都是public static final
(静态常量),因此不能用类型变量定义。
这并不难理解,对于静态常量而言,它的值在类或接口建立的时候就是确定的,既然如此,它肯定不能是个“类型不确定”的变量(类型都不能确定,它的值还怎么定下来呢?)。
public interface Person<T> {
T information; // error
T getInformation(); // ok
}
对于泛型接口的实现,可以在实现时就指定类型变量的具体类型,这样实现的类就是一个具体的类,而不是泛型类,如:
public class MyPerson implements Person<String> {
String information;
@Override
public String getInformation() {
return information;
}
}
当然也可以在实现此接口时不具体指定类型变量的具体类型,这样得到的就是一个泛型类,如:
public class MyPerson<T> implements Person<T> {
T information;
@Override
public T getInformation() {
return information;
}
}
4. 泛型方法
除了泛型类和泛型接口,实际上,还可以在普通类中定义泛型方法(带有类型参数的方法),例如:
public class MyPerson {
public static <T> T getFirstInformation(T[] information) {
if (information.length >= 2) {
return information[1];
}
return null;
}
}
从尖括号和类型变量可以看出,这是一个泛型方法。当调用泛型方法时,需要在方法名之前的尖括号中填入具体的类型,如:
String[] information = {"111111", "JuJunjian", "boy"};
String name = MyPerson.<String>getFirstInformation(information);
System.out.println(name);
// 程序打印:JuJunjian
需要注意的是,在定义时尖括号被放在了修饰符之后,返回类型之前;在调用时尖括号被放在了方法名之前。这样定义的好处是可以避免语法分析的歧义。
试想如果尖括号被放在方法名之后,如方法: f<T, U>();
在调用方法时,对于g(f<a,b>(c)),是理解成函数f<a,b>(c)的返回值,还是两个布尔变量f<a和b>(c)呢?