分享
 
 
 

Java语言与Generics

王朝java/jsp·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

内容:

一 Generics简介

二 Generics与Java语言的发展

三 Java中Generics的使用

四 Generics的设计和实现

五 结论

参考资料

关于作者

在 Java 专区还有:

教学

工具与产品

代码与组件

所有文章

实用技巧

欧阳辰 (yeekee@sina.com)

一 Generics简介

Generics是程序设计语言的一种技术,指将程序中数据类型进行参数化,它本质上是对程序的数据类型进行一次抽象,扩展语言的表达能力,同时支持更大粒度的代码复用。对于一些数据类型参数化的类和方法来说,它们往往具有更好的可读性、可复用性和可靠性。在设计集合类和它们的抽象操作时,往往需要将它们定义为与具体数据类型无关,在这种情况下,使用Generics就是非常适合的。举例来说,假如我们需要设计一个Stack类,有时需要元素为int类型的Stack,有时可能需要元素为Boolean或者Object类型的Stack。假如不使用Generics,我们通常需要定义不同的多个类,或者通过继续来实现。通过继续实现往往引起数据类型的转换问题,本文稍后对其进行分析。假如使用Generics技术,将Stack的元素类型进行参数化,那么Stack类的只需要实现一个版本,当需要某元素类型的Stack时,可以将类型作为参数来创建Stack对象。

二Generics与Java语言的发展

Generics技术很早就被提出来了,但是Generics的实现和推广并非一帆风顺。这是因为Generics对语言规范的修改较大,对于编译技术要求也较高,另外,90年代,所有语言都在积极扩展面向对象技术,并没有太多时间关注Generics。到目前为止,只少量语言支持Generics,例如Ada, C++, Eiffel等。在C++中, Template被用来现实Generics,同时C++还提供了标准模板库(STL,Standard Template Library)供开发人员使用,极大提高了编程的效率,因此Generics经常又被混称作Template。

但是,到目前JDK1.4.1为止,Java语言不支持Generics。目前,对于数据类型的参数化,Java不能够直接实现,而是通常采用继续方式间接实现。由于Java中,所有类都是Object的子类,因此假如需要支持多种类型时,我们可以将类型定义为Object,在使用的时候,可以通过父类和子类进行相互转化来实现。以上面的Stack为例,我们可以将Stack的类型定义为Object类型,那么所有的Java对象都可以放在这个Stack中,在存取对象时候,我们可以将对象强行转化成所需要的类型。但是,这种方式也会带来一些问题。

编译时,类型检查较为宽松,很多问题只有运行时候才能发现

很难实现复杂的类型参数化,缺少表达能力

增加使用者的麻烦,使用者需要对数据类型进行手工转换,并且进行类型检查

由于Java区分Object和基本类型,因此这种方式不轻易支持基本类型。

虽然当前Java语言规范并不支持Generics,但是一些组织和个人通过扩展Java语言的方式来实现Generics,其中比较闻名的有GJ,PolyJ和NextGen。Java语言标准的制定组织Java Community Process(JCP)也早已收到关于在Java语言中支持Generics的建议,并且一直在讨论是否在Java语言支持Generics。其中,一个比较重要的里程碑是Gilad Bracha博士等在2001年提出的提议。

Generics的实现对于Java语言本身、Class文件格式和JVM构造都有较大的影响;另外,Generics在C++中的实现也存在一些问题,这些问题都应该在Java中尽量避免,最后,还有一部分人认为Generics的引入会损失Java语言的简洁性。基于这些考虑,目前Java语言仍然不能够支持Generics,但是根据一些相关人员的预计,2003年年底推出JDK1.5很可能要支持Generics。

三Java中Generics的使用

在这一章中,我们将预览一些Java的Generics的用法,虽然这些技术并没有正式推出,但是根据Java Generics提议的草稿版本来看,这些用法都比较稳定且成熟,预计它们与正式规范不会有太大差别。另外,这些Generics程序通过专用的编译器,可以转换成兼容的Java类文件,并且可以在以前的JRE环境下运行,保证了向前兼容。本章节的部分内容来自Java语言Generics提议的草稿版本,另外一些例子也参考了GJ的运行结果。

1) Generics的定义:

为了使用Generics,首先必须定义支持Generics的类,接口或者方法,它与C++语言的模板的语法类似。<>用于包含参数化类型,参数化类型用Java标识符标识表示,通常使用大写字母,例如T,A,B等。

1.1) 类和接口定义

以下是最简单的Generics类定义,定义了一个参数化类型T1:

