Generic programming
1-Reason:
编写的代码可以被很多不同类型的对象所重用。
泛型提供的一个解决方案:type parameters
ArrayList类有一个类型参数用来指示元素的type:
ArrayList<String> files=new ArrayList<String>();
更具可读性
tips:构造函数中可以省略泛型类型:
ArrayList<String> files=new ArrayList<>();
省略的type可以从变量的type推断
利用wildcard type编写出尽可能灵活的方法
2-定义简单泛型类 generic class
一个泛型类就是具有一个或多个类型变量的类。
例:pair类,只关注泛型
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类,其中第一个域和第二个域使用不同的类型:
public class Pair<T,U>{...}
类定义中的类型变量指定方法的返回类型以及域和局部变量的类型,如:
private T first; //uses the type variable
java库中,使用变量E表示集合的元素类型
K和V分别表示表的关键字与值的类型
T(需要时还可以用邻近的字母U和S)表示“任意类型”
用具体的类型替换类型变量就可以实例化泛型类型,如:
Pair<String>
可以将结果想象成带有构造器的普通类和方法,或者普通类的工厂
3-泛型方法
定义一个带有类型参数的方法
class ArrayAlg{
public static <T> T getMiddle(T...a){
return a[a.length/2];
}
}
这个方法是在普通类中定义的,而不是在泛型类中定义的
类型变量放在修饰符后,返回类型前
泛型方法可以定义在普通类中,也可以定义在泛型类中
当调用一个泛型方法是,在方法名前的<>内放入具体的类型:
String middle=ArrayAlg.getMiddle("John","Q.","Public");
此时,方法调用可以省略<String>类型参数。编译器用names的类型(String[])
与泛型类型 T[] 进行匹配并推断出T一定是String。
可以调用:
String middle=ArrayAlg.getMiddle("John","Q.","Public");
attention:提示错误
例:
double middle=ArrayAlg.getMiddle(3.14,1729,0);
4-类型变量的限定
类或方法有时需要对类型变量加以约束
例:
class ArrayAlg{
public static<T> T min(T[] a){ //almost correct
if(a==null||a.length==0) return null;
T smallest=a[0];
for(int i=1;i<a.length;i++)
if(smallest.compareTo(a[i])>0) smallest=a[i];
return smallest;
question:
smallest类型为T,怎样能确信T所属的类有compareTo方法?
方案是将T限制为实现了Comparable接口(只含一个方法compareTo的标准接口)的类。
通过对类型变量T设置bound(绑定\限定)实现:
public static <T extends Comparable> T min(T[] a)...
实际上Comparable接口本身就是一个泛型类型
现在,泛型min方法只能被实现了Comparable接口的类(如String、Date等)的数组调用
由于Rectangle方法没有实现Comparable接口,所以调用min将会产生编译错误
<T extends BoudingType>
表示T应该是绑定类型的subtype。T和绑定类型可以是类,也可以是接口,
因此选择关键字extends更接近概念
一个类型变量或通配符可以有多个限定
如:
T extends Comparable & Serializable 可比较&序列化
& 分隔限定类型,"," 分隔类型变量
Java继承中,可以 根据需要拥有多个接口超类型,但限定中至多有一个类。
如果用一个类作为限定,它必须是限定列表中的第一个
例:(重新编写了一个泛型方法minmax,计算泛型数组的最大值和最小值,并返回Pair<T>)
package pair2;
import java.util.*;
public class PairTest{
GregorianCalendar[] birthdays=
{new GregorianCalendar(1906,Calendar,DECEMBER,9),//G.H
new GregorianCalendar(1815,Calendar,DECEMBER,10),//A.L
new GregorianCalendar(1903,Calendar,DECEMBER,3),//J.N
bew GregorianCalendar(1910,Calendar,JUNE,22),//K.Z
}
Pair<GregorianCalendar>mm=ArrayAlg.minmax(birthdays);
System.out.println("min="+mm.getFirst().getTime());
System.out.println("max="+mm.getSecond().getTime());
}
}
class ArrayAlg{
/**Gets the minimun and maximun of an array of objects of type T.
@param a an array of ibjects of type T
@return a pair with the min and max value,or null if a is null or empty
*/
public static <T extends Comparable> Pair<T> minmax(T[] a){
if(a==null || a.length==0) return null;
T min=a[0];
T max=a[0];
for(int i=1;i<a.length;i++){
if(min.compareTo(a[i])>0) min=a[i];
if(max.compareTo(a[i])<0) max=a[i];
}
return new Pair<>(min,max);
}
}
5-泛型代码和虚拟机
定义一个泛型类型,都自动提供一个相应的raw type,原始类型的名字就是删去类型参数后的泛型类型名。
erased类型变量,并替换为限定类型(无限定的变量用Object)
例:(Pair<T>的原始类型如下)
public class Pair{
private Object first;
private Object second;
public Pair(Object first,Object second){
thie.first=first;
this.second=second;
}
public Object getFirst(){return first;}
public Object getSecond(){return second;}
public void setFirst(Object newValue){first=newValue;}
public void setSecond(Object newValue){second=newValue;}
}
因为T是一个无限定的变量,所以直接用Object替换
结果是一个普通的类。
在程序中可以包含不同类型的Pair,如Pair<String>或Pair<GregorianCalendar>。
而擦除类型后就变成原始的Pair类型了
原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换
如,类Pair<T>中的类型变量没有显式的限定,因此原始类型用Objext替换T,假定声明一个不同的类型
public class Interval<T extends Comparable & Serializable> implements 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;}
}
}
//原始类型Interval如下:
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
...
public Interval(Comparable first,Comparable second){...}
}
翻译泛型表达式:
当程序调用泛型方法,如果擦除返回类型,编译器插入强制类型转换。如
Pair<Employee> buddies=...;
Employee buddy=buddies.getFirst();
擦除gerTirst的返回类型后将返回Object类型。编译器自动插入Employee的强制类型转换,即,编译器把这个方法调用翻译为两条虚拟机指令:
*对原始方法Pair.getFirst的调用
*将返回的Object类型强制转换为Employee类型
当存取一个泛型域是也要插入强制类型转换。假设Pair类的first域和second域都是公有的。表达式:
Employee buddy=buddies.first;
也会在结果字节码中插入强制类型转换
翻译泛型方法:
类型擦除也会出现在泛型方法中
public static<T extends Comparable> T min(T[] a)
擦除类型后,只剩下一个方法:
public static Comparable min(Comparable[] a)
attention:类型参数T已经被擦除了,只留下了限定类型Comparable
方法的擦除带来了两个复杂的问题,如下:
class DateInterval extends Pair<Date>{
public void setSecond(Date second){
if(second.compareTo(getFirst())>=0)
super.setSecond(second);
}
...
}
一个日期区间是一对Date对象,并且需要覆盖这个方法来确保第二个值永远不小于第一个值,这个类擦除后变成
class DateInterval extends Pair {//after erasure
public void setSecond(Date second){...}
...
}
存在另一个从Pair继承的setSecond方法,即:
public void setSecond(Object second)
这显然是一个不同的方法,因为它有一个不同类型的参数——Object,instead of Date.While it should be same.Think of this:
DateInterval interval=new DateInterval(...);
Pair<Date> pair=interval; //OK-assignment to superclass
pair.setSecond(aDate);
希望对setSecond的调用具有多态性,并调用最合适的方法,由于Pair引用DateInterval对象,所以应该调用DateIntervalSecond。
the contradiction:类型擦除与多态发生了冲突。
需要编译器在DateInterval类中生成一个bridge method:
public void setSecond(Object second){setSecond(Date) second;}
————————————————————————————————————
pair.setSecond(aDate)
变量pair已经声明为类型Pair<Date>,并且这个类型只有一个方法setSecond,
即setSecond(Object)。虚拟机用pair引用的对象调用这个方法。该对象是DateInterval类型的,因而将会调用DateInterval.setSecond(Object)方法。
这个方法是合成的桥方法。它调用DateInterval.setSecond(Date)