泛型程序设计
泛型程序设计
写在最前
泛型 VS 强制类型转换
使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性以及可读性
为什么要使用泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用
定义简单的泛型类
泛型类就是具有一个或者多个类型变量的类,一般而言,类型变量使用大写形式并且比较短。
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;}
}
用具体的类型替换泛型类的类型变量就可以实现实例化泛型类,其实,泛型类可以看作是普通类的工厂
使用泛型类
/**
* @return return 的是一个Pair类
*/
public static Pair<String> minmax(String[] a){
if(a == null || a.length == 0) return null;
String min = a[0];
String max = a[0];
for(int i = 0;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);
}
/**
String[] words = {"Mary","had","a","little","lamb"};
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min = "+mm.getFirst());
System.out.println("max = "+mm.getSecond());
*/
泛型方法
泛型方法就是一个带有类型参数的简单方法
public static <T> T getMiddle(T... a){
return a[a.length/2];
}
/**
String middle = ArrayAlg.getMiddle("John","Q.","Public");
// 注意:如果参数是不同种类型的话,是不可以的,例如ArrayAlg.getMiddle(3.14,1729,0)编译器会报错
double middle2 = ArrayAlg.getMiddle(3.14,1729.0,0.0);
System.out.println(middle);
System.out.println(middle2);
*/
类型变量的限定
有的时候,需要对类型变量加以约束
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(smallest.compareTo(a[i]) > 0 ) smallest = a[i];
return smallest;
}
让T extends Comparable接口是为了确保泛型类型T所属的类有Comparable接口,方法为将泛型类型T限制为实现了Comparable接口的类。为泛型类型T设置限定bound
Comparable是一个接口,为什么用extend而不用implements,<T extends Comparable & Serializable> 表示T应该是绑定类型的子类型,通过&实现对泛型类型T的多个限定
泛型代码和虚拟机
写在最前:JAVA虚拟机(JVM)没有泛型类型对象,所有的对象都属于普通类
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都用他们的限定类型来进行替换
- 桥方法被合称来保持多态
- 为了保持类型安全性,必要时插入强制类型转换
类型擦除:无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type),原始类型的名字就是删除类型参数后的泛型类型名,擦出类型变量,并替换成为限定类型(无限定类型用Object)
例如,上述Pair
的原始类型如下(因为T是一个无限定的类型变量,所以直接使用Object替换)
public class Pair{
private Object first;
private Object second;
public Pair(){first = null;second = null;}
public Pair(Object first, Object second){this.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;}
}
再来一个原始类型类的转换
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){...}
}
如果是class Interval<T extends Serializable & Comparable> 那么原始类型会用Serializable替换T
翻译泛型表达式:当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
JVM对原始方法Pair.getFirst()方法的调用,然后将返回的Object类强制转换成为Employee类型
翻译泛型方法
类型擦除也会出现在泛型方法中
public static <T extends Comparable> T min(T[] a){}
擦除类型之后
public static Comparable min(Comparable[] a){}
有时候,类型擦除会带来复杂的问题,如下实例:
class DateInterval extends Pair<LocalDate>
{
public void setSecond(LocalDate second){....}
}
// 类型擦除之后
class DateInterval extends Pair
{
public void setSecond(LocalDate second){....}
}
// 存在另一个从Pair继承的setSecond方法
public void setSecond(Object second){...}
上述方法擦除就产生了问题,问题在于类型擦除与多态发生了冲突,想要解决这个问题,就需要在编译器在DateInterval类中生成一个桥方法(bridge method)
class DateInterval extends Pair<Localdate>{
public void setSecond(Object second){setSecond((Date) second);}
}