interface MyList<T1> {….. }

以下是支持多个参数化类型的接口:

interface MyList<T1,T2,T3> {….. }

Java支持带有限制的参数化类型,这意味着在构造该类对象的时候,实际类型必须满足限制条件。在下面的例子中,T1的类型必须实现类Comparable接口,T2类型必须为Component类的子类,否则将构造失败。这些限制检查工作通常在编译的时候就可以进行。

interface MyList<T1 implements Comparable, T2 extends Component> {}

复杂的定义可以带有限定的声明,甚至可以使用向前引用。

class Test<A implements ConvertibleTo<B>, B implements ConvertibleTo<A>{}

对于class的定义,基本与interface相同,此处不再详述。

1.2) 方法的定义

在方法中,通过定义参数化类型,可以提高方法的抽象级别,提高其可复用性。方法的参数化类型列表放在方法修饰符的后面,返回值的前面。在方法的参数中和方法体中,就可以直接使用参数化类型了。以下就是一个方法的例子。

public static <Elem> void swap(Elem[] a,int i,int j)

Elem temp=a[i];

a[i]=a[j]

a[j]=temp;

}

带限定的Generics方法定义例子。

public static <Elem implements Comparable> void swap(Elem[] a,int I,int j)

Elem temp=a[i];

a[i]=a[j]

a[j]=temp;

}

>

1.3) 实际例子

一但Java语言支持了Generics,Collection中的大部分类将用Generics方式重写,事实上,在一些支持Generics的Java扩展中,这些类已经被重写了。现在我们给出一个GJ中,使用Generics重写的Hashtable的定义。

public class Hashtable<K,V>

extends Dictionary<K,V>

implements Map<K,V>, java.lang.Cloneable, java.io.Serializable {

public V put(K key, V value) {……}

…….

}

与以前的Hashtable定义不同的是,它增加了两个用于表达Key和Value参数化类型(K,V),由于Dictionary和Map都将支持Generics,因此它们都使用Generics的表达方式。

2) Generics的使用:

2.1) 创建对象

Generics类在使用之前,必须按照定义进行初始化,设置参数化类型的实际类型,以下是构造Hashtable和Vector的一些例子。

Hashtable<Integer,String> ht1=new Hashtable<Integer,String>();

Hashtable<Integer,String> ht2=null;

Vector<String> v1=new Vector<String>();

Vector<Integer> v2=new Vector<Integer>();

2.2) Generics对象的类

在上例中,对于v1和v2对象来说,它们都是Vector类的对象,它们有相同的Class类型,换句话说,在运行时,以下表达式为真。虽然它们使用不同的参数类型创建,但是它们的Class类型是相同的。

Assert(V1.getClass().equals(v2.getClass()));

但是,假如我们定义一个 带有Vector<String>参数的方法,在调用该方法时,传入一个Vector<Integer>的对象,这将会导致编译失败。这是因为在编译时,这两个对象被当作不同的类型。

void method(Vector<String> v) {};

Vector<Integer> v2=new Vector<Integer>();

method(v2);//编译失败

2.3) 类型检查

在构造一个Generics对象时,编译器将首先检查参数化类型是否有效,例如是否满足限制条件等,确定所有参数化类型,然后编译器将在使用这些参数化类型的地方进行类型检查,假如符合定义,那么编译通过,否则将编译失败,报告类型检查错误。因此,通过这种方式,编译器可以检查出很多类型不匹配的错误,避免开发人员的错误,这也是Generics的重要优点之一。以下是类型检查的一些例子:

Vector<String> strvector=new Vector<String>();

Vector<Integer> intvector=new Vector<Integer>();

strvector.add(new String());//OK

strvector.add(new Object());//编译失败,需要String类

strvector.add(new Integer());//编译失败,需要String类

intvector.add(new Integer());//OK

intvector.add(new String());//编译失败,需要Integer类

2.4) 类的强制转换

对于Generics类的强制转化的原理,我们可以使用通用的转化规则进行操作。需要注重的一点是,同一个Generics类所定义的不同参数化类型的对象之间是不能进行转化的。例如Vector<Object>和Vector<String>之间就是不能转化的。另外,Object也不能够转化成Generics类型,但是Generics类可以转化成Object。

以下是一些转化的例子

Class Dictionary<A,B> extends Object{}

Class Hashtable<A,B> extends Dictionary<A,B> {}

Dictionary<String,Integer> d=new Dictionary<String,Integer>();

Hashtable<String,Integer> h=new Hashtable <String,Integer>();

