泛型指参数化类型的能力,可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它。
在泛型产生之前,像集合的存取都是靠强制类型转换:
public class ArrayList {
public Object get(int i) {...}
public void add(Object o) {...}
...
}
String fileName = (String) names.get(0);
这样没有进行错误检查,在运行时可能发生类型转换错误,编程时必须小心。
泛型提供了类型参数,放在一对尖括号中。像<T>这样被称为 形式泛型类型(formal generic type),随后可以用一个实际具体类型(actual concrete type)来替换,这个过程叫泛型实例化(generic instantiation)。
ArrayList<String> names = new ArrayList<String>();
类型参数String说明了List中只存放String对象,当调用get的时,不需要手动进行类型转换,编译器会帮我们完成这个过程,并返回String。当调用add时,编译器会进行检查,传入非String类型的参数,编译不能通过。
泛型的加入,使得能够在编译时而不是在运行时检测出类型错误。提高代码的可靠性和可读性。
注意, 泛型必须是引用类型,不能是基本类型,int, double这种需要用Integer,Double等替换。
泛型类: 定义一个类为泛型类型,需要将类型参数放在类名之后。
public class Pair<T> {
private T first;
private T second;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
类型参数命名规则:E-集合,K-Key,V-Value,T-任意类型。
泛型方法:泛型方法既可以定义在普通类中,也可以定义在泛型类中。
定义一个方法为泛型类型,需要将类型参数放在方法返回类型之前:
public static <T> T getMiddle(T[] a) {
return a[a.length / 2];
}
调用这个方法:
public class Test {
public static void main(String[] args) {
String names[] = {"A", "B", "C"};
String middle = Test.<String>getMiddle(names);
}
...
类型参数也可以省略:
public class Test {
public static void main(String[] args) {
String names[] = {"A", "B", "C"};
String middle = getMiddle(names);
}
...
<T extends Bounding Type> T和绑定类型可以是类,也可以是接口。
一个类型变量或者通配符可以有多个限定:
T extends Comparable & Serializable
&分隔限定类型,逗号分隔类型变量:
public class Interval<T extends Comparable & Serializable , M extends Comparable> {
类型擦除:
虚拟机中没有泛型,只有普通的类和方法。虽然在代码中制定了泛型参数类型,但是执行的时候,虚拟机会执行类型擦除。
每个泛型类型都有其对应的原始类型,原始类型的名字就是删去类型参数后的泛型类型的名称。对于类/方法的类型变量,如果它有限定类型,则替换为限定类型,否则替换为Object。
类的类型擦除:Pair<T>的原始类型:
public class Pair {
private Object first;
private Object second;
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
public Object getSecond() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
}
因为T没有限定类型,所以用Object替换。
另一个例子:
public class Interval<T extends Comparable & Serializable> {
private T lower;
private T upper;
public Interval(T first, T second) {
if (first.compareTo(second) < 0) {
lower = first;
upper = second;
} else {
lower = second;
upper = first;
}
}
}
类型擦除后:
public class Interval {
private Comparable lower;
private Comparable upper;
public Interval(Comparable first, Comparable second) {
if (first.compareTo(second) < 0) {
lower = first;
upper = second;
} else {
lower = second;
upper = first;
}
}
}
如果调换Comparable 和Serializable的顺序,原始类型会用Serializable替换T,在方法体内会执行Comparable 的强制类型转换,所以为了提高效率,一般应该将没有方法的接口放在最后。
方法的类型擦除:
public static <T extends Comparable> T min(T[] a) {
}
擦除后:
public static Comparable min(Comparable[] a) {
}
如果一个方法的返回类型是泛型变量,由于进行了类型擦除,在调用这个方法后,返回值会被进行类型强制转换:
Pair<Employee> p = ...
Employee e = p.getFirst();
擦除getFirst返回类型后将返回Object类型,虚拟机会进行强制类型转换,以上语句,会被翻译为2条虚拟机指令:
1.对原始方法getFrist的调用
2.将返回的Oject类型强制转换为Employee类型
通配泛型类型:
? extends T : 受限通配
现有方法 print输出Employee 的姓名:
public class Employee {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Employee" + getName();
}
}
public class Manager extends Employee {
}
public class Test {
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("joe");
Employee employee2 = new Employee();
employee2.setName("Tom");
Pair<Employee> p = new Pair<Employee>();
p.setFirst(employee);
p.setSecond(employee2);
print(p);
}
public static void print(Pair<Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName());
}
}
尽管Manager 是Employee的子类,但是不能这样调用:
Pair<Manager> p2 = new Pair<Manager>();
print(p2); //编译不过
这时需要使用通配符,
public static void print(Pair<? extends Employee> p) {
参数表示,任何泛型Pair类型,它的类型参数是Employee的子类。
Pair<Manager> p2 = new Pair<Manager>();
Manager manager = new Manager();
manager.setName("Tim");
Manager manager2 = new Manager();
manager2.setName("Hank");
p2.setFirst(manager);
p2.setSecond(manager2);
print(p2);
public static void print(Pair<? extends Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName());
}
输出: Tim and Hank
注意: Employee first = p.getFirst();可以正常调用,但是setFirst参数不能传入 Employee的实例,而只能传入Manager的实例。
因为类型参数是<? extends Employee>编译器只知道是 Employee的子类型,父类引用子类是合法的,所以可以将getFirst的返回值赋给一个Employee对象,而Employee子类不包含Employee本身,所以在set方法中不能传入Employee的实例。
? super T : 下限通配:
? super Manager 这个通配符限制为Manager及其所有超类型。
这个通配符的行为和上面相反,它可以使用Manager作为方法的参数,但是不能使用返回值:
Pair<Manager> p3 = new Pair<Manager>();
p3.setFirst(manager);
p3.setSecond(manager2);
print2(p3);
public static void print2(Pair<? super Manager> p) {
Manager first = (Manager) p.getFirst();
Manager second = (Manager) p.getSecond();
System.out.println(first.getName() + " and " + second.getName());
}
注意这里 p.getFirst()返回的是Object对象,因为 ? super Manager,编译器只知道是 Manager的超类,并不知道具体的类。这段代码只是为了说明问题,实际上是不合适的。
实际应用举例:根据提供的Manager数组参数,提供排序后的Pair对象。
public static void minMax (Manager[] a, Pair<? super Manager> result) {
if (a == null || a.length == 0) {
return;
}
Manager min = a[0];
Manager max = a[0];
for (int i = 0; i < a.length; i++) {
if (min.getName().compareTo(a[i].getName()) > 0) {
min = a[i];
}
if (max.getName().compareTo(a[i].getName()) < 0) {
max = a[i];
}
}
result.setFirst(min);
result.setSecond(max);
}
简单说就是,下限通配可以写入,受限通配可以读取。
?:无限定通配:
像Pair<?> 这样的?通配符叫做无限定通配,使用它不需要知道实际的类型:
public boolean hasNulls(Pair<?> p) {
return p.getFirst() == null || p.getSecond() == null;
}
无限定通配符也可以用具体类型变量替代:
public <T> boolean hasNulls2(Pair<T> p) {
return p.getFirst() == null || p.getSecond() == null;
}
但是前者的可读性更强。