JAVA5泛型和反射.docx
- 文档编号:9738087
- 上传时间:2023-02-06
- 格式:DOCX
- 页数:46
- 大小:37.43KB
JAVA5泛型和反射.docx
《JAVA5泛型和反射.docx》由会员分享,可在线阅读,更多相关《JAVA5泛型和反射.docx(46页珍藏版)》请在冰豆网上搜索。
JAVA5泛型和反射
J2SE1.5-代号为Tiger-计划在2003年年底发布。
我一直都热衷于尽可能多地收集有关即将推出的新技术的预告信息,因此我将撰写一系列的文章,讨论可从V1.5中获得的新的和经过重组的特性,本文是第一篇。
我特别想谈谈泛型类型并重点讲述在Tiger中为了支持它们而进行的更改和调整。
在许多方面,Tiger肯定是迄今为止在Java编程方面(包括对源语言语法的重大扩展)所取得的最大进步。
Tiger中计划进行的最显著的变化是添加泛型类型,正如在JSR-14原型编译器中所预先展示的那样(您可以立即免费下载该编译器;请参阅参考资料)。
让我们从介绍泛型类型是什么以及添加了什么特性来支持它们开始吧。
数据类型转换和错误
为理解泛型类型为何如此有用,我们要将注意力转向Java语言中最容易引发错误的因素之一-需要不断地将表达式向下类型转换(downcast)为比其静态类型更为具体的数据类型(请参阅参考资料中的“TheDoubleDescentbugpattern”,以了解进行数据类型转换时,可能会碰到的麻烦的某些方面)。
程序中的每个向下类型转换对于ClassCastException而言都是潜在的危险,应当尽量避免它们。
但是在Java语言中它们通常是无法避免的,即便在设计优良的程序中也是如此。
在Java语言中进行向下类型转换最常见的原因在于,经常以专用的方式来使用类,这限制了方法调用所返回的参数可能的运行时类型。
例如,假定往Hashtable中添加元素并从中检索元素。
那么在给定的程序中,被用作键的元素类型和存储在散列表中的值类型,将不能是任意对象。
通常,所有的键都是某一特定类型的实例。
同样地,存储的值将共同具有比Object更具体的公共类型。
但是在目前现有的Java语言版本中,不可能将散列表的特定键和元素声明为比Object更具体的类型。
在散列表上执行插入和检索操作的类型特征符告诉我们只能插入和删除任意对象。
例如,put和get操作的说明如下所示:
清单1.插入/检索类型说明表明只能是任意对象
classHashtable{
Objectput(Objectkey,Objectvalue){...}
Objectget(Objectkey){...}
...
}
因此,当我们从类Hashtable的实例检索元素时,比如,即使我们知道在Hashtable中只放了String,而类型系统也只知道所检索的值是Object类型。
在对检索到的值进行任何特定于String的操作之前,必须将它强制转换为String,即使是将检索到的元素添加到同一代码块中,也是如此!
清单2.将检索到的值强制转换成String
importjava.util.Hashtable;
classTest{
publicstaticvoidmain(String[]args){
Hashtableh=newHashtable();
h.put(newInteger(0),"value");
Strings=(String)h.get(newInteger(0));
System.out.println(s);
}
}
请注意main方法主体部分的第三行中需要进行的数据类型转换。
因为Java类型系统相当薄弱,因此代码会因象上面那样的数据类型转换而漏洞百出。
这些数据类型转换不仅使Java代码变得更加拖沓冗长,而且它们还降低了静态类型检查的价值(因为每个数据类型转换都是一个选择忽略静态类型检查的伪指令)。
我们该如何扩展该类型系统,从而不必回避它呢?
用泛型类型来解决问题!
要消除如上所述的数据类型转换,有一种普遍的方法,就是用泛型类型来增大Java类型系统。
可以将泛型类型看作是类型“函数”;它们通过类型变量进行参数化,这些类型变量可以根据上下文用各种类型参数进行实例化。
例如,与简单地定义类Hashtable不同,我们可以定义泛型类Hashtable
除了类名后跟着尖括号括起来的一系列类型参数声明之外,在Tiger中定义这样的泛型类的语法和用于定义普通类的语法很相似。
例如,可以按照如下所示的那样定义自己的泛型Hashtable类:
清单3.定义泛型Hashtable类
classHashtable
然后可以引用这些类型参数,就像我们在类定义主体内引用普通类型那样,如下所示:
清单4.像引用普通类型那样引用类型参数
classHashtable
...
Valueput(Keyk,Valuev){...}
Valueget(Keyk){...}
}
类型参数的作用域就是相应类定义的主体部分(除了静态成员之外)(在下一篇文章中,我们将讨论为何Tiger实现中有这样的“怪习”,即必须对静态成员进行此项限制。
请留意!
)。
创建一个新的Hashtable实例时,必须传递类型参数以指定Key和Value的类型。
传递类型参数的方式取决于我们打算如何使用Hashtable。
在上面的示例中,我们真正想要做的是创建Hashtable实例,它只将Integer映射为String。
可以用新的Hashtable类来完成这件事:
清单5.创建将Integer映射为String的实例
importjava.util.Hashtable;
classTest{
publicstaticvoidmain(String[]args){
Hashtable
h.put(newInteger(0),"value");
...
}
}
现在不再需要数据类型转换了。
请注意用来实例化泛型类Hashtable的语法。
就像泛型类的类型参数用尖括号括起来那样,泛型类型应用程序的参数也是用尖括号括起来的。
清单6.除去不必要的数据类型转换
...
Strings=h.get("key");
System.out.println(s);
当然,程序员若只是为了能使用泛型类型而必须重新定义所有的标准实用程序类(比如Hashtable和List)的话,则可能会是一项浩大的工程。
幸好,Tiger为用户提供了所有Java集合类的泛型版本,因此我们不必自己动手来重新定义它们了。
此外,这些类能与旧代码和新的泛型代码一起无缝工作(下个月,我们会说明如何做到这一点)。
Tiger的基本类型限制
Tiger中类型变量的限制之一就是,它们必须用引用类型进行实例化-基本类型不起作用。
因此,在上面这个示例中,无法完成创建从int映射到String的Hashtable。
这很遗憾,因为这意味着只要您想把基本类型用作泛型类型的参数,您就必须把它们组装为对象。
另一方面,当前的这种情况是最糟的;您不能将int作为键传递给Hashtable,因为所有的键都必须是Object类型。
我们真正想看到的是,基本类型可以自动进行包装(boxing)和解包装(unboxing),类似于用C#所进行的操作(或者比后者更好)。
遗憾的是,Tiger不打算包括基本类型的自动包装(但是人们可以一直期待Java1.6中出现该功能!
)。
受限泛型
有时我们想限制可能出现的泛型类的类型实例化。
在上面这个示例中,类Hashtable的类型参数可以用我们想用的任何类型参数进行实例化,但是对于其它某些类,我们或许想将可能的类型参数集限定为给定类型范围内的子类型。
例如,我们可能想定义泛型ScrollPane类,它引用普通的带有滚动条功能的Pane。
被包含的Pane的运行时类型通常会是类Pane的子类型,但是静态类型就只是Pane。
有时我们想用getter检索被包含的Pane,但是希望getter的返回类型尽可能具体些。
我们可能想将类型参数MyPane添加到ScrollPane中,该类型参数可以用Pane的任何子类进行实例化。
然后可以用这种形式的子句:
extendsBound来说明MyPane的声明,从而来设定MyPane的范围:
清单7.用extends子句来说明MyPane声明
classScrollPane
当然,我们可以完全不使用显式的范围,只要能确保没有用不适当的类型来实例化类型参数。
为什么要自找麻烦在类型参数上设定范围呢?
这里有两个原因。
首先,范围使我们增加了静态类型检查功能。
有了静态类型检查,就能保证泛型类型的每次实例化都符合所设定的范围。
其次,因为我们知道类型参数的每次实例化都是这个范围之内的子类,所以可以放心地调用类型参数实例出现在这个范围之内的任何方法。
如果没有对参数设定显式的范围,那么缺省情况下范围是Object,这意味着我们不能调用范围实例在Object中未曾出现的任何方法。
多态方法
除了用类型参数对类进行参数化之外,用类型参数对方法进行参数化往往也同样很有用。
泛型Java编程用语中,用类型进行参数化的方法被称为多态方法(Polymorphicmethod)。
多态方法之所以有用,是因为有时候,在一些我们想执行的操作中,参数与返回值之间的类型相关性原本就是泛型的,但是这个泛型性质不依赖于任何类级的类型信息,而且对于各个方法调用都不相同。
例如,假定想将factory方法添加到List类中。
这个静态方法只带一个参数,也将是List唯一的元素(直到添加了其它元素)。
因为我们希望List成为其所包含的元素类型的泛型,所以希望静态factory方法带有类型变量T这一参数并返回List
但是我们确实希望该类型变量T能在方法级别上进行声明,因为它会随每次单独的方法调用而发生改变(而且,正如我在下一篇文章中将讨论的那样,Tiger设计的“怪习”规定静态成员不在类级类型参数的范畴之内)。
Tiger让我们通过将类型参数作为方法声明的前缀,从而在单独的方法级别上声明类型参数。
例如,可以按照如下所示的那样为factory方法make添加前缀:
清单8.将类型参数作为前缀添加到方法声明
classUtilities{
returnnewList
}
}
除了多态方法中所增加的灵活性之外,Tiger中还增加了一个优点。
Tiger使用类型推断机制,根据参数类型来自动推断出多态方法的类型。
这可以大大减少方法调用的繁琐和复杂性。
例如,如果想调用make方法来构造包含newInteger(0)的List
清单9.强制make构造新实例
Utilities.make(Integer(0))
然后会自动地从方法参数中推断出类型参数的实例化。
结束语
正如我们所见到的那样,在Java语言中添加泛型类型肯定会大大增强我们使用静态类型系统的能力。
学习如何使用泛型类型相当简单,但是同样也需要避免一些缺陷。
在接下来的文章中,我们将讨论如何充分使用将出现在Tiger中的泛型类型的特定表现,以及一些缺陷。
我们还将研究对泛型Java类型工具的扩展,我们期盼这些工具可以出现在仍处于设计阶段的Java平台之中。
一、逐渐深入泛型
1、没有任何重构的原始代码:
有两个类如下,要构造两个类的对象,并打印出各自的成员x。
publicclassStringFoo{
privateStringx;
publicStringFoo(Stringx){
this.x=x;
}
publicStringgetX(){
returnx;
}
publicvoidsetX(Stringx){
this.x=x;
}
}
publicclassDoubleFoo{
privateDoublex;
publicDoubleFoo(Doublex){
this.x=x;
}
publicDoublegetX(){
returnx;
}
publicvoidsetX(Doublex){
this.x=x;
}
}
以上的代码是在无聊,就不写如何实现了。
2、对上面的两个类进行重构,写成一个类:
因为上面的类中,成员和方法的逻辑都一样,就是类型不一样,因此考虑重构。
Object是所有类的父类,因此可以考虑用Object做为成员类型,这样就可以实现通用了,实际上就是“Object泛型”,暂时这么称呼。
publicclassObjectFoo{
privateObjectx;
publicObjectFoo(Objectx){
this.x=x;
}
publicObjectgetX(){
returnx;
}
publicvoidsetX(Objectx){
this.x=x;
}
}
写出Demo方法如下:
publicclassObjectFooDemo{
publicstaticvoidmain(Stringargs[]){
ObjectFoostrFoo=newObjectFoo("HelloGenerics!
");
ObjectFoodouFoo=newObjectFoo(newDouble("33"));
ObjectFooobjFoo=newObjectFoo(newObject());
System.out.println("strFoo.getX="+(String)strFoo.getX());
System.out.println("douFoo.getX="+(Double)douFoo.getX());
System.out.println("objFoo.getX="+(Object)objFoo.getX());
}
}
运行结果如下:
strFoo.getX=HelloGenerics!
douFoo.getX=33.0
objFoo.getX=java.lang.Object@19821f
解说:
在Java5之前,为了让类有通用性,往往将参数类型、返回类型设置为Object类型,当获取这些返回类型来使用时候,必须将其“强制”转换为原有的类型或者接口,然后才可以调用对象上的方法。
3、Java5泛型来实现
强制类型转换很麻烦,我还要事先知道各个Object具体类型是什么,才能做出正确转换。
否则,要是转换的类型不对,比如将“HelloGenerics!
”字符串强制转换为Double,那么编译的时候不会报错,可是运行的时候就挂了。
那有没有不强制转换的办法----有,改用Java5泛型来实现。
publicclassGenericsFoo
privateTx;
publicGenericsFoo(Tx){
this.x=x;
}
publicTgetX(){
returnx;
}
publicvoidsetX(Tx){
this.x=x;
}
}
publicclassGenericsFooDemo{
publicstaticvoidmain(Stringargs[]){
GenericsFoo
");
GenericsFoo
GenericsFoo
System.out.println("strFoo.getX="+strFoo.getX());
System.out.println("douFoo.getX="+douFoo.getX());
System.out.println("objFoo.getX="+objFoo.getX());
}
}
运行结果:
strFoo.getX=HelloGenerics!
douFoo.getX=33.0
objFoo.getX=java.lang.Object@19821f
和使用“Object泛型”方式实现结果的完全一样,但是这个Demo简单多了,里面没有强制类型转换信息。
下面解释一下上面泛型类的语法:
使用
当然T仅仅是个名字,这个名字可以自行定义。
classGenericsFoo
与Object泛型类相比,使用泛型所定义的类在声明和构造实例的时候,可以使用“<实际类型>”来一并指定泛型类型持有者的真实类型。
类如
GenericsFoo
当然,也可以在构造对象的时候不使用尖括号指定泛型类型的真实类型,但是你在使用该对象的时候,就需要强制转换了。
比如:
GenericsFoodouFoo=newGenericsFoo(newDouble("33"));
实际上,当构造对象时不指定类型信息的时候,默认会使用Object类型,这也是要强制转换的原因。
二、泛型的高级应用
1、限制泛型的可用类型
在上面的例子中,由于没有限制classGenericsFoo
限制比如我们要限制T为集合接口类型。
只需要这么做:
classGenericsFoo
注意:
但这里的extends已经不是继承的含义了,应该理解为T类型是实现Collection接口的类型,或者T是继承了XX类的类型。
下面继续对上面的例子改进,我只要实现了集合接口的类型:
publicclassCollectionGenFoo
privateTx;
publicCollectionGenFoo(Tx){
this.x=x;
}
publicTgetX(){
returnx;
}
publicvoidsetX(Tx){
this.x=x;
}
}
实例化的时候可以这么写:
publicclassCollectionGenFooDemo{
publicstaticvoidmain(Stringargs[]){
CollectionGenFoo
listFoo=newCollectionGenFoo
//出错了,不让这么干。
// CollectionGenFoo
// listFoo=newCollectionGenFoo
System.out.println("实例化成功!
");
}
}
当前看到的这个写法是可以编译通过,并运行成功。
可是注释掉的两行加上就出错了,因为
别急,泛型针对这种情况还有更好的解决方案,那就是“通配符泛型”。
2、通配符泛型
为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为
extendsCollection>,“?
”代表未知类型,这个类型是实现Collection接口。
那么上面实现的方式可以写为:
publicclassCollectionGenFooDemo{
publicstaticvoidmain(Stringargs[]){
CollectionGenFoo
listFoo=newCollectionGenFoo
//现在不会出错了
CollectionGenFoo
extendsCollection>listFoo1=null;
listFoo=newCollectionGenFoo
System.out.println("实例化成功!
");
}
}
注意:
1、如果只指定了
>,而没有extends,则默认是允许Obje
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JAVA5 反射