开始好好在CSDN记录学习过程。
今天大年初一。
参考:
- Java核心技术卷1 第12章 泛型程序设计
- [http://blog.csdn.net/lonelyroamer/article/details/7864531]
1. 为什么需要Java泛型
类似C++的模板概念,泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用,或者说叫某些普通类的1个工厂。
在泛型之前,用Object[]存储不同对象,引用时再进行必要的强制转换:
public class Alist
{
private Object[] elementData;
public Object get(int i) {...}
public void add(Object o) {...}
}
Alist lst = new AList();
lst.add("good");
lst.add(123);
String s = (String)lst.get(0); //必须的
Integer s = (Integer)lst.get(1);
所有的强转都要程序员保证,编译器无法检查。
采用一种机制让编译器能检查对错,而且程序更好理解、更安全,是不是很好的事情?
String s = lst.get(0);
于是引入Alist<T>
的概念。T
代表具体的类,如:
Alist<String> lst = new AList<String>();
lst.add("good")//ok
lst.add(123)//编译器检查出错误
String s = lst.get(0)//ok
2. 什么是Java泛型
泛型一般用以下几种基本的形式出现,这里用1个例子囊括这些情况:
泛型集合 ,比如 ArrayList<>
泛型类 or 泛型接口
泛型方法
限定类型
限定类型可以是限定 泛型类or泛型接口or泛型方法,这里以泛型方法为例子:
interface iPair<T,U> { //泛型接口
public void show(T t,U u);
}
class Pair<T> implements iPair //实现泛型接口的泛型类 可以有多个类型变量,比如<T,U>
{
private T t;
private ArrayList<String> slst = new ArrayList<String>(); //泛型数组
public void show(T st U su){ //泛型接口中show的实现
...
}
public Pair(T t) {
this.t=t;
}
public void setValue(T value) {
this.t= value;
}
public <T> T getValue() {
return t;
}
public static <T> T getMiddleA(T[] tg){
return tg[tg.Length/2];
} //泛型方法
public static <T extends Comparable & Serializable> T getMiddleB(T[] t){
return t[t.Length/2];
} //限定类型的泛型方法,T限定为Comparable和Serializable
}
在引用方法时,把泛型变量具体化即可,如
Integer a = 123;
String b = "good";
Pair<String> p= new Pair<String>();
String s = p.<String>getMiddleA("S","M","L"); //指定泛型类型为String,而且String是实现了Comparable接口的类,
当然也可以不指定方法泛型类型的运行,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级,直到Object
Number c = p.getMiddleA(123,1.1,456); //不指定泛型类型,返回Double和Integer最近的共有父类Number
对于泛型方法getMiddleB
,只能被实现了Comparable
接口的类的数组调用。
然而在JVM里运行字节代码时,是没有某个泛型对象的,所有对象是普通类。所有的泛型都有1个原始类型(raw type),对于于Pair<String>
,去掉String的Pair就是原始类型。编译时去掉字节码中的泛型类型的过程,就是类型擦除(type erasure)。原始类型实际上做了替换,注意是所有的泛型类、方法都会发生相应的替换:
在无限定的时候,替换成Object类;
在有限定的时候,替换成限定类。(第1个限定类型)
而且在编译前,编译器先检查代码中泛型的类型,注意是检查引用对象本身,而不是引用的目标对象。类型不对会提示错误,再擦除。
Pair<String> p = new Pair<String>();
p.setValue("good");//编译器OK
p.setValue(123);//编译器发现错误并提示
String s = p.getValue()//返回是1个String对象
Pair<Object> q = new Pair<String>();
p.setValue("good");//编译器OK
p.setValue(123);//编译器OK
Object o = p.getValue()//返回是1个Object对象
如果不指定限定,Pair在运行时的原始类型将替换成如下的普通类:
class Pair //省略上面提到的泛型接口实现
{
private Object t;
private ArrayList<String> slst = new ArrayList<String>();
public Pair(Object t) {
this.t=t;
}
public void setValue(Object value) {
this.t= value;
}
public Object getValue() {
return t;
}
public static Object getMiddleA(Object[] tg){
return tg[tg.Length/2];
} //
public static Comparable getMiddleB(Comparable[] tg){
return tg[tg.Length/2];
} //限定类型的泛型方法,替换成第1个限定类
}
引用原来的泛型返回值时:
Pair<String> p = new Pair<String>();
String s = p.getValue();//编译器自动插入了强制转换,不需要(String)( p.getValue())
引用原来带限定的泛型方法:
Pair<String> p = new Pair<String>();
Comparable b= p.<String>getMiddleB();
3. 遇到问题
在引用泛型方法时,如果某个类是Pair的继承类,继承中很重要的一个特性-多态,将与类型擦除发生冲突。举个例子:
class subPari extends Pair<String>
{
private String t;
public void setValue(String value) {
if(value.Length == 100)
this.t= value;
}
public String getValue() {
return t;
}
}
上面代码的本意是实现setValue
和getValue
的多态。然而由于类型擦除后,Pair类中,也就是subPair的超类,他们实际上应该是
public void setValue(Object value) {
if(Object .Length == 100)
this.t= value;
}
public Object getValue() {
return t;
}
对于setValue
,这不是在实现多态,subPair看上去是实现了重载,父类方法的参数是Object,而子类的是String;
JVM解决的方法是引入一种叫“桥”的方法。对于setValue方法
subPair s = new subPair(...);
Pair<String> p = s;
p.setValue("good");//多态特性,应调用subPair的方法,
由于setValue
不是真的重写了,p是Pair
类型,调用的Pair.setValue(Object)
,在subPair里,另外还生成了1个桥方法(bridge method)
public void setValue(Object value) {setValue((String)value)};
根据多态规则,真正调用的应该是上面的桥方法。最终还是实现了subPair.setValue
的效果。
getValue
也有类似的桥方法。但它更奇怪,同1个方法却有Object和String 两种返回类型。JVM内部通过同时判断参数和返回类型来确定1个调用方法,这个特性叫协变(covariant return types),但在JAVA代码里是绝不允许出现。
4. 一些规则
不能用基本类型实例化类型参数
不允许Pair<double>
,可以有Pair<Double>
;
运行时查询不能用于泛型
不允许s instanceof Pair<String>
;
不允许强制转换 Pair<String> s = (Pair<String>)s1
;
对于Pair<String> s
,s.getClass()
一定返回的是Pair.class
;
不能创建参数化类型数组
不允许 Pair<String>[] s = new Pair<String>[10]
;
不能实例化类型变量
不能使用new T(...)
,new T[...]
,T.class
,可以通过反射调用Class.newInstance
来构造泛型对象。
不能将泛型变量静态化
不能使用private static T xxx
;
不能定义private static T function(){...}
;
不能捕获、抛出泛型类的实例
不能同时让一个类或泛型变量同时成为两个接口类型的子类
有class A extends Comparable<a>{...}
不允许class B extends A implements Comparable<b>{...}