1、泛型类
注意< T >的位置,T可以是任意的字母,常用的有K、V、T、S、U等,多个类型变量变量可以用逗号分割Pair < T, K, V >。
class Pair<T> {
private T min;
public Pair() {
this.min = null;
}
public Pair(T min) {
this.min = min;
}
public T getMin() {
return min;
}
public void setMin(T min) {
this.min = min;
}
}
2、泛型方法
下边是一个简单的泛型方法,注意类型变量(< T >)放在修饰符的后边(public、static),在返回值类型前边(T)。
public static <T> T getMiddle(T... ts) {
return ts[ts.length / 2];
}
对此方法的调用也有两种形式,一种指定类型变量,另一种编译器可以根据参数推断
ArrayAlg.<Integer>getMiddle(1, 2);
ArrayAlg.getMiddle(1, 2);
当没有指定类型变量,并且根据参数推断时发现不是同一种类型,会继续向上推断,直至找到共同的父类,这个父类就是推断出来的类型变量了,如下,第一行推断结果为Number,第二行为Object
Number n = ArrayAlg.getMiddle(1, 2, 2.2);
Object o = ArrayAlg.getMiddle(1, 2.2, "hello");
3、泛型变量限定
为什么要限定泛型变量呢?如果在方法中需要调用T类型的对象的一个方法,但是这个方法有的类有有的类没有,那么就限制这个类型变量必须是指定有这个方法的子类,包括继承和实现接口,如下将T类型变量限制为实现Comparable接口的类,这样就可以调T类型变量的compareTo()方法而不会报错了。
public static <T extends Comparable<T>> T min(T[] ts) {
if (ts == null || ts.length == 0)
return null;
T min = null;
for (T t : ts)
min = min.compareTo(t) > 0 ? t : min;
return min;
}
语法为< T extends BoundingType >,接口和继承都用extends,多限制可以用&分割< T extends BoundingType1 & BoundingType2 >
4、泛型代码和虚拟机
编译的时候类型参数会被擦除掉,替换为限定的类型,没有限定类型的话替换为Object,注意:限定类型是指< T extends BoundingType >中的BoundingType,而不是实例化时像List< String >中的String
如果有多个限定,会选择第一个限定作为擦除后的替换,例如在上边的例子中将类型限定改为< T extends Comparable & Serializable >,将会采用Comparable来替换T,但是如果写成< T extends Serializable & Comparable >,将会使用Serializable来替换T,但是之后的代码中会执行compareTo()方法,编译器就要做必要的类型转化了,所以为了提高效率,应将标签接口(没有方法的接口)放到边界列表的末尾(边界列表就是指上边的多个类型限定)
在擦除的的时候会有一些必要的强制类型转化,其中脉络比较复杂,也牵扯了泛型的核心,这里不展开说明了。
5、通配符
用如下代码讲解通配符的作用和简单用法
public class Main {
public static void main(String[] args) {
Utils.printEmployee(new Pair(new Manager("Job"), new Manager("Ketty")));
}
}
class Utils {
// 方法一:不能接收Manager
public static void printEmployee(Pair<Employee> e) {
System.out.println("first:" + e.getFirst().getName());
System.out.println("second:" + e.getSeconde().getName());
}
// 方法二:能接收Manager
public static void printEmployee(Pair<? extends Employee> e) {
System.out.println("first:" + e.getFirst().getName());
System.out.println("second:" + e.getSeconde().getName());
}
}
class Pair<T> {
private T first;
private T seconde;
public Pair(T first, T second) {
this.first = first;
this.seconde = second;
}
// getter and setter...
}
class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
// getter and setter...
}
class Manager extends Employee {
private int rank;
public Manager(String name) {
super(name);
}
// getter and setter...
}
- Manager继承自Employee,工具类中有一个方法printEmployee()希望可以去打印Pair类中存放的两个Employee的name信息,自然也可以想到,Manager继承自Employee,那么应该也可以打印Manager的name信息。
- 那么明确一点Pair< Manager >不是Pair< Employee >的子类,而且他们没有任何关系
- 那么问题来了,根据上一点说的,Pair< Manager >不是Pair< Employee >的子类,那方法一
printEmployee(Pair< Employee > e) 也就没法接收Pair< Manager >类型的参数,那么main方法中的调用也就自然而然报错,这就不符合我们自然的想法了,怎么解决呢?用通配符! - 方法二 printEmployee(Pair< ? extends Employee > e) 需要的参数是Pair< ? extends
Employee >类型,而Pair< Manager >是其子类,所以可以成功。