泛型程序设计
-
为什么要使用泛型程序设计
泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象重用。
-
定义简单泛型类
public class Pair<T>{ //T:类型变量 private T first; private T second; public Pair(){ first = null; second = null; } public Pair(T first,T second){ this.first = first; this.second = this.second; } public void setFirst(T first){ this.first = first; } public T getFirst(){ return first; } public void setSecond(T second){ this.second = second; } public T getSecond(){ return second; } } /** *实例化泛型类 *Sting:类型参数 */ Pair<String> p = new Pair<>();
泛型类可以有多个类型变量,如
public class Pari<T,U>{}
-
泛型方法
除了可以定义泛型类外,还可以定义带类型参数的简单方法
class ArrayAlg{ public static <T> T getMiddle(T...a){ return a[a.length/2]; } } //调用泛型方法 String middle = ArrayAlg.<String>getMiddle("a","b","c"); /* * 下面的代码编译器会自动打包参数为1个Double和两个Integer,然后寻找这两个类的公共超类型 * 找到2个这样的超类,一个是Number,一个是Comparable,将会报错 * 解决的办法就是将所有参数写为double值 */ double middle = ArrayAlg.getMiddle(2.3,10,0);
类型变量放在修饰符的后面,返回类型的前面
调用泛型方法时,类型参数放在方法名之前的尖括号中。
-
泛型变量的限定
有时,类或方法需要对类型变量加一约束。
class ArrayAlg{ /* *约束方法调用者传递的类型参数必须实现Comparable接口 *不管绑定类型是接口还是类,都使用关键字extends *一个类型变量可以有多个限定:<T extends Comparable & Serializable> */ public static <T extends Comparable> T min(T[] a){ if (a == null || a.length == 0){ return null } T smallest = a[0]; for (int i = 1; i < a.length; i++){ if (a[i] < smallest){ smalleset = a[i]; } } } }
-
擦除类型
在程序中可以有不同类型的
Pair
,如Pair<String>
,Pair<Date>
,擦除类型后,就变成原始类型Pair
,
原始类型Pair
如下:/* * 类型查出后,无限定的变量用Object替换, * 有限定的变量用限定的第一个类型替换,如: * public class Pair<T extends Comparable & Serializable> * 擦除类型后,用Comparable代替; * 类型限定时,将标签接口放在限定的最后 */ public class Pair{ private Object first; private Object seconde; public Pair(Object first,Object second){ this.first = first; this.second = this.second; } public void setFirst(Object first){ this.first = first; } public Object getFirst(){ return first; } public void setSecond(Object second){ this.second = second; } public Object getSecond(){ return second; } }
-
翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换:
Pair<Employee> buddies = ...; /* * 擦除getFirst的返回类型后将返回Object,编译器将自动插入Employee的强制类型转换 */ Employee buddy = buddies.getFirst();
-
翻译泛型方法
方法的类型擦除:
public static <T extends Comparable> T min(T[] a); //擦除类型后: public static Comparable min(Comparable[] a);
方法的擦除带来一个复杂的问题:
class DateIntervel extends Pair{ public void setSecond(Date second){ if (second.compara(getFirst())>=0){ super.setSecond(Seconde); } } }
DateIntervel
继承自擦除类型的Pair
,则DateIntervel
将有两个setSecond
方法:public void setSecond(Date second)
和继承自
Pair
的:public void setSecond(Object second)
显然,
DateIntervel
类中定义的setSecond
方法应该是对父类方法的覆盖才对。这时,擦除参数与多态发生了冲突。解决此问题的方式是:编译器在DateIntervel
类中生成一个桥方法:public void setSecond(Object second){ setSecond((Date)second); }
-
约束与局限(大多数限制都是由于类型擦除引起的)
1、 不能用基本类型实例化类型参数
2、 运行时类型检查只适用于原始类型:
if (a instanceof Pair<String>) //只能判断变量a是否引用了Pair类型,无法具体到Pair<String> //同理,getClass 也将返回原始类型 Pair<String> stringPair = ...; stringPari.getClass();//Pair.class
3、不能创建参数化类型的数组
例如:
```java
Pair<String>[] table = new Pair<>[10]; //这是错误的
```
擦除之后,`table` 的类型是Pair[],可以类型转换为Object[]。
```java
Pair[] table = new Pair[10];
Object[] objarray = table;
/*
* objarray[0]引用了table[0],table[0]是Pair类型
*/
objarray[0] = "string"; // java.lang.ArrayStoreException
/*
* 能够通过数组类型检查,但是任然会导致一个类型错误(不是Pair<String>)
*/
objarray[0] = new Pair<Employee>();
```
>只是不能创建参数化类型数组,声明类型为`Pair<String>[]`的变量任然是合法的,但是不能用`new Pair<String>[10]`来初始化变量。
参数化类型集合使用`ArrayList`来实现。
4、不能实例化类型变量
不能使用像`new T(...)`、`new T[...]`或`T.class`这样的表达式中的类型变量
在泛型类中,可以这样来创建一个类型变量实例:
```java
public static <T> Pair<T> makePair(Class<T> cl){
try{
return new Pair<>(cl.newInstance(),cl.newInstance());
}catch(Exception e){
return null;
}
}
/*
* Class本身是一个泛型类。String.class 是Class<String>的唯一实例
*/
Pair<String> p = Pair.makePair(String.class);
```
5、禁止使用带有类型变量的静态域和方法。
6、不能捕获或者抛出泛型类的实例
-
泛型类型的继承
无论
S
与T
有什么关系,Pair<S>
与Pair<T>
之间都没有任何联系但是,永远可以将一个参数化类型转换为一个原始类型。如:
Pair<Manager>
是原始类型Pair
的一个子类。Pair<Manager> manager = new Pair<>(new Manager(),new Manager()); Pair rowManager = manager; Manager m1 = manager.getFirst();//ok Manager m2 = rowManager.getFirst();//错误: 不兼容的类型: Object无法转换为Manager
泛型类可以实现或者扩展其他的泛型类。
泛型类继承 -
通配符类型
Pair<? extends Employee>
表示任何泛型类型,它的类型参数是Employee
的子类,如Pair<Manager>
,但不是Pair<String>
。作用:
public static void printBuddies(Pair<Employee> p){ Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName() + " and " + secon.getName() + " are buddies"); }
调用
printBuddies()
方法,只能向其传Pair<Employee>
类型的参数,而不能传Pair<Manager>
类型的参数,因为这两个类型之间没有继承关系。为了解除这一限制:
public static void pringBuddies(Pair<? extends Employy> p){ ... }
这样只要是
Pair<Employee>
类或其子类都可以作为printBuddies()
的参数。通配符类型继承关系不安全的修改器方法:
Pair<Manager> m = new Pair<>(new Manager("Bob",1300,new Address()), new Manager("Jack",1500,new Address())); Pair<? extends Employee> e = m; e.setFirst(new Manager("Tony",1200,new Address()));
不安全的修改器方法对于
Pair<? extends Employee>
,它的访问器和修改器似乎是:? extends Employee getFirst(); void setFirst(? extends Employee);
不能调用
setFirst
,编译器只知道需要某个Employee
类的子类,而不知道具体需要哪个类型。
将getFirst
的返回值赋给Employee
的一个引用就没有问题。image -
通配符的超类型限定
通配符还可以指定一个超类型限定:
? super Manager
,这个通配符限制为Manager
的所有超类型。超类型限定继承关系在子类型限定中,可以调用访问器方法,不能调用修改器方法;在超类型限定中,可以调用修改器方法,不能调用访问器方法。
Pair<? super Manager>
的方法似乎是:void setFirst(? super Manager); ? super Manager getFirst();
调用修改器方法,将
Manager
类或者其子类作为参数,都是可行的。但是调用访问器方法时,返回的对象类型就得不到保证,只能用Object
来接收。image -
无限定通配符
Pair<?>
有方法:? getFirst(); void setFirst(?);
getFirst
的返回值只能赋给Object
,不能调用setFirst
方法。Pair
和Pair<?>
的区别:可以使用任何Object类型
调用Pair
原始类型的setFirst
方法。Pair<?>继承结构根据该继承结构,
Pair<?>
实现许多简单的操作非常有用(只涉及到调用访问器的一些操作),如判断一个pair
是否包含一个null
引用:public static boolean hasNulls(Pair<?> p){ return p.getFirst() == null || p.getSecond() == null; }
-
通配符捕获
编写一个交换
Pair
元素的方法public static void swap(Pair<?> p){ //因为不知道Pair元素的具体类型,我们无法暂存第一个元素 swapHelper(p);//调用辅助方法 } public static <T> void swapHelper(Pair<T> p){ //参数T捕获通配符,此时T不再是类型变量,而是一个具体的类型 T t = p.getFirst(); p.setFirst(p.getSecond()); p.setSecond(t); } Pair<Manager> m = new Pair<>(new Manager("Bob",1300,new Address()),new Manager("Jack",1500,new Address())); Pair.swap(m);