Hashtable<Float,Double> hfd=new Hashtable<Float,Double>();

Object o=new Object();

1) d= (Dictionary<String,Integer>)h//编译成功,运行成功;它们具有父子类关系。

2) h=(Hashtable<String,Integer>)d;// 编译成功,运行失败;它们具有父子类关系。

3) h=(Hashtable<String,Integer>)o;//编译失败,Object不能转化成Generics类;

4) hfd=(Hashtable<Float,Double>)d;//编译失败;

四Generics的设计和实现

1) Java的Generics与C++的Template

由于Java的Generics设计在C++的Template之后,因此Java的Generics设计吸取Template的很多经验和教训,非凡是Generics避免了一些Template已知的一些问题。首先,与Template不同的是,Generics的声明是需要进行类型检查的,而Template不提供这一功能,这使得Generics的使用更加安全。另外,Java的Generics程序只需要编译一次,以后所有程序就可以复用这个类字节码,而Template的实现是为每一个使用Template变量编译成一个新类,这会引起一定的冗余代码。

2) Generics的实现方案

Generics Java目前有很多不同的实现,比较闻名的有GJ,PolyJ和NextGen。其中,GJ(Generic Java)是Gilad Bracha博士等设计和开发的支持Generics的Java编译器,它是较早,且较全面的Generics的解决方案,实际上,GJ是目前Java语言的一个扩展,主要对编译器进行了扩展,以支持带有Generics 的Java 程序。

GJ的工作原理本质上就是消除程序中的Generics语法,并将其转化成等价的无Generics的程序,这样编译的结果就是传统的类字节码,它们可以在传统的JVM中的运行,保证了向前兼容。编译的过程就是将所有参数化类型的变量都替换为Object,通过这种方式消除所有的参数化类型。由于Java中,所有对象都可以转换程Object的对象,因此通过这种方式可以消除参数化类型,但这种方法也有一个问题,那就是无法处理基本类型,因为Object与基本类型无法相互转换。在消除了参数化类型后,在适当的地方还需要加上一些类型检查和转化语句。

例如Stack类的Generics源程序可能如下表示:

public class Stack<A> {

public void push(A elem){…. }

public A pop(){…..}

}

经过GJ改写后,程序将变为如下:

public class Stack {

public void push(Object elem){….}

public Object pop(){….}

}

在使用Generics类的时候,我们首先设置参数化类型的值,在下例子我们传入Button类型。

Stack<Button> s = new Stack<Button>();

Button b = new Button("OK");

s.push(b);

b=s.pop();

GJ在编译以上代码时,在消除<>后,首先需要检查b是否能够转换成Button,假如b不能够转换成Button,编译器将报类型转化错误。同时, 在pop方法的返回值处,我们需要将Object类型显式转换为Button类型。以下就是GJ转化的结果;

Stack s = new Stack();

Button b = new Button("b");

s.push(b);

b=(Button)s.pop();

以上只描述了一些基本原理,在真正的实现中,GJ还需要处理继续,约束和类型转换等复杂问题。从实现的效果来看,GJ是非常成功地支持了Generics,并且提供了通过Generics改写的java.utils.collection包。更多的实现技术,请参考相关资料。

五 结论

Generics的出现将改变一些Java程序员的编程风格,以往所有不确定类型的对象都被定义为Object,需要使用对象时,通过强制类型转化获取,而Generics的出现将可用于治理一些类型抽象的类,让编译器来检查更多的类型匹配的问题,以减轻程序员的负担。但同时,Generics的引入,增加了Java程序的抽象程度,增加程序理解的难度。

相关资料:

Gilad Bracha, Norman Cohen,Christian Kemper etc, Adding Generics to the Java Programming Language Participant draft Specification, 2001,

http://java.sun.com/aboutJava/communityprocess/review/jsr014/

Paul Mingardi, Prepari8java.sun.com/developer/technicalArticles/releases/generics/">http://developer.java.sun.com/developer/technicalArticles/releases/generics/

Keith Turner, Catching more errors at compile time with Generic Java,IBM DeveloperWorks ,2001

http://www-106.ibm.com/developerworks/library/j-genjava.Html

Generic Java (GJ)

http://www.research.avayalabs.com/user/wadler/gj/

关于作者

欧阳辰,2001年毕业于北京大学计算机系,获硕士学位,SCJP(Sun Certificated Java Programmer),现任某公司软件工程师,长期从事java软件的研究和开发工作,已发表多篇Java相关的技术文章。联系方式yeekee@sina.com

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有