泛型对于集合类尤为有用
1 为什么要使用泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。
1.1 类型参数的好处
在Java中增加泛型类之前,泛型程序设计是用继承实现的。ArrayList类只维护一个Object引用的数组:
public class ArrayList{
private Object[] elementData;
...
public Object get(int i){...}
public void add(Object o){...}
}
上述方法存在两个问题:1当获取一个值时必须进行强制类型转换
2没有错误检查,可以向数组列表中添加任何类的对象
泛型提供了一个更好的解决方案:类型参数,ArrayList类有一个类型参数用来指示元素的类型:
ArrayList<String> files=new ArrayList<String>();
这样做是的代码具有更好的可读性
Java SE 7及以后的版本中可以在构造函数中省略泛型类型:
ArrayList<String> files=new ArrayList<>();
省略的类型可以从变量的类型推断得出。
*编译器也可以很好的利用这个信息,当获取一个值时不需要进行强制类型转换,编译器就知道返回值类型为String。
*编译器还知道ArrayList<String>中add方法有一个类型为String的参数,并且编译器会进行检查,避免插入错误类型的对象
类型参数的魅力在于:使得程序具有更好的可读性和安全性。
1.2 谁想称为泛型程序员
使用一个像ArrayList的泛型类很容易,但是实现一个泛型类并没有那么容易。
例如,ArrayList类有一个addAll方法用来添加另一个集合的全部元素。可以将ArrayList<Manage>中的所有元素添加到ArrayList<Employee>中去,但是反过来就不行。如果只能允许前一个调用,而不能允许后一个调用呢?(通配符类型)
2 定义简单泛型类
一个泛型类是具有一个或多个类型变量的类,泛型类可以有多个类型变量
类定义中的类型变量指定方法的返回类型以及域和局部变量的类型
public class Paris<T> {
private T second;
first = null;
}
this.first = first;
}
return first;
public T getSecond() {
}
first = newValue;
public void setSecond(T newValue) {
}
private T first;
public Paris() {
second = null;
public Paris(T first,T second) {
this.second = second;
public T getFirst() {
}
return second;
public void setFirst(T newValue) {
}
second = newValue;
}
用具体的类型替换类型变量就可以实例化泛型类型,可以将结果想象成带有构造器的普通类,换句话说,泛型类可看作普通类的工厂
3 泛型方法
泛型方法既可以定义在普通类中,也可以定义在泛型类中。
类型变量放在修饰符的后面,返回类型的前面。
4 类型变量(T)的限定
class ArrayAlg
{
public static <T> 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所属的类有compareTo方法,将T限制为实现了Comparable接口的类
public static <T extends Comparable> T min(T[] a)...
**为什么用extends而不是implements?
T应该是绑定类型的子类型,T和绑定类型可以是类,也可以是接口,选用extends是因为更加接近子类的概念
同时一个类型或通配符可以有多个限定(可以拥有多个接口类型,但至多只能有一个类),例:
T extends Comparable & Serializable
限定类型用“&”分隔,用逗号来分隔类型变量,如果用一个类作为限定,它必须是限定列表中的第一个。
5 泛型代码和虚拟机
虚拟机中没有泛型类型对象,所有对象都属于普通类
5.1 类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。
擦除类型变量,并替换为限定类型(无限定的变量用Object)
Paris<T>的原始类型为:
public class Pair
{
private Object first;
private Object second;
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;}
}
因为T是一个无限定的变量,所以直接用Object替换。
假定有一个泛型类Interval:
public class Interval<T extends Compareble & 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,而编译器在必要时要插入Comparable强制类型转换。
为了提高效率,应该将标签接口(没有方法的接口)放在边界列表的末尾。
5.2 翻译泛型表达式(就是泛型类型被擦除,编译器要对其进行翻译,到底返回一个什么样类型的值)
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,即:
1.对原始方法Pair.getFirst的调用
2.将返回的Object类型强制转换为Employee类型
当存取一个泛型域时也要插入强制类型转换
5.3 翻译泛型方法
方法擦除带来了两个复杂的问题:
有一个日期区间是一对LocalDate对象,并且需要覆盖Pair中的setSecond这个方法来确保第二个值永远不小于第一个值。
class DateInterval extends Pair<LocalDate>
{
public void setSecond(LocalDate second)
{
if(second.compareTo(getFirst())>=0)
super.setSecond(second);
}
}
但是类型擦除后变成:
class DateInterval extends Pair
{
public void setSecond(LocalDate second){...}
...
}
这里,希望对setSecond的调用具有多态性,并调用最合适的方法。但由于类型擦除与多态发生了冲突。要解决这个问题需要在DateInterval中生成一个桥方法(再覆盖超类的setSecond(Object second)方法):
public void setSecond(Object second){ setSecond((Date) second); }
java泛型转换的事实:
a虚拟机中没有泛型,只有普通的类和方法
b所有的类型参数都用他们的限定类型替换
c桥方法被合成来保持多态
d为保持类型安全性,必要时插入强制类型转换
5.4 调用遗留代码
设计Java泛型类型时,主要目标是允许泛型代码和遗留代码之间能够相互操作。
可以使用@SuppressWarnings("unchecked")消除警告
6 约束与局限性
大多数限制是由类型擦除引起的
6.1 不能用基本类型实例化类型参数
可以使用基本类型的包装类,如Pair<Double>,擦除之后Pair类可以含Object类型的域,而Object不能存储double。
6.2 运行时类型查询只适用于原始类型
所有的类型查询只产生原始类型
if(a instanceof Pair<String>)//error
如果仅仅测试a是否是任意类型的一个Pair,也会出现错误
if(a instanceof Pair<T>)//error
总结:试图查询一个对象是否属于某个泛型类型时,倘若使用instanceof会得到一个编译错误,如果使用强制类型转换会得到一个警告