由于JDK5以前的版本都不支持泛型,因此Java有必要提供某种从旧的非泛型代码的过度措施.简单来说,非泛型的遗留代码需要既保留其功能,又与泛型兼容.这意味着非泛型代码必须能够处理泛型,泛型代码也必须能够处理非泛型代码.
为了处理向泛型的过度问题,Java允许不带任何类型实参来使用泛型类.这样做将创建一个原类型(raw type)的类.原类型与不识别泛型的遗留代码兼容.使用原类型的主要缺点是丧失了泛型的类型安全性.
下面是一个演示原类型的示例:
public class test2 { public static void main(String args[]) { Gen<Integer> iOb = new Gen<Integer>(88); Gen<String> strOb = new Gen<String>("test"); Gen raw = new Gen(new Double(98.6)); System.out.println("raw value : "+raw.getob()); strOb = raw; //原类型重写了类型安全性 //System.out.println("strOb value : "+strOb.getob()); raw = iOb; System.out.println("raw value : "+raw.getob()); } } class Gen<T> { T ob; Gen(T o) { ob = o; } T getob() { return ob; } }
该程序有几点需要说明,首先通过下面的声明创建了泛型类Gen的原类型:
Gen raw = new Gen(new Double(98.6));
注意没有指定类型实参,实际上,该语句创建了一个其类型T被Object取代的Gen对象.
原类型不具有类型安全性,原类型的变量可以被赋值为任何类型的Gen对象的引用,反之也可以,即特定的Gen对象类型可以赋值为原Gen对象的引用.但是,这两种操作都有潜在的不安全性,因为他们跳过了泛型的类型检查机制.
类型安全性的丧失由程序末尾注释掉的语句演示,下面分情况讨论.首先,考虑下面的情况:
// int i = (Integer) raw.getob();
在该语句中,获得了raw中ob的值,然后把该值转换为integer.问题是raw包含的是double值,而不是Integer值.但是,这一点编译时无法检测出来,因为raw类型是未知的.因此,该语句在运行时会失败.
下面的语句把原Gen对象的引用赋值给strOb(Gen<String>类型的引用):
strOb = raw;
从语法上看赋值语句是正确的,但是实际上有问题.由于strOb是Gen<String>类型的,因此他被认为包含一个字符串.但是,赋值之后,strOb引用的对象包含Double.因此,在运行时试图把strOb的内容赋值给str时,将产生运行时错误,因此strOb现在包含Double,由此可见,把原类型引用赋值给泛型引用跳过了类型安全性机制.
下面的语句是前面示例的相反过程:
raw = iOb;
其中,泛型引用赋值给了原引用变量.尽管从语法上看是正确的,但是也会产生问题,这可以从第二行看出.在本例中,raw引用了一个包含Integer对象的对象,但是类型转换假定他包含Double.该错误在编译时无法防止,因此他将导致运行时错误.
由于原类型具有固有的潜在风险,因此当使用原类型可能跳过类型安全性时,javac将显示一些未检测警告(unchecked warning),在前面的程序中,下面的语句产生了未检测警告:
Gen raw = new Gen(new Dobule(66.6)); strOb = raw;
第一行是因为不带类型实参调用Gen构造函数而产生警告,第二行是因为把原引用赋值给泛型变量而产生警告.
起初,可能会认为下面的语句也会产生未检测警告,其实不然:
raw = iOb;
因为该赋值语句产生的类型安全性损失与创建raw时已经发生的相比没有增加,所以编译器不会发出警告.
最后一点提示:应该把原类型的使用限制在必须把遗留代码和新的泛型代码混合使用的情况下,原类型只是一种过度措施,而不是应该应用于新代码的功能.