87

Csdn Java电子杂志第2期

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Csdn Java电子杂志第2期
Page 2: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

目录

目录 ..........................................................................................................................................i

读者反馈 ............................................................................................................................ - 1 -

热点新闻 ............................................................................................................................ - 4 -

新品发布 ............................................................................................................................ - 4 -

1.1. BEA WebLogic Server 9.0 测试版发布 .............................................................................- 4 -

1.2. Hibernate3 发布beta版本 支持EJB3 风格对象持久化.....................................................- 4 -

1.3. JBoss 4 应用服务器指南文档发布 ....................................................................................- 5 -

业界动态 ............................................................................................................................ - 6 -

1.1. IBM为开源献上免费午餐 无偿开放 500 件专利 ............................................................- 6 -

1.2. 诺基亚与沃达丰携手 拟制订手机Java行业标准.............................................................- 7 -

年度盘点 ............................................................................................................................ - 8 -

技术专题—J2SE 5.0 新特性精解 ................................................................................... - 11 -

升级到J2SE 5 平台的 5 大理由 ...................................................................................... - 11 -

1.1. 以前的应用程序可以直接在 5.0 上运行.........................................................................- 11 -

1.1.1. 改进的性能 ...........................................................................................................- 11 -

1.1.2. 监控和易管理性 ...................................................................................................- 11 -

1.1.3. 新的观感 ...............................................................................................................- 12 -

1.2. 速度更快 ...........................................................................................................................- 12 -

1.2.1. 缩短启动时间 .......................................................................................................- 12 -

1.2.2. 卓越的 64 位性能 .................................................................................................- 12 -

1.2.3. 性能改善 ...............................................................................................................- 13 -

1.3. 缩短开发时间 ...................................................................................................................- 13 -

1.3.1. 减少开发人员编码数量 .......................................................................................- 13 -

1.3.2. 增强的错误检验存储 ...........................................................................................- 14 -

1.4. 适用于任务关键型系统 ...................................................................................................- 14 -

1.4.1. 可伸缩性 ...............................................................................................................- 14 -

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期 i

Page 3: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

1.4.2. 质量 .......................................................................................................................- 14 -

1.4.3. 部署 .......................................................................................................................- 15 -

1.5. 您选择了一家优秀的公司 ...............................................................................................- 15 -

1.5.1. 有关J2SE 5.0 的图书 ............................................................................................- 15 -

1.6. 结束语 ...............................................................................................................................- 16 -

Tiger核心库简介 .............................................................................................................. - 17 -

1.1. 事务的属性 .......................................................................................................................- 17 -

1.1.1. 访问环境变量 .......................................................................................................- 17 -

1.1.2. 访问子进程 ...........................................................................................................- 18 -

1.2. 并发集合 ...........................................................................................................................- 19 -

1.2.1. Queue接口 .............................................................................................................- 19 -

1.2.2. ConcurrentMap实现 ..............................................................................................- 23 -

1.2.3. CopyOnWriteArrayList 和 CopyOnWriteArraySet.............................................- 24 -

1.3. Formatter ...........................................................................................................................- 25 -

1.3.1. PrintStream支持 ....................................................................................................- 27 -

1.3.2. String支持..............................................................................................................- 28 -

1.3.3. 格式化任意对象 ...................................................................................................- 28 -

1.4. 从XML文件中装载属性...................................................................................................- 30 -

1.4.1. XML属性文件 ......................................................................................................- 30 -

1.4.2. 存取XML属性文件...............................................................................................- 31 -

1.5. 其他核心库的增强 ...........................................................................................................- 32 -

1.5.1. Scanner ..................................................................................................................- 32 -

1.5.2. JavaBeans组件体系结构.......................................................................................- 32 -

1.5.3. Math包 ...................................................................................................................- 33 -

1.5.4. 网络 .......................................................................................................................- 33 -

1.5.5. 安全性 ...................................................................................................................- 33 -

1.5.6. 国际化 ...................................................................................................................- 33 -

1.5.7. 序列化 ...................................................................................................................- 34 -

1.6. 结束语 ...............................................................................................................................- 34 -

JAVA泛型QUIK START .................................................................................................. - 35 -

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期 ii

Page 4: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

1.1. JAVA泛型 ..........................................................................................................................- 35 -

1.1.1. 泛型通配符 ...........................................................................................................- 36 -

1.1.2. 编写泛型类要注意: ...........................................................................................- 36 -

1.2. 泛型与数据类型转换 .......................................................................................................- 37 -

1.2.1. 消除类型转换 .......................................................................................................- 37 -

1.2.2. 自动解包装与自动包装的功能............................................................................- 38 -

1.2.3. 限制泛型中类型参数的范围................................................................................- 38 -

1.2.4. 多态方法 ...............................................................................................................- 39 -

J2SE 5.0 专题 之 用户接口 ........................................................................................... - 40 -

1.1. 引子 ...................................................................................................................................- 40 -

1.2. 综述 ...................................................................................................................................- 40 -

1.3. 国际化 ...............................................................................................................................- 41 -

1.4. Java声音技术 ....................................................................................................................- 42 -

1.5. Java 2D技术及图像 I/O ...................................................................................................- 43 -

1.5.1. AWT与Swing ........................................................................................................- 43 -

1.6. 结束语 ...............................................................................................................................- 46 -

J2SE 5.0 专题 之 语言特性 ........................................................................................... - 47 -

1.1. 背景 ...................................................................................................................................- 47 -

1.2. 准备工作 ...........................................................................................................................- 47 -

1.3. 概述 ...................................................................................................................................- 48 -

1.4. 泛型 ...................................................................................................................................- 49 -

1.5. 增强的for循环...................................................................................................................- 51 -

1.6. 自动装箱/自动拆箱 ..........................................................................................................- 53 -

1.7. 类型安全的枚举 ...............................................................................................................- 54 -

1.8. 可变长度参数 ...................................................................................................................- 56 -

1.9. 静态引入 ...........................................................................................................................- 56 -

1.10. 元数据(注解) ...........................................................................................................- 57 -

1.11. C风格格式化输出.............................................................................................................- 59 -

1.12. 结语 ...............................................................................................................................- 60 -

J2SE 5.0 新特性 之 线程 ............................................................................................... - 61 -

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期 iii

Page 5: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

1.1. 进程、线程与线程池 .......................................................................................................- 61 -

1.2. Java的线程概述 ................................................................................................................- 61 -

1.3. Collection部分的扩容.......................................................................................................- 62 -

1.3.1. Queue 接口 ...........................................................................................................- 62 -

1.3.2. List、Set、Map接口.............................................................................................- 65 -

1.4. 线程池 ...............................................................................................................................- 66 -

1.4.1. FutureResult 实例.................................................................................................- 68 -

1.4.2. 使用 FutureResult 来改善高速缓存 ...................................................................- 69 -

1.5. 结束语 ...............................................................................................................................- 70 -

MetaData Programme ....................................................................................................... - 71 -

1.1. 什么是元数据编程 ...........................................................................................................- 71 -

1.2. Annotation的意义和简单例子 .........................................................................................- 71 -

1.3. Annotation的class文件格式..............................................................................................- 72 -

1.4. 为什么需要Annotation .....................................................................................................- 76 -

1.5. 再议Annotation .................................................................................................................- 77 -

1.6. 元数据编程的应用: .......................................................................................................- 78 -

1.7. 结束语: ...........................................................................................................................- 81 -

思考题 .............................................................................................................................. - 82 -

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期 iv

Page 6: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

读者反馈

Java 电子杂志第一期发布已经有一个月的时间了,这期间我们收到了好多热心读者的意见和建

议,下面是一些读者的反馈:

首先,我要感谢 CSDN 的各位编辑能在百忙之中,抽出时间为我们献上了这么好的杂志。

内容十分的不错,图片很漂亮。但是,还是不免有不少问题:

1. 文章中的错误很多,大多是拼写的错误,要么丢字,要么多字,让人感觉起来就想

是应付差事似的,希望能在发稿前多检查两边。

2. 排版不是太好,感觉有些插图摆放的不是太好,页面也被切的乱七八糟,代码片段

几乎没有一点格式可言。程序员本应该很重视的代码规范在这里面完全无法体现出

来,没有了缩进的代码开起来实在是很不舒服,也不美观。

3. 一些建议:希望能在没篇文章后做一个标记之类的结束符,说明文章已经结束,要

不然感觉非常的生硬;

4. 另外,个人不太喜欢这种左右分栏,看起来不舒服,还是不分栏比较习惯;文章中

的连接,不知道为什么没有办法直接点击,我看过的一些书都是按下 ctrl+鼠标左键

点击连接的,不知道你们是怎么实现的。

提的问题比较多,还请各位编辑见谅。总体上说,这期杂志还是不错的,能看出来我们

的编辑同志还是花了不少心思的。感谢各位编辑带给我们的精彩内容,希望下一期杂志

能更加精彩。

作为一个初学者角度来讲,作为创号刊应该:

1. 注重阐述 Java 的发展历史、趋向,让人真正了解 Java

2. 应用方面(吸引更多人来学习)

3. 应该对专业术语进行讲解,为以后更好的学习提供基础

能不能出一些针对 java web 应用测试的教程。比如如何用软件做 java 内存泄露检查。如

何发现我们程序的漏洞,程序运行效率的瓶颈。大家把相关的经验都拿出来,我想对大

家开发工作会有很大的帮助。

创刊号令我惊讶,总体感觉相当不错,出乎我的意料之外。页与页之间的插图有些选得

很棒。希望以后能保持水准,坚持下去。把 java 杂志做成 好的电子杂志。 2005 年 1 月 1 期 总第 2 期

- 1 -

Page 7: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

希望有深入一些的文章.其中有很多只不过是从网上看到的英文再译一下.能不能写一些

完整的项目开发经验之类的.

不错,很值得期待的一本杂志。这可能是国内首创的专门针对 JAVA 技术的杂志,如果可

能发行的话,我一定订阅。能否在杂志中增加一些连载?

设个小专栏,发表大家的简短学习心得.

很高兴能有这样一份杂志出来,我很喜欢。我有一个小小的建议:每页分栏我感觉很不

方便,看完一栏还得向上滚动才能开始看,没有一页就是一栏看起来舒服。期待着下一

期的到来

我在阅读电子杂志,感觉排版对阅读不是很方便,看起来比较累,我不知道这本杂志除

了电子版之外,还有其他方式,如果没有,我觉得完成可以充分利用技术将各个信息整

理成一个目录,方便查阅。在内容上,我建议可以将一个高手的成长经历总结出来让其

他程序员分享。JAVA 现在应用越来越普通,但随着各种技术和规范层出不穷,给程序员

选择构架时,不知所措,所以建议多增加这方面的内容。

我今天下载了 CSDN Java 电子杂志创刊号,觉得内容不错!可是我觉得你们是否可以少

放点图片,多些实质的内容这样可以缩减文件大小,而且让读者看着舒服!我觉得你们

可以在每期都出一道 java 程序设计的题目,然后在后一次公布答案!这样读者可以实践

自己的所学。谢谢!希望你们的杂志越办越好!

很高兴看到了第一期 java 电子杂志,看了一些杂志的开头,感觉杂志办的很专业,内容

也很不错,希望这份 java 的电子杂志能继续办下去,并且办的越来越好。下面就对杂志

提几点自己小的小的看法:看了一下电子杂志,感觉排版做的有点不太好,我指的是,

《struts 佳实践》这篇文章,对于文章中出现的程序代码,希望排版的时候能把代码整

理的让人比较容易看懂,就是不要象杂志中出现的,本来可以写在一行的代码因为,但

是在排版的时候,被排到了好几行,让人不是很容易就看懂代码。还有就是在同一篇文

章里面有一段 jsp 代码也是排版排的不太好,感觉看起来不太容易。另外就是在杂志的正

文中出现的图片,图片倒是很好看,但是感觉有的地方图片太大了,有点喧宾夺主的感

觉。上面说了那么多主要还是一个排版版面的问题,就是希望出现代码的地方能够排版

的时候能排的整齐一些,让读者可以容易看一些。还有就是关于杂志的内容,能不能就

是以后的文章每期能以专题的形式来介绍一些 java 开源的比较流行的 framework 和技术。

比如象 spring framework,struts,webworks,hibnate,eclipse,jboss,ant,junit 等比较热

门的技术。然后杂志的内容呢希望多能偏中一些设计的思想的层面的介绍,比如现在热

2005 年 1 月 1 期 总第 2 期

- 2 -

Page 8: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

门的 design pattern,ood ,oop 的介绍。好了现在能想起来就上面那么多了,祝 java 电子

杂志能越办越好。各位杂志的编辑 happy new year!

刚下载了一期,觉得不错有两点觉得不方便:

1. pdf 文档分栏,阅读十分不便,建议不要分栏

2. 能不能附源码

对于以上意见我们在这里就不一一解答了,我们在做杂志的时候一定会认真接受读者提出的意

见的, 在此对所有关心,关注,支持 java 电子杂志的读者以及网友表示感谢,希望大家能够一如既往

的支持电子杂志,也希望大家能够积极的反馈意见,踊跃的投稿,电子杂志的发展离不开大家的参

与!CSDN 关于 java 电子杂志创刊号的调查的结果已经统计出来了,我放到 Java 电子杂志官方 Blog

的里了,大家可以到这个地址查看: http://blog.csdn.net/emag_java/archive/2005/01/28/271486.aspx,也

欢迎大家到电子杂志的 Blog 已及 emag 站点上了解杂志的 新情况,发表您的意见和建议。

《CSDN 社区电子杂志——Java 杂志》编辑部

2005 年 1 月

2005 年 1 月 1 期 总第 2 期

- 3 -

Page 9: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

热点新闻

新品发布

1.1. BEA WebLogic Server 9.0 测试版发布

BEA WebLogic Server 9.0 测试版以于日前提供免费下载。WebLogic 一直是全球 j2ee 领域强大

的只得信赖的应用程序服务器,这次的 9.0 测试版也不例外,甚至更强。这次的这个测试版是基于

j2ee 1.4 标准,优点包括:扩展了新的管理员控制台,通过框架和脚本诊断工具使管理员能够及时

的发现和解决出现的问题。使 Web 服务在应用程序中支持异步的消息传递机制。等等。

测试版下载地址: http://commerce.bea.com/index.jsp

1.2. Hibernate3 发布 beta 版本 支持 EJB3 风格对象持

久化

12月 20日Hibernate框架发布了 3.0版本的第一个 beta版本。据作者Gavin King表示,Hibernate

3.0 将于明年(2005 年)第一季度正式发布。Hibernate 是一个基于 POJO(Plain-Old Java Object,

普通 Java 对象)的 O/R mapping 框架,也是目前 J2EE 社群 流行的对象持久化工具。正在制订中

的 EJB3 规范就大量借鉴了 Hibernate 的经验。

在 beta1 版本中,Hibernate3 并未提供对 J2SE 5.0 的支持,这主要是因为出于兼容性的考虑。

Gavin King 表示,将在 2005 年逐步引入 J2SE 5.0 提供的新语言特性,例如泛型、元数据标注等。

Hibernate3 主要的新特性包括:

实现了 EJB3 风格的持久化操作。在原有的 saveOrUpdate()和 saveOrUpdateCopy()两个方法之

外,又提供了 EJB3 风格的 create()和 merge()两个操作。

提供更强的映射灵活性。允许将一个类映射到多张表,允许混合使用“每个继承体系一张表”

和“每个子类一张表”的映射策略,等等。

支持存储过程和手写 SQL,并且可以用手写 SQL 替代 Hibernate 自动生成的 SQL 语句。

基于 AST(抽象语法树)的 HQL 解析。

字段级的懒式获取。每个属性都可以在映射描述符中声明“lazy=true”,这样声明的属性会到

2005 年 1 月 1 期 总第 2 期

- 4 -

Page 10: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

真正使用时才从数据库加载。不过,实现这项功能需要首先在编译期对字节码进行增强。

详细的改进列表请看:http://www.hibernate.org/200.html

1.3. JBoss 4 应用服务器指南文档发布

近日,开源组织 JBoss 发布了 J2EE 应用服务器 JBoss 4.0 的应用指南文档。JBoss 用户可以在

以下地址下载这份文档:

http://docs.jboss.org/jbossas/jboss4guide/r1/html/

目前这份文档尚未 终正式定稿,可以在 JBoss 文档论坛提出意见和建议。

JBoss 4.0 是业界领先的开源 J2EE 应用服务器,也是唯一一个全面基于 AOP 思想设计的应用

服务器产品,因此受到 Java 开发者的广泛关注。

2005 年 1 月 1 期 总第 2 期

- 5 -

Page 11: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

业界动态

1.1. IBM 为开源献上免费午餐 无偿开放 500 件专利

IBM 已经决定让开放源代码开发人员放心地使用其 500 件软件专利,这也是 IBM 为鼓励协作

编程思想而采取的 新步骤。

对于象 IBM 这样在研发上投入巨资,获得专利,然后向其它厂商许可专利的公司而言,这是很小

但重要的一步。但是,在 IBM 在美国取得的 10000 件软件专利中,绝大多数仍然不是“免费午餐”。

Linux 厂商 RedHat 反对软件专利,并免费开放其很少的软件专利。Novell 则表示将利用它的

专利反击针对开放源代码软件的法律行动。IBM 是 Linux 的热心支持者,有数百名软件编程人员

在从事 Linux 的开发工作。它在去年 8 月份表示,决不会利用其专利攻击 Linux。

一些人担心专利可能会伤及 Linux.惠普的一名官员在 2002 年警告称,他预计微软将利用专利

攻击 Linux 和其它开放源代码项目,微软已经开始更多地关注其专利许可计划了。一家开展 Linux

专利保险业务的保险公司称,Linux 侵犯了 283 项专利,其中 60 项为 IBM 所拥有。

专利软件对于 Linux 和其它开放源代码项目而言是破坏性因素。Linux 和其它开放源代码软件

使用的 GPL 许可协议禁止使用专利软件,在开放源代码软件中发现专利软件代码至少将迫使编程

人员编写替代性的代码,客户也需要根据新版本对自己的系统进行必要的改变。

在计算技术领域,IBM 是当之无愧的专利王,它在全球和美国分别获得了 40000 和 25000 件

专利。在 2004 年,美国专利和商标署授予了 IBM3248 项专利,这也使得它连续 12 年成为获得专

利 多的厂商。

IBM 表示,它免费开放的专利覆盖了许多技术,其中包括动态连接操作系统进程、导出文件

的协议、数据库和操作系统之间的互操作性、语言处理、用户界面、互联网。

IBM 将本周二的这一举措称作是在这一方向上迈出的第一步。IBM 的一名代表在周一表示,

未来他们将免费开放更多的软件专利,供开放源代码软件使用。IBM 还计划开放被应用在开放标

准中的专利,这将使这些标准能够更方便地被应用在开放源代码和专有软件中。

IBM 新成立的知识产权部门的负责人柯利在一份声明中表示,他希望其它公司能够效仿 IBM。

他说,通过今天的举措,我们将越来越多地利用专利鼓励和保护全球性的创新,利用开放标准来

鼓励和保护互操作性,我们建议其它厂商也采取类似的行动。我们将继续与美国专利和商标署以

2005 年 1 月 1 期 总第 2 期

- 6 -

Page 12: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

及其它机构合作,确保美国的专利系统能够继续向前发展,解决创新经济的挑战。

1.2. 诺基亚与沃达丰携手 拟制订手机 Java 行业标准

在芬兰埃斯波(Espoo ),诺基亚和沃达丰(VODAFONE)联合宣布了移动服务架构计划,

旨在为移动产业和移动用户带来更多的利益。该计划的目的在于通过定义下一代基于开放标准的

移动 JAVA 服务架构规范,简化移动 JAVA 标准。它将代表并服务于整个移动价值链,包括其他领

先的移动设备制造商、移动运营商和 IT 公司。

该计划将在 Java Community ProcessSM(JCPSM )的范围内制订和行使其标准。八月初,

J2ME?执行委员会批准了该项目提交的第一个 JAVA 标准要求(JSRs)。为了保持 JAVA API 服务

架构的一致性,JSRs248 和 249 将包含一些新的 JSRs 元素,并明确一些现有的规范,而不会引入

任何新的 API 规范。

该计划将进一步规范 JSRs 的许可条款,以实现开放、公平和可预见性。因此,诺基亚、沃达

丰将与 SUN 联合制定该计划的许可框架,JSR248 和 249 的技术兼容工具包(TCKs)的开发和许

可将由 SUN 负责。

该计划得到了移动行业巨头的广泛支持,包括 Orange、西门子、索尼爱立信、SUN 和 T-Mobile

International. 这些公司都有意加入诺基亚和沃达丰,成为 JAVA 服务架构规范制定专家组的成员。

通过采用此标准,开发商可以开发出能够在 JAVA 手机上轻松互发的软件,这将为移动用户提供丰

富的 JAVA 应用选择。

移动 JAVA 服务架构的管理框架还包括提高安全性的内容。通过实现软件到移动终端的空中发

送和管理,该管理框架将支持先进的远程平台以及面向企业和服务提供商的应用管理。这将降低

移动软件维护的费用。此外,它还为商业用户和消费者提供了在移动终端上获取 新应用和服务

的简易操作。采用该 JAVA 标准的应用平台将为运营商、企业、服务提供商和设备制造商带来新的

商机。

为了制定平台的要求和容量,该计划的目标和职责范围将与一些国际组织保持协调一致,如

OSGI 联盟、开放移动联盟(OMA )、开放移动终端平台(OMTP)和互连网联盟(W3C )。该标

准还将提供 MIDP 环境下的向后平滑兼容。

2005 年 1 月 1 期 总第 2 期

- 7 -

Page 13: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

年度盘点

——2004 开发技术年度综述之 Java 世界 (天极网)

2004 年 Java 世界发生了巨大变化,主要方面是诞生和发展了几个新名词:IOC 、AOP、SOA、

MDA,当然,除了这些都是决定 Java 技术未来走向的革命性发展以外,还有很多细部和微观的发

展,我们先来谈谈这些重要的细节技术发展。

首先, Java 的根基 JDK 已经跨越到了 J2SE 1.5,或者称为 J2SE 5.0,不过,我更喜欢称它为

1.5,延续性是我们应用程序员更关心的,当然革命性是那些新技术发明者更陶醉的,所以他们曾

经取名 J2SE 2.0,现在,为了表示这种跨越,而且跨越是非常巨大,一下子达到了 J2SE5.0,非常

夸张,不过老外在技术上确实经常极端,"偏执者才能生存"我想是其行动主义文化的主要基础。

J2SE1.5 吸引眼球的我认为主要是 annotate 特性,该特性可以大大降低程序员的编程量,它

和 Java Relfect 机制结合,可以编制出相当灵活的软件系统或框架。

从 J2SE1.5 看出,SUN 正在试图简化 Java,但是也许这一简化动作来得迟了点。

同样,珊珊来迟的是 J2EE Web 层的一个标准 JSF,JSF 使得程序员在开发 J2EE 的 Web 程序

时,能够如同开发 SWING 那样实现可视化开发,提高了开发效率,但是必须有高级开发工具支持。

由于好东西来得太迟,在一份 近调查中显示,开源项目 Struts 已经在 J2EE 实际 Web 层开发

中占据主导地位,Struts 在 2004 年是达到成熟顶点,它的主要特点是将 Form 表单对象化,同时提

供简单的 XML 标签替代以前 Jsp 中的 Java 语言,这样在 Jsp 中彻底消灭了 Java 代码,除了 Struts,

Tapestry 这种彻底抛弃 Jsp 的开源 Web 框架产品也值得一提,它和 WebWork 一起成为一颗引人注

目的星星。

2004 年发生了太多新的激动人心的事情,其中 引人注目的是 AOP 和 SOA 的迅速发展,Java

世界的发展一直以来是分两条路线,第一条是工业路线,以 SUN、IBM、BEA 和 Oracle 等工业巨

头为代表的业界标准,当工业巨头制定出 EJB 2.0 以后,他们认为在 JavaBeans 技术框架内 EJB 已

经走到极限,基本无需太多发展,因此,他们将关注目光投向软件系统服务功能上,当一个个 EJB

组件提供了可重用的功能后,如何向不同客户输出这些强大的、可重用的功能呢?是反复安装拷

贝这些组件功能?还是以一种服务的方式适时提供全面的租用?SOA 面向服务架构因此提出来,

2005 年 1 月 1 期 总第 2 期

- 8 -

Page 14: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

并且得到工业界巨头的大量投入和发展,同时,他们还在关注流程的可定制化和可视化开发,通

过强大的开发平台,可以很轻松地完成工作流的业务定制和修改。

当业界巨头关注重点从 JavaBeans 结构转移时,一种新的设计思想和编程风格 AOP(面向方

面编程)诞生了,面向方面编程是 OOP 面向对象编程的延续,AOP 于 2003 年底已经在国外热起

来,到了 2004 年应该算是全面开花,各种 AOP 产品如 JBoss 4.0、Spring、Aspectwerkz 等迅速诞

生和发展,带给程序员全新的概念和理念冲击,AOP 编程方法可以应用在软件开发的各个领域,

当你发现某个功能具有普遍性和通用性以后,那么这个功能也许可以使用 AOP 实现,这样你的代

码优雅,同时可维护性和拓展性大大增强。

Ioc 反转控制应该说有着 AOP 类似解耦思路,通过 Ioc 模式或容器,可以比较彻底分离

JavaBeans 之间的调用和被调用关系,达到真正地面向接口编程,是 Ioc 使的面向接口编程变得更

具有意义而且重要,Ioc 的产品如纯洁的 PicoContainer 以及 Spring 等等。

开源和业界标准的分歧不仅仅体现在 JavaBeans 体系发展上,还体现在对象持久化方式上,在

2004 年有三种流行的对象持久化在被广泛使用:EJB 的 CMP、Hibernate 和 JDO,这三者之间的区

别和联系也是很多程序员津津乐道的,特别是 Hibernate 的创始人 Gavin King 不断对 JDO 的质疑,

使的 Hibernate 名气大升,也使的 JDO 一直未能够进入 EJB 的持久化标准,Spring+Hibernate 架构

以及使用基于 JBoss 的 EJB 架构成为低成本高质量 J2EE 流行架构。

这种"混乱"的局面下,EJB 3.0 标准 Preview 方案被 2004 年 Java ONE 大会首次提出,EJB 3.0

试图取悦那些轻量方案的追随者,同时又能兼顾过去 EJB 标准的系列发展,JBoss 迅速地提供了对

EJB3.0 支持包,其中我们看到了 Gavin King 这位斗士的身影。

同时,Hibernate 向何处去是我们应用系统程序员关心的,Struts 2.0 正在向 JSF 标准靠拢和迁

移,Hibernate 是否能够一直孤军作战?目前一项旨在统一 J2EE 持久层技术的标准方案正在启动

中,我们期望简单统一的持久层方案出台,减少程序员选择迁移之苦。

2004 年又是 Java 世界吵闹的一年,有的程序员说:现在学 java 却越学越糊涂,看了很多东西,

不但大脑里没有头绪,反而有种走火入魔的感觉,因为各种技术派别都在争夺话语权,话语权的

争夺意味着商机的诞生,JBoss 和 Spring 的开发团体先后转为商业公司是一个证明。对于应用者来

说,没有永远的解决方案,只有合适的工具,Java 世界提供给你的是工具箱,而非一种选择,需

求是选择工具的唯一原因。

让我们视野从 J2EE 思想和技术之争转移到需求分析领域的领域,在这个领域一直也存在两种

意见:面向数据表分析还是面向模型分析?也就是说,建立一个新的系统,是先分析设计数据表

2005 年 1 月 1 期 总第 2 期

- 9 -

Page 15: Csdn Java电子杂志第2期

《CSDN社区电子杂志——Java杂志》

http://emag.csdn.net

还是模型,前者是以前数据库系统经常采取的方式,但是这一传统的方式目前也受到 OO 对象的

冲击,MDA(面向模型分析)在 2004 年成熟开花,各种 Java 成品也如雨后春笋一样遍地开花,

MDA 思想提出了极端观点:将来只有建模专家(其它程序员都变成蓝领工人了),只要建模专家

通过 UML 设计出一个系统的模型,通过特定的 Java MDA 工具就可自动生成相应的代码,好像真

的不需要普通程序员了。

结束语

总之,2004 年的 Java 技术带给我们更多的是激动和压力,新技术发展以月计算,作为程序员

的我们如何跟上这些新技术发展,同时能够选择正确的技术引入我们自己的应用系统,这些才是

我们的真正挑战。

2005 年 1 月 1 期 总第 2 期

- 10 -

Page 16: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

技术专题—J2SE 5.0 新特性精解

升级到 J2SE 5 平台的 5 大理由

作者:Calvin Austin,2005 年 1 月 4 日

本文给出了升级到 Java 2 Platform, Standard Edition (J2SE platform) 5.0 的 5 大理由。每个理由

都通过大量数据和参考资料来证实升级到 5.0 版本将大大降低开发与运行成本。

J2SE 5.0(代号 Project Tiger)于 2004 年 9 月底发布。它是 Java 平台多年来的重大更新之一。

Java 社区为其新增功能欢呼不已,然而这些新增功能还无法全面地描述此版本的特色。与以往任

何版本相比,这一平台更能满足高水平的测试要求,更适合于任务关键型产品的开发。

J2SE 5.0 中的改进非常之多,本文选出 重要的前 5 条理由实非易事。我的选择如下:

1.1. 以前的应用程序可以直接在 5.0 上运行

“为了确保向后高度兼容,兼容测试次数是 1.4 版本中的两倍...”

——Calvin Austin Sun Microsystems

现有的应用程序无需重新设计或重新编译就可以直接在 J2SE 5.0 中使用。不仅现有应用程序

的投资得到了保护,而且应用程序实际上已经使用新的 Java 运行时,并从中获益。

为了确保向后高度兼容,兼容测试次数是 1.4 版本中的两倍。这些兼容测试只是 Java 测试套

件的一部分,该测试套件包含近 20 万个测试,其中接受测试的有许多著名的 Java 产品。

既然应用程序可以未加修改地运行,那么 J2SE 5.0 能为您提供什么呢?

1.1.1. 改进的性能

在 J2SE 5.0 中,客户机和服务器应用程序的性能均得到重大的改进,由于性能改进非常明显,

我将它列为第 2 条理由,后面将详述。

1.1.2. 监控和易管理性

J2SE 5.0 引入了先进的监控和易管理性框架,并将其内建在 Java 平台的虚拟机上(Java 虚拟

2005 年 1 月 1 期 总第 2 期

- 11 -

Page 17: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

机或 JVM)。您可以使用带有行业标准 JMX 和 SNMP 协议的现有管理控制台来监控 JVM,甚至可

以检测低内存情况。JDK 发行版提供一个名为 Jconsole 的演示。您可以借助它来评估监控 JVM 所

带来的好处,从而了解如何拓展自己的可用性指标。

1.1.3. 新的观感

Java 平台已包含了一个插入式观感(Look-and-Feel)框架。新增的海洋风格的观感允许跨平

台的应用程序能够在海洋和本机操作系统的观感之间进行切换,而无需重建或重新编译。

1.2. 速度更快

从下图可以看出,众多因素让 J2SE 5.0 的系统速度得到了显著提高。

1.2.1. 缩短启动时间

如果您 近几年一直没有启动过 Java 桌面应用程序,您可能会有惊喜的发现。引入了类数据

共享(结合其他流线化选项)后,一些应用程序的启动时间缩短了近 30%。

1.2.2. 卓越的 64 位性能

J2SE 5.0的64位 JVM可以为AMD64/Opteron CPU和Suse Linux Enterprise Edition 8.0 SLES 提

供记录结果。另外,32 位 JRE 版本可以在同一 64 位操作系统下与现有的 32 位 Web 浏览器同时运

行。

2005 年 1 月 1 期 总第 2 期

- 12 -

Page 18: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.2.3. 性能改善

现在,JVM 可在服务器级别的机器上自我配置和优化。服务器级别的机器是指具有两个或更

多 CPU,内存至少 2GB 的机器。基于服务器的性能改善消除了那些长时间运行的应用程序所需的

内存和优化类。其结果是不用改动一行代码或提供任何运行时选项,就可以使应用序服务器基准

程序提高 80%的性能!

1.3. 缩短开发时间

集成开发环境(IDE)通过使用自动生成和向导来完成常规任务,从而减轻了开发人员的负担。

J2SE 5.0 新增的语言功能进一步流线化了开发过程,无论您是使用 IDE 还是在文本编辑器手工编

码。

1.3.1. 减少开发人员编码数量

Java 语言的许多改进都减少了开发人员必须编码的数量。下图针对与 J2SE 1.4.2 版本的比较,

对这种减少进行了量化。举一个现实中的例子来说,一个开放源码应用服务器使用了超过 2,000 个

迭代器。通过用新的循环改进功能来取代,编码量大大减少多达 40,000 个字符。对于开发人员来

说,这相当于节省了两个小时的手工编码工作量(按每秒 5 个字符计算)。

图 2:减少的编码工作量(编码行数更少)

J2SE 5.0 平台增加了 metadata,通过使用工具生成样板文件代码和配置信息,大大短少了开发

2005 年 1 月 1 期 总第 2 期

- 13 -

Page 19: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

时间。在 JSR 181 定义的 SOA 和 Web 服务的情况下,开发人员通过自动生成公共代码,有望减少

50%的编码工作量。[注意:上图给出的例子表明,元数据 JAX-RPC 从 209 字符减少至 95 字符,

使用 generics 的循环改进从 139 字符减少至 100 字符,不使用 generic 的循环改进从 121 字符减至

90 字符,自动装箱从 69 字符减至 56 字符]。

元数据将减少编写部署描述文件的工作量。曾有人建议 EJB 3.0 默认由应用程序自身生成配置

信息,从而无需部署描述文件。

1.3.2. 增强的错误检验存储

用 google 搜索“ClassCastException”和“help”关键词将得到 40,000 条搜索结果。Sun 论坛

上的专业搜索报告了 3,444 个开发者案例,他们都是请求帮助跟踪由于类转换异常问题而引起的无

法解释的运行时故障。

在 3,444 个案例中,有 470 个涉及到 pre-generics vector 类。从投资回报的角度来看,在业务

高峰期间任何运行时错误都可能损失公司站点 10%的定单收入(若收入为 30 亿 GBP,则损失 3 亿

GBP)。这还未计入人力和商誉等其他因素。

随着编译时类型安全 generic 类型引入到 Java 平台,通过提醒开发人员在开发期间内是否存在

类型匹配问题,将会降低运行时错误的风险。在论坛的 470 个案例中,Vector 和其他 JDK 中的关

键类库都已使用 J2SE 5.0 中的 generics 进行更新,从而避免重复出现那些无法解释的运行时问题。

1.4. 适用于任务关键型系统

5.0 版本提供了增强的可伸缩性、质量与部署支持。

1.4.1. 可伸缩性

Java 平台的设计令客户机和服务器均具有可伸缩性。5.0 版本引入了一个功能强大的并发类库,

令多线程编程比从前更简单同时功能也更强大。Java 平台还支持 64 位以及多核心和超线程芯片技

术。64 位 JVM 允许应用程序使用 4 GB 以上的堆空间,还允许与其他 64 位应用程序集成。

1.4.2. 质量

J2SE 5.0 平台已经在一组广泛的系统和操作系统变体上进行了深入的测试。测试中使用了许多

2005 年 1 月 1 期 总第 2 期

- 14 -

Page 20: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

流行的应用服务器和客户应用程序。 后一项测试标准——平均故障压力测试时间——以没有任

何故障而完满结束。

1.4.3. 部署

在 J2SE 5.0 之前,很难确定 JVM 产品的事务吞吐量。新的分析工具和自动调优功能提供了更

好的产品合理精简信息和配置。当应用程序投入使用后,可以看到通过这些功能缩减的范围令人

惊叹。

如前在第 1 条理由中重点强调的,为了完成部署过程,JVM 现在能够使用行业标准工具执行

完整的远程监控。这就使得系统管理员无论在集群中还是在传统垂直伸缩的环境中,都可成功地

管理 JVM。所部署应用程序的健康状态和可利用性都能得到监控。

除了上面升级的 4 条技术理由之外,还有 后一条至关重要的理由。

1.5. 您选择了一家优秀的公司

J2SE 5.0 的改进归功于 Java 社区。J2SE 5.0 专家组包括以下 Java 领域的公司:Apache,Apple,

BEA Systems,Borland,Cisco Systems,Fujitsu,Hewlett-Packard,IBM,Macromedia,Nokia,Oracle,

SAP,SAS Institute,SavaJe Technologies,Sun Microsystems,John Zukowski,Osvaldo Doederlein

和 Juergen Kreileder。

共有 16 个由著名 Java 领域专家组成的工作组。它们组织了全世界 160 名专家协同工作, 终

形成了 J2SE 5.0 Java 规范。该规范覆盖了从语言更新到并发类库再到类压缩文件 API。有关更多

的技术细节,请参阅 J2SE 5.0 in a Nutshell。也可参考本页侧栏给出的参考书,诸如此类的参考书

正在不断增加。

1.5.1. 有关 J2SE 5.0 的图书

Java 5.0 Tiger: A Developer's Notebook. David Flanagan, Brett McLaughlin. O'Reilly & Associates,

2004.

Java 2 v5.0 (Tiger) New Features. Herbert Schildt. McGraw-Hill Osborne Media, 2004.

Beginning Java 2, JDK 5 Edition. Ivor Horton. Wiley, 2004.

Core Java 2, Volume I, II (7th Ed.). Cay Horstmann, Gary Cornell. Pearson Prentice Hall, 2005.

2005 年 1 月 1 期 总第 2 期

- 15 -

Page 21: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

Just Java 2 (6th Ed.). Peter van der Linden. Prentice Hall, 2004.

An Introduction to Programming and Object-Oriented Design Using Java 2, 2nd Ed. Jaime Niño,

Frederick A. Hosch. Wiley 2005

The Java Class Libraries Poster : Java 2 Platform Standard Edition 5.0 (8th Ed.). Patrick Chan,

Rosanna Lee. Addison-Wesley Professional, 2005.

Objects, Abstraction, Data Structures and Design Using Java Version 5.0. Elliot Koffman, Paul

Wolfgang. Wiley, 2004.

1.6. 结束语

本文列出了我使用 Java 2 Platform, Standard Edition (J2SE platform) 5.0 的 5 个重要理由。(本文

不涵盖客户机或服务器端的全部功能)。有关平台的所有资料,从规范到源代码,均可从 SCSL 和

Java Research Library 获得,它有助于您作出自己的决策。请通过社区论坛联系我们,让我们分享

您对 J2SE 5.0 版本的看法。

下面的工具和应用程序利用了 J2SE 5.0 增强功能,从而提高开发人员的效率。

New J2SE 5.0 Enhanced tools and applications

Java System Application Server 9

NetBeans 4.0

Eclipse 3.x

Borland JBuilder 2005 - Wily Introscope

2005 年 1 月 1 期 总第 2 期

- 16 -

Page 22: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

Tiger 核心库简介

本文作者: 大阿福 ([email protected] )

摘要:Java 之所以得到很多程序员的青睐,除了她的严谨的面向对象特性外,还有一个不容

轻视的因素,那就是她强大的类库。一门语言如果没有库,功能将会大打折扣,在 JDK5.0 版本中,

其核心库也有了诸多的改进,本文将就其新特性进行简介。

1.1. 事务的属性

1.1.1. 访问环境变量

虽然 Java 从一开始推出的时候,就一再强调她的跨平台特性,“一次编译,到处运行”。所以

能够访问平台专有信息的 System.getenv()方法从一开始进入 java 的 lang 包时,就遭到了大多数人

的反对。虽然 1.0 版本中抛弃了其中的一些内容,但是在 tiger 版本中我们又可以使用这个方法了,

请注意该方法的名称全部是小写。

使用的方法很简单:如清单 1

public class EnvTest {

public static void main(String args[]) {

System.out.println(System.getenv(args[0]));

}

}

只要给定变量的名称,就可以得到它的当前值。

Tiger 提供了两个版本的 getenv(),第二个版本返回系统当前设置中所有的环境变量对应的

“名值”对。清单 2 说明该方法的使用:

import java.util.Map;

public class EnvDump {

public static void main(String args[]) {

for (Map.Entry entry: System.getenv().entrySet()) {

System.out.println(entry.getKey() + “/” + entry.getValue());

}

}

}

2005 年 1 月 1 期 总第 2 期

- 17 -

Page 23: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.1.2. 访问子进程

J2SE平台的前期版本提供了Runtime类的exec()方法用来创建子进程的运行,在Tiger版本中,

这个方法依然有效,但是为了更方便的定制子进程,Tiger提供了ProcessBuilder类,它使依据改变

了的进程变量来创建子进程更加便利。ProcessBuilder提供了directory(File)方法来改变进程的工作目

录,用enviroment()方法在进程空间中添加和删除环境变量。清单 3 说明了Processor的简单用法,

它使用 ipconfig 命令获得 Internet 配置信息。该方法适用于多数平台,否则可以将 ipconfig

改写成所用平台上的工作命令。启动进程构造程序之后,需要获得其 InputStream,以读入所创

建进程的结果。

import java.io.*;

public class ProcessTest {

public static void main(String args[]) throws IOException {

Process p = new ProcessBuilder("ipconfig").start();

InputStream is = p.getInputStream();

BufferedReader br = new BufferedReader(new InputStreamReader(is));

String line;

while ((line = br.readLine()) != null) {

System.out.println(line);

}

}

}

运行结果如清单 4:

Windows 2000 IP Configuration

Ethernet adapter 本地连接:

Connection-specific DNS Suffix . :

IP Address. . . . . . . . . . . . : 10.97.69.166

Subnet Mask . . . . . . . . . . . : 255.255.255.128

Default Gateway . . . . . . . . . : 10.97.69.129

ProcessBuilder 类不仅能生成新的进程,而且还能获得其结果。在调用其 start() 方法之前,

还可以调整进程所执行的上下文。如果不喜欢环境变量,可以使用 environment 获得当前设置,

并调用 clear() 清除映射。如果需要添加环境变量,可以调用 environment 获得当前设置,

然后通过 put(name, value) 添加新的变量。如果希望使用新的工作目录,可以调用

directory() 并提供新的工作目录作为 File 对象。就是这么简单。

使用表示将运行的命令及其参数的数目可变的字符串参数来创建 ProcessBuilder,一旦使

2005 年 1 月 1 期 总第 2 期

- 18 -

Page 24: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

用新的环境变量和工作目录配置 ProcessBuilder,就可以调用 start() 来执行命令。

1.2. 并发集合

Java 中的集合框架一直是令人津津乐道的,在 Tiger 中,集合框架新添加了 Queue 接口以及这

个接口的并发和非并发实现,以及并发 Map 的实现和专用于读操作大大超过写操作的情况下的并

发 List 和 Set 实现。

1.2.1. Queue 接口

虽然在 List 的两端可以添加删除元素达到模拟 queue 的性能,但是 Queue 的提出提供了支持

添加、删除和检查集合的更为方便的方法:如清单 5 所示

public boolean offer(Object element)

public Object remove()

public Object poll()

public Object element()

public Object peek()

一些队列有大小长度的限制,因此如果想在一个已满的队列中加入一个新项,多出的项就会

被拒绝。这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked

异常,而只是得到由 offer() 返回的 false。remove() 和 poll() 方法都是从队列中删除第

一个元素(head)。remove() 的行为与 Collection 接口的版本相似,但是新的 poll() 方法

在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。

后两个方法 element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在

队列为空时,element() 抛出一个异常,而 peek() 返回 null。

1.2.1.1. 基本队列实现

1、 在 Tiger 中,java.util.LinkedList 已经被改造为实现了 java.util.List 和 java.util.Queue 两个接口。

清单 6 显示的是 LinkedList 的简单使用:

Queue queue = new LinkedList();

queue.offer("One");

queue.offer("Two");

queue.offer("Three");

queue.offer("Four");

System.out.println("Head of queue is: " + queue.poll());

2005 年 1 月 1 期 总第 2 期

- 19 -

Page 25: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

输出的结果应该是 One

2、 util 包新增加的 AbstractQueue 类,其工作方式类似于 AbstractList 和 AbstractSet 类,在需要创

建自己所需的对垒时,可以直接继承该类,必须实现 offer(),poll(),peek()三个方法,读者可

以在其中提供优化的实现。读者也可以不必创建自己的子类,而是使用几个 tiger 提供的实现,

其中两个时不阻塞队列:PriorityQueue 和 ConcurrentLinkedQueue。PriorityQueue 类实际上是维

护了一个有序的队列,加入到该 Queue 中的元素按照它们的自然顺序来进行排序,排序的依据

是元素对 java.util.Comparable 的实现或者传递给该 Queue 构造函数的 Comparator 参数。如果

将清单 6 的具体实现改为 PriorityQueue,则输入的结果应该为 Four,因为按字母顺序排列,Four

是排在了第一个。ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不

需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以不需要知道队列的大小,

ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息

会很慢,需要遍历队列。

1.2.1.2. 阻塞队列实现

Tiger 提供的 java.util.concurrent 包在 集合框架 中加入了 BlockingQueue 接口和五个阻塞队

列类。简单的讲,阻塞队列的意思就是当队列无空间时,添加元素的线程执行操作阻塞,直到有

空间;或者是,当队列为空无元素可删时,执行删除的线程阻塞,知道有元素可删。BlockingQueue

接口的 Javadoc 给出了阻塞队列的基本用法,如清单 7 所示。生产者中的 put() 操作会在没有空

间可用时阻塞,而消费者的 take() 操作会在队列中没有任何东西时阻塞。

class Producer implements Runnable {

private final BlockingQueue queue;

Producer(BlockingQueue q) { queue = q; }

public void run() {

try {

while(true) { queue.put(produce()); }

} catch (InterruptedException ex) { ... handle ...}

}

Object produce() { ... }

}

class Consumer implements Runnable {

private final BlockingQueue queue;

Consumer(BlockingQueue q) { queue = q; }

public void run() {

2005 年 1 月 1 期 总第 2 期

- 20 -

Page 26: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 21 -

try {

while(true) { consume(queue.take()); }

} catch (InterruptedException ex) { ... handle ...}

}

void consume(Object x) { ... }

}

class Setup {

void main() {

BlockingQueue q = new SomeQueueImplementation();

Producer p = new Producer(q);

Consumer c1 = new Consumer(q);

Consumer c2 = new Consumer(q);

new Thread(p).start();

new Thread(c1).start();

new Thread(c2).start();

}

}

另外五个阻塞队列提供的情况各有不同:

• ArrayBlockingQueue:一个由数组支持的有界队列。 • LinkedBlockingQueue:一个由链接节点支持的可选有界队列。 • PriorityBlockingQueue:一个由优先级堆支持的无界优先级队列。 • DelayQueue:一个由优先级堆支持的、基于时间的调度队列。 • SynchronousQueue:一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制

前两个类 ArrayBlockingQueue 和 LinkedBlockingQueue 几乎相同,只是在底层存储实现方面

有所不同,LinkedBlockingQueue 并不总是有容量界限。无大小界限的 LinkedBlockingQueue 类在

添加元素时永远不会有阻塞队列的等待(至少在其中有 Integer.MAX_VALUE 元素之前不会,这

个时候采用的容量限制即是 Integer.MAX_VALUE)。

PriorityBlockingQueue 是具有无界限容量的队列,它利用所包含元素的 Comparable 排序顺序

来以逻辑顺序维护元素。可以将它看作 TreeSet 的可能替代物。例如,在队列中加入字符串 One、

Two、Three 和 Four 会导致 Four 被第一个取出来。对于没有天然顺序的元素,可以为构造函数

提供一个 Comparator 。不过对 PriorityBlockingQueue 使用时需要注意,从 iterator() 返回的

Iterator 实例并不一定按照优先级顺序返回元素。如果必须以优先级顺序遍历所有元素,那么让它

们都通过 toArray() 方法并自己对它们排序,像 Arrays.sort(pq.toArray())。

新的 DelayQueue 实现可能是其中 有意思(也是 复杂)的一个。加入到队列中的元素必

Page 27: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

须实现新的 Delayed 接口(只有一个方法 —— long getDelay(java.util.concurrent.TimeUnit unit))。

因为队列的大小没有界限,使得添加可以立即返回,但是在延迟时间过去之前,不能从队列中取

出元素。如果多个元素完成了延迟,那么 早失效/失效时间 长的元素将第一个取出。实际上没

有听上去这样复杂。清单 8 演示了这种新的阻塞队列集合的使用:

import java.util.*;

import java.util.concurrent.*;

public class Delay {

/**

* Delayed implementation that actually delays

*/

static class NanoDelay implements Delayed {

long trigger;

NanoDelay(long i) {

trigger = System.nanoTime() + i;

}

public int compareTo(Object y) {

long i = trigger;

long j = ((NanoDelay)y).trigger;

if (i < j) return -1;

if (i > j) return 1;

return 0;

}

public boolean equals(Object other) {

return ((NanoDelay)other).trigger == trigger;

}

public boolean equals(NanoDelay other) {

return other.trigger == trigger;

}

public long getDelay(TimeUnit unit) {

long n = trigger - System.nanoTime();

return unit.convert(n, TimeUnit.NANOSECONDS);

}

public long getTriggerTime() {

return trigger;

}

public String toString() {

return String.valueOf(trigger);

}

}

public static void main(String args[]) throws InterruptedException {

2005 年 1 月 1 期 总第 2 期

- 22 -

Page 28: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 23 -

Random random = new Random();

DelayQueue queue = new DelayQueue();

for (int i=0; i < 5; i++) {

queue.add(new NanoDelay(random.nextInt(1000)));

}

long last = 0;

for (int i=0; i < 5; i++) {

NanoDelay delay = (NanoDelay)(queue.take());

long tt = delay.getTriggerTime();

System.out.println("Trigger time: " + tt);

if (i != 0) {

System.out.println("Delta: " + (tt - last));

}

last = tt;

}

}

}

这个例子首先是一个内部类 NanoDelay,它实质上将暂停给定的任意纳秒(nanosecond)数,

这里利用了 System 的新 nanoTime() 方法。然后 main() 方法只是将 NanoDelay 对象放到队列中

并再次将它们取出来。如果希望队列项做一些其他事情,就需要在 Delayed 对象的实现中加入方

法,并在从队列中取出后调用这个新方法。(请随意扩展 NanoDelay 以试验加入其他方法做一些

有趣的事情。)显示从队列中取出元素的两次调用之间的时间差。如果时间差是负数,可以视为一

个错误,因为永远不会在延迟时间结束后,在一个更早的触发时间从队列中取得项。

SynchronousQueue 类是 简单的。它没有内部容量。它就像线程之间的手递手机制。在队列

中加入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在

消费者和生产者之间传递,永远不会加入到阻塞队列中

1.2.2. ConcurrentMap 实现

新的 java.util.concurrent.ConcurrentMap 接口和 ConcurrentHashMap 实现只能在键不存在时

将元素加入到 map 中,只有在键存在并映射到特定值时才能从 map 中删除一个元素。

ConcurrentMap 中有一个新的 putIfAbsent() 方法用于在 map 中进行添加。这个方法以要添加

到 ConcurrentMap 实现中的键和值为参数,就像普通的 put() 方法,但是只有在 map 不包含这

个键时,才能将键加入到 map 中。如果 map 已经包含这个键,那么这个键的现有值就会返回。

Page 29: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

这个操作等于清单 9 的代码:

if (!map.containsKey(key))

return map.put(key, value);

else

return map.get(key);

像 putIfAbsent() 方法一样,重载后的 remove() 方法有两个参数 —— 键和值。在调用时,

只有当键映射到指定的值时才从 map 中删除这个键。如果不匹配,那么就不删除这个键,并返回

false。如果值匹配键的当前映射内容,那么就删除这个键。清单 10 显示了这种操作的等价源代码:

if (map.get(key).equals(value)) {

map.remove(key);

return true;

} else {

return false;

}

1.2.3. CopyOnWriteArrayList 和 CopyOnWriteArraySet

copy-on-write 模式是这样声明的,为了维护对象的一致性快照,要依靠不可变性(immutability)

来消除在协调读取不同的但是相关的属性时需要的同步。对于集合,这意味着如果有大量的读(即

get()) 和迭代,不必同步操作以照顾偶尔的写(即 add())调用。对于新的 CopyOnWriteArrayList

和 CopyOnWriteArraySet 类,所有可变的(mutable)操作都首先取得后台数组的副本,对副本进

行更改,然后替换副本。这种做法保证了在遍历自身更改的集合时,永远不会抛出

ConcurrentModificationException。遍历集合会用原来的集合完成,而在以后的操作中使用更新后的

集合。

这些新的集合,CopyOnWriteArrayList 和 CopyOnWriteArraySet, 适合于读操作通常大大超

过写操作的情况。一个 常提到的例子是使用监听器列表。已经说过,Swing 组件还没有改为使

用新的集合。相反,它们继续使用 javax.swing.event.EventListenerList 来维护它们的监听器列表。

如清单 11 所示,集合的使用与它们的非 copy-on-write 替代物完全一样。只是创建集合并在

其中加入或者删除元素。即使对象加入到了集合中,原来的 Iterator 也可以进行,继续遍历原来

集合中的项。

import java.util.*;

2005 年 1 月 1 期 总第 2 期

- 24 -

Page 30: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 25 -

import java.util.concurrent.*;

public class CopyOnWrite {

public static void main(String args[]) {

List list1 = new CopyOnWriteArrayList(Arrays.asList(args));

List list2 = new ArrayList(Arrays.asList(args));

Iterator itor1 = list1.iterator();

Iterator itor2 = list2.iterator();

list1.add("New");

list2.add("New");

try {

printAll(itor1);

} catch (ConcurrentModificationException e) {

System.err.println("Shouldn't get here");

}

try {

printAll(itor2);

} catch (ConcurrentModificationException e) {

System.err.println("Will get here.");

}

}

private static void printAll(Iterator itor) {

while (itor.hasNext()) {

System.out.println(itor.next());

}

}

}

这个示例程序用命令行参数创建 CopyOnWriteArrayList 和 ArrayList 这两个实例。在得到每

一 个 实 例 的 Iterator 后 , 分 别 在 其 中 加 入 一 个 元 素 。 当 ArrayList 迭 代 因 一 个

ConcurrentModificationException 问题而立即停止时,CopyOnWriteArrayList 迭代可以继续,不会

抛出异常,因为原来的集合是在得到 iterator 之后改变的。如果这种行为(比如通知原来一组事件

监听器中的所有元素)是您需要的,那么 好使用 copy-on-write 集合。如果不使用的话,就还用

原来的,并保证在出现异常时对它进行处理。

1.3. Formatter

对于那些从一开始就使用 Java 编程而从没有接触过 C 的人,或者,对那些对 C 没有足够

了解的人,格式化字符串是一些古怪的文本串,它们指定一组变量的输出特性。不是用加号将字

Page 31: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

符串连接在一起(如 firstName + " " + lastName),而是提供一个字符串描述输出,并提供参数以

在方法调用结束时,替换字符串中的占位符:String s = String.format("%1$s %2$s", firstName,

lastName)。

首先,让我们分析新的 java.util.Formatter 类。您可能不会经常直接使用这个类,但是它提供

了所要进行的格式化的内部机制。在这个类的 Javadoc 中,会看到一个描述所支持的格式化选项

的表。这些选项的范围从以类似 %7.4f 这样的格式指定浮点数的精度和位数,到格式化时间的

%tT,到格式化第三个参数 %3$s。

用 Formatter 格式化输出分为两步:创建一个 Appendable 对象以存储输出,用 format() 方

法将带格式的内容放到这个对象中。下面列出了 Appendable 接口的实现器:

• BufferedWriter • CharArrayWriter • CharBuffer • FileWriter • FilterWriter • LogStream • OutputStreamWriter • PipedWriter • PrintStream • PrintWriter • StringBuffer • StringBuilder • StringWriter • Writer

在使用 Formatter 类时,可以将实现了这个接口的对象传递给构造函数 Formatter 以把它作为

目标。大多数这种类看起来很相似,除了 StringBuilder 类。StringBuilder 与 StringBuffer 类几乎

相同,只有一个大的区别:它不是线程安全的。如果知道要在单线程中构建字符串,就使用

StringBuilder。如果构建过程会跨越多个线程,则使用 StringBuffer。清单 12 显示了通常如何开始

使用 Formatter:

StringBuilder sb = new StringBuilder();

Formatter formatter = new Formatter(sb, Locale.US);

创建了 Formatter 类后,用格式化字符串和参数调用其 format() 方法。如果需要使用与传递

2005 年 1 月 1 期 总第 2 期

- 26 -

Page 32: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

给出构造函数的不同的 Locale 作为格式化输出的一部分,还可以向 format() 方法传递一个

Locale 对象。清单 13 显示了两种不同的 format():

public Formatter format(String format, Object... args)

public Formatter format(Locale l, String format, Object... args)

如果希望得到精度为 10 位数字的 Pi 值,清单 14 中的代码会将这个值放到 StringBuilder

中并打印输出。打印 formatter 对象将显示 Appendable 对象的内容。

import java.util.Locale;

import java.util.Formatter;

public class Build {

public static void main(String args[]) {

StringBuilder sb = new StringBuilder();

Formatter formatter = new Formatter(sb, Locale.US);

formatter.format("PI = %12.10f", Math.PI);

System.out.println(formatter);

}

}

1.3.1. PrintStream 支持

PrintStream 类中定义了常见的、分别用于写入标准输出和标准错误的 System.out 和

System.err 对象。Tiger 引入了两个新的构造函数(用于直接写入文件)和六个方法以提供对格式

化的支持(三对)。第一对是另一版本的 append() 方法。这一对方法实现了新的

java.lang.Appendable 接口。一般不会直接调用这些方法。直接调用的是 format() 和 printf(),其

中 printf() 版本只是 format() 版本的方便的包装器,如清单 15 所示:

public PrintStream format(String format, Object... args)

public PrintStream format(Locale l, String format, Object... args)

要记住新的变量参数支持,它是由 ... 指派的

清单 16 演示了用 PrintStream 的 format() 方法打印今天的日期:

2005 年 1 月 1 期 总第 2 期

- 27 -

Page 33: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

import java.util.Calendar;

public class Now {

public static void main(String args[]) {

System.out.format("Today is %1$tB %1$te, %1$tY.",Calendar.getInstance());

}

}

运行这个程序的输出是 Today is January 19, 2005.,当然实际的输出取决于运行这个程序的日

期。上面代码中的格式化字符串 %1$tB 告诉程序使用第一个参数并打印 date 对象的完整月名。

格式化字符串 %1$te 意味着显示月中的日期,而格式化字符串 %1$tY 是四位数字的年。在

Formatter 对象的 Javadoc 中列出了打印日期的其他选项。

1.3.2. String 支持

String 类有两个新的 static format() 方法,它们的作用与相应的 printf() 类似。发送一个格式

化字符串和参数(还可能有 Locale)、并使用在格式化字符串中指定的格式转换参数。如果是这个

方法的 String 版本,那么是以 String 对象而不是经过流返回结果。这些方法不是很显眼,但是有

了它们就可以避免直接使用 Formatter 对象并创建中间的 StringBuilder。

1.3.3. 格式化任意对象

到目前为止看到的每项内容都是描述如何使用新的格式化能力对已有对象和基本数据进行格

式化。如果希望用 Formatter 提供对自己的对象的支持,那么就要用到 Formattable 接口。通过在

自己的类中实现如清单 17 所示的一个 formatTo() 方法,可以对自已的类使用格式化字符串:

void formatTo(Formatter formatter, int flags, Integer width, Integer precision)

清单 18 演示了通过提供一个具有 name 属性的简单类来使用 Formattable 接口。这个 name

显示在输出中,它支持控制输出的宽度和对齐。

import java.util.Locale;

import java.util.Formatter;

2005 年 1 月 1 期 总第 2 期

- 28 -

Page 34: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 29 -

import java.util.Formattable;

public class MyObject implements Formattable {

String name;

public MyObject(String name) {

this.name = name;

}

public void formatTo(

Formatter fmt,

int f,

Integer width,

Integer precision) {

StringBuilder sb = new StringBuilder();

if (precision == null) {

// no max width

sb.append(name);

} else if (name.length() < precision) {

sb.append(name);

} else {

sb.append(name.substring(0, precision - 1)).append('*');

}

// apply width and justification

if ((width != null) && (sb.length() < width)) {

for (int i = 0, n=sb.length(); i < width - n; i++) {

if ((f & Formattable.LEFT_JUSTIFY) == Formattable.LEFT_JUSTIFY) {

sb.append(' ');

} else {

sb.insert(0, ' ');

}

}

}

fmt.format(sb.toString());

}

public static void main(String args[]) {

MyObject my1 = new MyObject("John");

MyObject my2 = new MyObject("Really Long Name");

// First / Using toString()

System.out.println("First Object : " + my1);

// Second / Using Formatter

System.out.format("First Object : '%s'\\n", my1);

// Second / Using Formatter

System.out.format("Second Object: '%s'\\n", my2);

Page 35: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 30 -

// Second / Using Formatter with width

System.out.format("Second Object: '%10.5s'\\n", my2);

// Second / Using Formatter with width and left justification

System.out.format("Second Object: '%-10.5s'\\n", my2);

}

}

运行这个程序会生成如清单 19 所示的输出。前两行展示了使用 toString 和 Formatter 的不同

结果。后三行显示宽度和对齐控制的选项。

First Object : MyObject@10b62c9

First Object : 'John'

Second Object: 'Really Long Name'

Second Object: ' Real*'

Second Object: 'Real*

1.4. 从 XML 文件中装载属性

Properties 这个类对于读者来说应该并不陌生,在应用程序的配置文件中大多采用的都是这样

的属性文本文件。在 Tiger 中,java.util.Properties 类现在提供了一种为程序装载和存储设置的更容

易的方法:loadFromXML(InputStream is) 和 storeToXML(OutputStream os, String comment) 方法。

1.4.1. XML 属性文件

XML 属性文件的指定 DTD 如清单 20 所示:

<?xml version="1.0" encoding="UTF-8"?>

<!-- DTD for properties -->

<!ELEMENT properties ( comment?, entry* ) >

<!ATTLIST properties version CDATA #FIXED "1.0">

<!ELEMENT comment (#PCDATA) >

<!ELEMENT entry (#PCDATA) >

<!ATTLIST entry key CDATA #REQUIRED>

简单的 XML 属性文件如清单 21 所示:

<?xml version="1.0" encoding="UTF-8"?>

Page 36: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 31 -

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>

<comment>Hi</comment>

<entry key="foo">bar</entry>

<entry key="fu">baz</entry>

</properties>

相信读者对此一目了然。

1.4.2. 存取 XML 属性文件

1.4.2.1. 读取 XML 属性文件

读取 XML 版本的 Properties 文件与读取老格式的文件没什么不同。如清单 22 所示

import java.util.*;

import java.io.*;

public class LoadSampleXML {

public static void main(String args[]) throws Exception {

Properties prop = new Properties();

FileInputStream fis = new FileInputStream("sampleprops.xml");

prop.loadFromXML(fis);

prop.list(System.out);

System.out.println("\nThe foo property: " + prop.getProperty("foo"));

}

}

1.4.2.2. 保存 XML 属性文件

用新的 storeToXML() 方法创建 xml 文件。只要传递一个 OutputStream 和一个用于注释的

String 就可以了。代码如清单 23 所示:

import java.util.*;

import java.io.*;

public class StoreXML {

public static void main(String args[]) throws Exception {

Properties prop = new Properties();

Page 37: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 32 -

prop.setProperty("one-two", "buckle my shoe");

prop.setProperty("three-four", "shut the door");

prop.setProperty("five-six", "pick up sticks");

prop.setProperty("seven-eight", "lay them straight");

prop.setProperty("nine-ten", "a big, fat hen");

FileOutputStream fos = new FileOutputStream("rhyme.xml");

prop.storeToXML(fos, "Rhyme");

fos.close();

}

}

运行结果如清单 24 所示:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>

<comment>Rhyme</comment>

<entry key="seven-eight">lay them straight</entry>

<entry key="five-six">pick up sticks</entry>

<entry key="nine-ten">a big, fat hen</entry>

<entry key="three-four">shut the door</entry>

<entry key="one-two">buckle my shoe</entry>

</properties>

1.5. 其他核心库的增强

另外,Tiger 以下核心库方面也有了一定的增强,由于篇幅所限,这里就不一一赘述了,请感

兴趣的读者参照 JDK 5.0 API Documentation 中的相关内容。

1.5.1. Scanner

java.util.Scanner 类可用于将文本转换成原语或字符串。由于它是基于 java.util.regex 包的,

因此它也提供了一种方式来管理正则表达式,该表达式把搜索建立在流、文件数据、字符串或

Readable 接口的封装行为(implementor)上。

1.5.2. JavaBeans 组件体系结构

已经添加了一个称为 IndexedPropertyChangeEvent 的 PropertyChangeEvent 子类来支持界限

Page 38: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

属性,该属性使用索引来指出 bean 的更改部分。另外,已经在 PropertyChangeSupport 类中添加

了一些方法,用以支持激发索引属性改变事件。

1.5.3. Math 包

java.math 包包含了下面的增强功能:

• BigDecimal 类已经为固定精度浮点计算添加了支持。参见 JSR 13.

• Math 和 StrictMath 库包含双曲线超越函数(sinh、cosh 和 tanh)、立方根和基于 10 的

对数等。

• 十六进制浮点数支持 —— 为允许特定浮点值的精确和可预知的说明,十六进制表示法可

用于浮点数的字面值,以及用于 Float 和 Double 中浮点数转换方法的字符串。

1.5.4. 网络

1.5.5. 安全性

这一版本的 J2SE 在安全方面大大增强了。它为安全性令牌提供了更好的支持, 为更多的安

全标准(SASL、OCSP 和 TSP)提供了支持,改进了可伸缩性(SSLEngine)和性能,此外在 Crypto

和 Java GSS 方面也提供了许多增强功能。有关更多信息,请参见上面的链接。

1.5.6. 国际化

增强功能有:

• 现在 字符处理是以 4.0 版本的 Unicode 标准为基础的。这就影响了 java.lang 包中的

Character 类和 String 类、java.text 包中的排序规则和双向文本分析功能、

java.util.regex 包中的 Character 类及 J2SE 的其他许多部分。作为这一升级的一部

分,JSR 204 专家组已经指定了对辅助字符的支持,而且在整个 J2SE 中已经实现了该支

持。有关更多信息,请参见 JSR 204 或 Character 类文档。

• DecimalFormat 类已经得到了增强,现在可以格式化和解析 BigDecimal 和 BigInteger

值,而不会丢失精确度。这些值的格式化是自动得到增强的;必须启用

setParseBigDecimal 方法才可以进行到 BigDecimal 的解析。

• 现在,Vietnamese 在 java.util 和 java.text 包中所有的 Locale 敏感功能方面得到了支持。有

关支持的 Locale 和写系统的完整信息,请参见 支持的 Locale。

2005 年 1 月 1 期 总第 2 期

- 33 -

Page 39: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.5.7. 序列化

已经添加了支持来处理 1.5 版本中新增的枚举类型。序列化一个枚举实例的规定不同于序列

化一个“普通”可序列化对象的那些规则:枚举实例的序列化表单只包括它的枚举常量名和指出它的

基本枚举类型的信息。反序列化行为也是不同的 —— 类信息用于查找相应的枚举类,并且为了

获取返回的枚举常量,通过使用那个类和接收的常量名来调用 Enum.valueOf 方法。

1.6. 结束语

以上所讲到的只是 J2SE 1.5 核心库新特性的一小部分,还有很多其他新特性限于篇幅不能一

一 讲 述 , 有 兴 趣 进 一 步 详 细 了 解 这 些 新 特 性 的 读 者 可 以 参 看

http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html 可获取更多信息。

Tiger“通过增强 Java 平台的力量、允许开发者更容易地使用,Java 编程语言的这些改进将吸

引大量各种 Java 开发者”,是“Java 技术发展历程的一个重要里程碑”。

2005 年 1 月 1 期 总第 2 期

- 34 -

Page 40: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

JAVA 泛型 QUIK START

JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为 5.0。这说明 JAVA 已经有大幅

度的变化.本文将讲解 JDK5.0 支持的新功能-----JAVA 的泛型.

1.1. JAVA 泛型

其实 JAVA 的泛型就是创建一个用类型作为参数的类。就象我们写类的方法一样,方法是这样

的 method(String str1,String str2 ),方法中参数 str1、str2 的值是可变的。而泛型也是一样的,这样写

class Java_Generics<K,V>,这里边的 K 和 V 就象方法中的参数 str1 和 str2,也是可变。下面看看例

子:

//code list 1

import java.util.Hashtable;

class TestGen0<K,V>{

public Hashtable<K,V> h=new Hashtable<K,V>();

public void put(K k, V v) {

h.put(k,v);

}

public V get(K k) {

return h.get(k);

}

public static void main(String args[]){

TestGen0<String,String> t=new TestGen0<String,String>();

t.put("key", "value");

String s=t.get("key");

System.out.println(s);

}

}

正确输出:value

这只是个例子(JAVA中集合框架都泛型化了,这里费了 2 遍事☺),不过看看是不是创建一个

用类型作为参数的类,参数是K,V,传入的“值”是String类型。这个类他没有特定的待处理型

别,以前我们定义好了一个类,在输入输入参数有所固定,是什么型别的有要求,但是现在编写

程序,完全可以不制定参数的类型,具体用的时候来确定,增加了程序的通用性,像是一个模板。

呵呵,类似C++的模板(类似)。

2005 年 1 月 1 期 总第 2 期

- 35 -

Page 41: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.1.1. 泛型通配符

下面我们先看看这些程序:

//Code list 2

void TestGen0Medthod1(List l) {

for (Object o : l)

System.out.println(o);

}

看看这个方法有没有异议,这个方法会通过编译的,假如你传入 String,就是这样 List<String>。

接着我们调用它,问题就出现了,我们将一个 List<String>当作 List 传给了方法,JVM 会给我们一

个警告,说这个破坏了类型安全,因为从 List 中返回的都是 Object 类型的,而让我们再看看下面

的方法。

//Code list 3

void TestGen0Medthod1(List<String> l) {

for (Object o : l)

System.out.println(o);

}

因为这里的 List<String>不是 List<Object>的子类,不是 String 与 Object 的关系,就是说

List<String>不隶属于 list<Object>,他们不是继承关系,所以是不行的,这里的 extends 是表示限制

的。

类型通配符是很神奇的,List<?>这个你能为他做什么呢?怎么都是“?”,它似乎不确定,他

总不能返回一个?作为类型的数据吧,是啊他是不会返回一个“?”来问程序员的?JVM 会做简

单的思考的,看看代码吧,更直观些。

//code list 4

List<String> l1 = new ArrayList<String>();

li.add(“String”);

List<?> l2 = l1;

System.out.println(l1.get(0));

这段代码没问题的,l1.get(0)将返回一个 Object。

1.1.2. 编写泛型类要注意:

1、 在定义一个泛型类的时候,在 “<>”之间定义形式类型参数,例如:“class TestGen<K,

V>”,其中“K” , “V”不代表值,而是表示类型。

2、 实例化泛型对象的时候,一定要在类名后面指定类型参数的值(类型),一共要有两次

2005 年 1 月 1 期 总第 2 期

- 36 -

Page 42: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

书写。例如:TestGen<String,String> t=new TestGen<String,String>();

3、 泛型中<K extends Object>,extends 并不代表继承,它是类型范围限制。

1.2. 泛型与数据类型转换

1.2.1. 消除类型转换

上面的例子大家看到什么了,数据类型转换的代码不见了。在以前我们经常要书写以下代码,

如:

//code list 5

import java.util.Hashtable;

class Test {

public static void main(String[] args) {

Hashtable h = new Hashtable();

h.put("key", "value");

String s = (String)h.get("key");

System.out.println(s);

}

}

这个我们做了类型转换,是不是感觉很烦的,并且强制类型转换会带来潜在的危险,系统可

能会抛一个 ClassCastException 异常信息。在 JDK5.0 中我们完全可以这么做,如:

//code list 6

import java.util.Hashtable;

class Test {

public static void main(String[] args) {

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

h.put("key", new Integer(123));

int s = h.get("key").intValue();

System.out.println(s);

}

}

这里我们使用泛化版本的 HashMap,这样就不用我们来编写类型转换的代码了,类型转换的过

程交给编译器来处理,是不是很方便,而且很安全。上面是 String 映射到 String,也可以将 Integer

映射为 String,只要写成 HashTable<Integer,String> h=new HashTable<Integer,String>();h.get(new

Integer(0))返回 value。果然很方便。

2005 年 1 月 1 期 总第 2 期

- 37 -

Page 43: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.2.2. 自动解包装与自动包装的功能

从上面有没有看到有点别扭啊,h.get(new Integer(123))这里的 new Integer(123);好烦的,在

JDK5.0 之前我们只能忍着了,现在这种问题已经解决了,请看下面这个方法。我们传入一个 int

这一基本型别,然后再将 i 的值直接添加到 List 中,其实 List 是不能储存基本型别的,List 中应该

存储对象,这里编译器将 int 包装成 Integer,然后添加到 List 中去。接着我们用 List.get(0);来检索

数据,并返回对象再将对象解包装成 int。恩,JDK5.0 给我们带来更多方便与安全。

//Code list 7

public void autoBoxingUnboxing(int i) {

ArrayList<Integer> L= new ArrayList<Integer>();

L.add(i);

int a = L.get(0);

System.out.println("The value of i is " + a);

}

2005 年 1 月 1 期 总第 2 期

- 38 -

1.2.3. 限制泛型中类型参数的范围

也许你已经发现在 code list 1 中的 TestGen<K,V>这个泛型类,其中 K,V 可以是任意的型别。也

许你有时候呢想限定一下 K 和 V 当然范围,怎么做呢?看看如下的代码:

//Code list 8

class TestGen2<K extents String,V extends Number>

{

private V v=null;

private K k=null;

public void setV(V v){

this.v=v;

}

public V getV(){

return this.v;

}

public void setK(K k){

this.k=k;

}

public V getK(){

return this.k;

}

public static void main(String[] args)

{

Page 44: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 39 -

TestGen2<String,Integer> t2=new TestGen2<String,Integer>();

t2.setK(new String("String"));

t2.setV(new Integer(123));

System.out.println(t2.getK());

System.out.println(t2.getV());

}

}

上边 K 的范围是<=String ,V 的范围是<=Number,注意是“<=”,对于 K 可以是 String 的,

V 当然也可以是 Number,也可以是 Integer,Float,Double,Byte 等。看看下图也许能直观些

请看上图 A 是上图类中的基类,A1,A2 分别是 A 的子类,A2 有 2 个子类分别是 A2_1,A2_2。

然后我们定义一个受限的泛型类 class MyGen<E extends A2>,这个泛型的范围就是上图中兰色部

分。

这个是单一的限制,你也可以对型别多重限制,如下:

class C<T extends Comparable<? super T> & Serializable>

我们来分析以下这句,T extends Comparable这个是对上限的限制,Comparable<? super

T>这个是下限的限制,Serializable是第 2个上限。一个指定的类型参数可以具有一个或多个

上限。具有多重限制的类型参数可以用于访问它的每个限制的方法和域。

1.2.4. 多态方法

//Code list 9

class TestGen {

<T extends Object> public static List<T> make(T first) {

return new List<T>(first);

}

}

A

A1 A2

A2 1

图-1

A2 2

Page 45: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

J2SE 5.0 专题 之 用户接口

1.1. 引子

“从 Java 诞生至今已有 9 年时间,而从第二代 Java 平台 J2SE 算起也有 5 个年头了。在这样

的背景下,将下一个版本的版本号从 1.5 改为 5.0 可以更好的反映出新版 J2SE 的成熟度、稳定性、

可伸缩性和安全性。” Sun 公司如是解释 Java 的这次意义深远的变革。众所周知 Sun 公司从发布

Java 以来,对 Java 的每一次变更可谓是小心翼翼,特别是经历了版本一到版本二的变更后,Java

一直就停留在比较稳定的时期,这对于一个处于 IT 行业前沿中的主流技术确实令人难以想象。

可能对于象 Sun 这种以技术起家的公司来说,从骨子里就透出一种“不鸣则已,一鸣惊人”

的气质。Java 从版本一到二的飞跃中已经初露端倪,那么这次 Java 的变更又会带来什么呢?从它

发布 3 个月以来的各方评价,亦可探知一二。其他暂且不论,单从这次变更涉及的面上看就知,

这绝非产于一朝一夕,从宏观上语言特性到具体的 API,从 Java 底层的虚拟机到高层的核心库,

几乎无一例外的加入了这次变更。如此之大的变更,即便是我这个对 Java 语言认识不深的人,也

能从中略窥一二,但不敢深论,只是学有小得,顺道写些想法,与各位共同学习交流。以下我将

对 J2SE(TM) 5.0 中的用户接口(UI)部分做一些简要的介绍。

1.2. 综述

Java 一直以来在桌面系统中表现的并不是很成功,究其原因主要是在 UI 方面表现的不尽如人

意,虽然在理论上 Sun 公司对 UI 同样做出了无所不能的承诺,可是一个真正成功的设计不能只停

留在理论层面上,而要真正受开发人员认可,在市场中得到青睐才能算得上成功,可 Java 在 UI

方面两者都做的不是很优秀。这次 J2SE(TM) 5.0 在 UI 方面作了大幅度的调整,大有弥补这方面缺

陷的势头,对于抢夺在桌面系统的领地是志在必得。关于这次调整的相关评论信息,可以在网上

查阅,这里限于篇幅暂且不论。下面将对 J2SE(TM) 5.0 涉及 UI 方面的调整做一些介绍,其内容包

括:

1、国际化

2、Java 声音技术

2005 年 1 月 1 期 总第 2 期

- 40 -

Page 46: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

3、Java 2D(TM)技术

4、图像 I/O

5、AWT

6、Swing

上面的各个主题由于本人水平及篇幅所限可能无法一一详述,有些在这里只能稍作介绍,作

为一个引子,其余的还望读者更多地去思考,去探索。

1.3. 国际化

Java 语言作为第一种支持国际化的语言,在 Internet 从一开始就具有其他语言无与伦比的国际

化的本质特性:用 Unicode 来编写所有的字符串。可是理论与实际总是不能完全谋和的,因为

Unicode 本身也在不断变化。

在J2SE(TM) 5.0 中,主要的变化是对java.lang, java.text, java.util.regex等包进行调整,使整个字

符处理基于Unicode4.0 的标准之上,同时加强了对增补字符的支持(欲了解更多信息请参照:

《Supplementary Characters in the Java Platform》By Norbert Lindenberg and Masayoshi Okutsu,)。另

外这些调整,已经比较全面地解决了越南等东南亚国家的文字读写及显示问题,虽然并没有经过

大范围的测试。查看Java所支持的语言及相应的版本信息可以采用以下代码进行查询。

import java.util.*;

import java.text.*;

public class Test{

public static void main(String[] args) {

//输出 JVM支持的语言

Locale locale[] = DateFormat.getAvailableLocales();

System.out.println("======本地系统支持语言:======== ");

for (int i = 0; i < locale.length; i++) {

System.out.println(locale[i].toString() + "\t" +

locale[i].getDisplayName());

}

//输出 JVM 默认属性

System.out.println("======系统属性======== ");

System.getProperties().list(System.out);

}

}

在桌面系统中,J2SE(TM) 5.0 的这次变更主要表现在成功地处理了逻辑字体的多语言文本支

2005 年 1 月 1 期 总第 2 期

- 41 -

Page 47: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

持,例如,如果您在泰国语环境中,但已经安装了韩国语的字体,就可以呈现泰国语和韩国语,

这只需通过 java.util.Locale 类进行相应的操作即可方便地达到应有的目标。还同时解决了在

Windows 2000/XP 中,AWT 调用 Unicode API 的问题。这样在 J2SE(TM) 5.0 中就可以运用它自带

的一些文本组件进行文本处理,而不用受到 Windows locale 设置的限制,增强了 Java 跨平台的功

能,使 Java 真正能够相对地独立于 Windows 平台。例如,AWT 文本组件可以在梵文书写系统中

接受和显示文本,并不用关心 Windows locale 的设置,而仅仅依靠 Java 程序内部的处理机制。

另外,在 java.nio 包中也增强了对 I/O 传输国际化的支持。这在核心库中已经有所介绍了,这

里就不在重复了。

1.4. Java 声音技术

声音技术在Java的领域里应该算是一个比较专业,对于很多非专业的开发者可能会很陌生,读

者如果希望对这方面知识进行更全面的了解,还请参阅《Java(TM) Sound Programmer Guide》。

以下仅列出J2SE(TM) 5.0 在声音处理技术中改进的部分:

1、 现在端口可在所有平台上使用(RFE 4782900)。

2、 现在 MIDI 设备 I/O 可在所有平台上使用(RFE 4812168 和 RFE 4782924)。

3、 在所有平台上实现了优化的直接音频访问 (RFE 4908240 和 RFE 4908879)。在提供本机

混合的系统上(如具有硬件混合的 Linux ALSA、启用的 Solaris Mixer 和 Windows

DirectSound),默认情况下启用了它。

4、 新的实时序列与所有 MIDI 设备一起工作,并允许无限地进行传送(RFE 4773012)。

5、 sound.properties 配置文件允许选择默认设备(RFE 4776511)。 有关更多信息,请参见

MidiSystem 和 AudioSystem。

6、 MidiDevices 可以查询连接的接收器和传送器(RFE 4931387, MidiDevice.getReceiver 和

MidiDevice.getTransmitter 方法)。

7、 AudioFormat、AudioFileFormat 和 MidiFileFormat 现在具有一些属性来允许进一步描述

和限定格式 (RFE 4925767 和 RFC 4666845)。

8、 一组易用的方法集允许更加容易地从 AudioSystem 检索行(RFE 4896221)。

序列接口是用循环方法扩展的,它可以在 MIDI 序列的特定部分进行无缝循环。(RFE 4204105)。

9、Java Sound 不再禁止 VM 退出(bug 4735740)。

2005 年 1 月 1 期 总第 2 期

- 42 -

Page 48: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.5. Java 2D 技术及图像 I/O

J2SE(TM) 5.0 在 Java 2D 技术及图像 I/O 方面在应用方面并没有进行过多的更改,多数还是保

持在原来的状态,但是很多更改的部分在 Java 语言中扮演着重要的角色,而且其中的更改部分仍

然代表着 Java 语言一贯坚持的前进方向。在 J2SE(TM) 5.0 之前所有针对图象操作 在

BufferedImage 内增加了预读功能,并适时地针对图象处理加入了一些硬件加速的处理方法,

如:Image 类中的 setAccelerationPriority and getAccelerationPriority。

另外,还添加了 2D 特性包括扩展的 Linux 和 Solaris 打印机支持、用于从文件和流中创建

字体的新方法和与 VolatileImages 和图像的硬件压缩有关的新方法。对文本呈现代码的大量更改

大大提高了它的强健性、性能和可伸缩性。其他性能工作包括在 Linux 和 Solaris 上使用 OpenGL

进行硬件加速呈现(默认情况下是禁用的)。以上性能的引入将使 Java 语言在桌面系统中的应用变

得更加简单实用。

而在图像 I/O方面,增加了具有针对 BMP 和 WBMP 格式的阅读器和编写器,即可以用XML

语言进行控制来处理相应的图像,增强了整个系统的重用性。

1.5.1. AWT 与 Swing

J2SE(TM) 5.0 版本提供了许多 AWT 增强功能和修补程序,其中包括一些客户经常要求的一

些增强功能和修补程序。特别是新的 MouseInfo 类使得可以决定桌面上鼠标的位置。新的 Window

方法使得可以根据平台来指定 近创建的窗体(或帧)的默认位置。另一个窗口增强功能使得可

以保证窗体(或帧)总是处在 上层(对于 Solaris/Linux 上的一些窗体处理器,这种特性是不能

工作的)。在数据转换方面,新的 DropTargetDragEvent API 使得在拖动过程中可以让目标访问传

送的数据。这些 API 的引入不管从哪一方面都在很大程度上增强了 Java 语言的易用性。

Swing 在这次 Java 的变更中扮演着一个极其重要的角色,它的变更已经不仅仅停留在更改几

个 API,而是在实现的理念里添加进了新的实现元素,这势必会影响 Java 在桌面系统应用里 UI 的

构架设计。Swing 是 Java 在桌面系统中 主要的应用技术,从其出现伊始就广受人们的关注,而

且 Sun 公司也一直将它作为 Java 在桌面系统的主流技术进行推广。在 J2SE(TM) 1.4.2 中, Swing

提供了两种外观:XP 和 GTK。但这没有到此就停止,在 J2SE(TM) 5.0 中 Swing 为我们提供了另

外两种外观:Synth —— 可切换皮肤的外观和 Ocean —— 针对 Metal 的新主题。摒弃了 1.4 默

认的陈旧的 Metal 外观之后,J2SE(TM) 5.0 引入了一个全新的 Ocean 外观。以 Sun 公司 SwingSet2

2005 年 1 月 1 期 总第 2 期

- 43 -

Page 49: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

演示程序作为向导,比较 Meta 外观的原有主题 Steel 与 Metal 外观的 Ocean 主题(如下图)。

另外,我们不得

Metal 的 Steel 主题 Metal 的 Ocean 主题

不提起的是 Synth,它是一个完整的外观,而不是一个主题,它针对的并不是程序员,而是界面设

计人员,让他们不必掌握 Java 知识便能够进行相应界面的选择定制。采用了 Synth 后,我们不能

从现有外观或主题派生子类、修改字体或颜色,而是通过转载不同的 XML 文件以达到对相应界

面的控制。其实现步骤包括将程序外观设置成 Synth 模式以及编辑相对应的 XML 文件。设置 Synth

外观模式,可以使用以下代码:

SynthLookAndFeel synth = new SynthLookAndFeel();

Class aClass = SynthTest.class;

InputStream is = aClass.getResourceAsStream("file1.xml");

synth.load(is, aClass);

UIManager.setLookAndFeel(synth);

对于 XML 中的大多数事情来说,文件的内容由文档类型定义(DTD)描述。只要在 XML 文

件中描述组件,然后把文件传给 SynthLookAndFeel 实例的 load() 方法,应用程序的外观就会有

所不同。以下是 XML 控制程序外观的代码:

<synth>

<style id="button">

<font name="Dotum" size="24" style="BOLD"/>

<state value="MOUSE_OVER">

<font name="System" size="48" style="ITALIC"/>

</state>

</style>

<bind style="button" type="region" key="Button"/>

</synth>

2005 年 1 月 1 期 总第 2 期

- 44 -

Page 50: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

以下附上 Synth 的演示代码 SynthTest.java,与其相对应的 XML 文件可选用上面的示例代码,

将其更名为 synth.xml 即可:

import java.io.InputStream;

import java.text.ParseException;

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.UIManager;

import javax.swing.plaf.synth.SynthLookAndFeel;

public class SynthTest{

private static void createGUI(){

SynthLookAndFeel synth = new SynthLookAndFeel();

try {

Class classA = SynthTest.class;

InputStream in = classA.getResourceAsStream("synth.xml");

if (in == null) {

System.err.println("Unable to find theme configuration");

System.exit(-1);

}

synth.load(in, classA);

} catch (ParseException e) {

System.err.println("Unable to load theme configuration");

System.exit(-2);

}

try {

UIManager.setLookAndFeel(synth);

} catch (javax.swing.UnsupportedLookAndFeelException e) {

System.err.println("Unable to change look and feel");

System.exit(-3);

}

JFrame frame = new JFrame("Test Synth");

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

JButton button = new JButton("Hello Synth");

frame.add(button);

frame.setSize(400, 200);

frame.setVisible(true);

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 45 - }

Page 51: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 46 -

public static void main(String[] args){

javax.swing.SwingUtilities.invokeLater(new Runnable(){

public void run() {

createGUI();

}

});

}

}

除了外观的调整之外,J2SE(TM) 5.0 已经添加了对 JTable 的打印支持,这使得可以容易地获

取 JTable 的漂亮打印副本。另外,在 Java 语言发展了多年后,J2SE(TM) 5.0 终于实现了

JFrame.add() 等价于 JFrame.getContentPane().add()。

1.6. 结束语

以上介绍的内容仅仅与大家共同领略了 J2SE(TM) 5.0 所带来的新的 UI 元素,不图窥一斑而

知全豹,但从中仍然能看出 Java 前进的步伐,愿 Java 一路走好!

Page 52: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

J2SE 5.0 专题 之 语言特性

本文作者: 高宇翔(大胃)

1.1. 背景

J2SE(TM) 5.0 正式发布至今已超过 3 个月的时间了,就在前不久,大概是在两周之前,Sun 又

发布了更新过的 JDK 5.0 Update 1,改掉了一些第一个版本中出现的 bug。

由于 Java 社群等待这一从 1.4 向 5.0 版本升级已经有相当长的一段时间,大家都很关心 5.0 中

有哪些值得关注的变化,于是 blog 的相关信息满天飞,我也兴冲冲地在自己的 blog 中添上了一系

列的文章。无奈这些 blog 文章,包括我自己的在内,通常都是泛泛而谈,因此 CSDN 第二期 Java

电子杂志的编辑们计划做一个专题对这一话题与相关人士进行一番深入的探讨。

作为这期电子刊物的一部分,编辑们也邀请我更系统的探讨一下:J2SE(TM) 5.0 中新引入的

语言特性究竟在实际中有哪些用途,以及为什么要引入这些新特性。对此我深感荣幸。我本人很

乐意将我的一些也许算得上经验的 Java 经验跟大家分享,希望这一篇小文能对大家了解 J2SE(TM)

5.0 有一定帮助。

1.2. 准备工作

首先,为了了解 J2SE(TM) 5.0 的新的语言特性,你需要下载新版的 JDK,在这里可以找到下

载链接:http://java.sun.com/j2se/1.5.0/download.jsp。当然,如果你已经有过手动配置 Java 环境的经

历,我也建议你使用一个支持 J2SE(TM) 5.0 的 IDE,推荐 Eclipse SDK 3.1 M4,或者 NetBeans IDE

4.0。两个都是开源免费的,且很容易找到(Eclipse 不用说了,NetBeans IDE 4.0 有与 JDK 5.0 Update

1 的捆绑版)。

说点题外话,Java的版本号自从1.2开始,似乎就多少显得有点蹩脚。从1.2版本开始,Java (J2SE)

2005 年 1 月 1 期 总第 2 期

- 47 -

Page 53: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

被称作 Java 2,而不是 Java 1.2,现在则显得更加离奇:Java(TM) 2 Platform Standard Edition 5.0 或

者 J2SE(TM) 5.0,而内部的版本号还是 1.5.0。那么到底是 1、2、还是 5 呢?来看看 Sun 官方网站

是怎么说的:

从 Java 诞生至今已有 9 年时间,而从第二代 Java 平台 J2SE 算起也有 5 个年头了。在这样的

背景下,将下一个版本的版本号从 1.5 改为 5.0 可以更好的反映出新版 J2SE 的成熟度、稳定性、

可伸缩性和安全性。

好吧,现在我们将面对如下一些名称,而它们指的基本上是同一个东西:

Tiger

Java(TM) 2 Platform Standard Edition 5.0

J2SE(TM) 5.0

Java version 1.5.0

在本文中,为了方便起见,我将统一使用 J2SE(TM) 5.0 这个名称。

如果你对 Java各个版本的代号感兴趣,就像这里的 "Tiger",可以参考如下网址:

http://java.sun.com/j2se/codenames.html。透露一点:Java下一个版本(6.0)的代号是"Mustang"野马,

再下一个版本(7.0)的代号是"Dolphin"海豚。

1.3. 概述

J2SE(TM) 5.0 引入了很多激进的语言元素变化,这些变化或多或少减轻了我们开发人员的一

些编码负担,其中的大部分也必然会被应用到即将发布的 J2EE(TM) 5.0 中。主要的新特性包括:

泛型

增强的 for 循环

自动装箱和自动拆箱

类型安全的枚举

可变长度参数

静态引入

2005 年 1 月 1 期 总第 2 期

- 48 -

Page 54: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

元数据(注解)

C 风格的格式化输出

这当中,泛型、枚举和注解可能会占用较大的篇幅,而其余的因为用法直截了当,抑或相对

简单,我就稍作介绍,剩下的留给读者去思考、去探索了。

1.4. 泛型

泛型这个题目相当大,大到完全可以就这个话题写一本书。有关 Java 是否需要泛型和如何实

现泛型的讨论也早就在 Java 社群广为流传。终于,我们在 J2SE(TM) 5.0 中看到了它。也许目前 Java

对泛型的支持还算不上足够理想,但这一特性的添加也经足以让我们欣喜一阵了。

在接下来的介绍中,我们会了解到:Java 的泛型虽然跟 C++的泛型看上去十分相似,但其实

有着相当大的区别,有些细节的东西也相当复杂(至少很多地方会跟我们的直觉背道而驰)。可以

这样说,泛型的引入在很大程度上增加了 Java 语言的复杂度,对初学者尤其是个挑战。下面我们

将一点一点往里挖。

首先我们来看一个简单的使用泛型类的例子:

ArrayList<Integer> aList = new ArrayList<Integer>();

aList.add(new Integer(1));

// ...

Integer myInteger = aList.get(0);

我们可以看到,在这个简单的例子中,我们在定义 aList 的时候指明了它是一个直接受 Integer

类型的 ArrayList,当我们调用 aList.get(0)时,我们已经不再需要先显式的将结果转换成 Integer,

然后再赋值给myInteger了。而这一步在早先的 Java版本中是必须的。也许你在想,在使用Collection

时节约一些类型转换就是 Java 泛型的全部吗?远不止。单就这个例子而言,泛型至少还有一个更

大的好处,那就是使用了泛型的容器类变得更加健壮:早先,Collection 接口的 get()和 Iterator 接

口的 next()方法都只能返回 Object 类型的结果,我们可以把这个结果强制转换成任何 Object 的子

类,而不会有任何编译期的错误,但这显然很可能带来严重的运行期错误,因为在代码中确定从

某个 Collection 中取出的是什么类型的对象完全是调用者自己说了算,而调用者也许并不清楚放进

Collection 的对象具体是什么类的;就算知道放进去的对象“应该”是什么类,也不能保证放到

Collection 的对象就一定是那个类的实例。现在有了泛型,只要我们定义的时候指明该 Collection 2005 年 1 月 1 期 总第 2 期

- 49 -

Page 55: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

接受哪种类型的对象,编译器可以帮我们避免类似的问题溜到产品中。我们在实际工作中其实已

经看到了太多的 ClassCastException,不是吗?

泛型的使用从这个例子看也是相当易懂。我们在定义 ArrayList 时,通过类名后面的<>括号中

的值指定这个 ArrayList 接受的对象类型。在编译的时候,这个 ArrayList 会被处理成只接受该类或

其子类的对象,于是任何试图将其他类型的对象添加进来的语句都会被编译器拒绝。

那么泛型是怎样定义的呢?看看下面这一段示例代码:(其中用 E 代替在实际中将会使用的类

名,当然你也可以使用别的名称,习惯上在这里使用大写的 E,表示 Collection 的元素。)

public class TestGenerics<E> {

Collection<E> col;

public void doSth(E elem) {

col.add(elem);

// ...

}

}

在泛型的使用中,有一个很容易有的误解,那就是既然 Integer 是从 Object 派生出来的,那么

ArrayList<Integer>当然就是 ArrayList<Object>的子类。真的是这样吗?我们仔细想一想就会发现这

样做可能会带来的问题:如果我们可以把 ArrayList<Integer>向上转型为 ArrayList<Object>,那么

在往这个转了型以后的 ArrayList 中添加对象的时候,我们岂不是可以添加任何类型的对象(因为

Object 是所有对象的公共父类)?这显然让我们的 ArrayList<Integer>失去了原本的目的。于是 Java

编 译 器 禁 止 我 们 这 样 做 。 那 既 然 是 这 样 , ArrayList<Integer> 以 及 ArrayList<String> 、

ArrayList<Double>等等有没有公共的父类呢?有,那就是 ArrayList<?>。?在这里叫做通配符。我

们为了缩小通配符所指代的范围,通常也需要这样写:ArrayList<? extends SomeClass>,这样写的

含义是定义这样一个类 ArrayList,比方说 SomeClass 有 SomeExtendedClass1 和 SomeExtendedClass2

这两个子类,那么 ArrayList<? extends SomeClass>就是如下几个类的父类:ArrayList<SomeClass>、

ArrayList<SomeExtendedClass1>和 ArrayList<SomeExtendedClass2>。

接下来我们更进一步:既然 ArrayList<? extends SomeClass>是一个通配的公用父类,那么我们

可 不 可 以 往 声 明 为 ArrayList<? extends SomeClass> 的 ArrayList 实 例 中 添 加 一 个

SomeExtendedClass1 的对象呢?答案是不能。甚至你不能添加任何对象。为什么?因为 ArrayList<?

extends SomeClass>实际上代表了所有 ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和

2005 年 1 月 1 期 总第 2 期

- 50 -

Page 56: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

ArrayList<SomeExtendedClass2>三种 ArrayList,甚至包括未知的接受 SomeClass 其他子类对象的

ArrayList。我们拿到一个定义为 ArrayList<? extends SomeClass>的 ArrayList 的时候,我们并不能

确定这个 ArrayList 具体是使用哪个类作为参数定义的,因此编译器也无法让这段代码编译通过。

举例来讲,如果我们想往这个 ArrayList 中放一个 SomeExtendedClass2 的对象,我们如何保证它实

际上不是其他的如 ArrayList<SomeExtendedClass1>,而就是这个 ArrayList<SomeExtendedClass2>

呢?(还记得吗?ArrayList<Integer>并非 ArrayList<Object>的子类。)怎么办?我们需要使用泛型

方法。泛型方法的定义类似下面的例子:

public static <T extends SomeClass> void add (Collection<T> c, T elem) {

c.add(elem);

}

其中 T 代表了我们这个方法期待的那个 终的具体的类,相关的声明必须放在方法签名中紧

靠返回类型的位置之前。在本例中,它可以是 SomeClass 或者 SomeClass 的任何子类,其说明<T

entends SomeClass>放在 void 关键字之前(只能放在这里)。这样我们就可以让编译器确信当我们

试图添加一个元素到泛型的 ArrayList 实例中时,可以保证类型安全。

Java 泛型的 大特点在于它是在语言级别实现的,区别于 C# 2.0 中的 CLR 级别。这样的做法

使得 JRE 可以不必做大的调整,缺点是无法支持一些运行时的类型甄别。一旦编译,它就被写死

了,能提供的动态能力相当弱。

个人认为泛型是这次 J2SE(TM) 5.0 中引入的 重要的语言元素,给 Java 语言带来的影响也是

大。举个例子来讲,我们可以看到,几乎所有的 Collections API 都被更新成支持泛型的版本。这

样做带来的好处是显而易见的,那就是减少代码重复(不需要提供多个版本的某一个类或者接口

以支持不同类的对象)以及增强代码的健壮性(编译期的类型安全检查)。不过如何才能真正利用

好这个特性,尤其是如何实现自己的泛型接口或类供他人使用,就并非那么显而易见了。让我们

一起在使用中慢慢积累。

1.5. 增强的 for 循环

你是否已经厌倦了每次写 for 循环时都要写上那些机械的代码,尤其当你需要遍历数组或者

Collection,如:(假设在 Collection 中储存的对象是 String 类型的)

public void showAll (Collection c) {

for (Iterator iter = c.iterator(); iter.hasNext(); ) {

2005 年 1 月 1 期 总第 2 期

- 51 -

Page 57: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 52 -

System.out.println((String) iter.next());

}

}

public void showAll (String[] sa) {

for (int i = 0; i < sa.length; i++) {

System.out.println(sa[i]);

}

}

这样的代码不仅显得臃肿,而且容易出错,我想我们大家在刚开始接触编程时,尤其是 C/C++

和 Java,可能多少都犯过以下类似错误的一种或几种:把 for 语句的三个表达式顺序弄错;第二个

表达式逻辑判断不正确(漏掉一些、多出一些、甚至死循环);忘记移动游标;在循环体内不小心

改变了游标的位置等等。为什么不能让编译器帮我们处理这些细节呢?在 5.0 中,我们可以这样写:

public void showAll (Collection c) {

for (Object obj : c) {

System.out.println((String) obj);

}

}

public void showAll (String[] sa) {

for (String str : sa) {

System.out.println(str);

}

}

这样的代码显得更加清晰和简洁,不是吗?具体的语法很简单:使用":"分隔开,前面的部分

写明从数组或 Collection 中将要取出的类型,以及使用的临时变量的名字,后面的部分写上数组或

者 Collection 的引用。加上泛型,我们甚至可以把第一个方法变得更加漂亮:

public void showAll (Collection<String> cs) {

for (String str : cs) {

System.out.println(str);

}

}

有没有发现:当你需要将 Collection<String>替换成 String[],你所需要做的仅仅是简单的把参

数类型"Collection<String>"替换成"String[]",反过来也是一样,你不完全需要改其他的东西。这在

J2SE(TM) 5.0 之前是无法想象的。

对于这个看上去相当方便的新语言元素,当你需要在循环体中访问游标的时候,会显得很别

扭:比方说,当我们处理一个链表,需要更新其中某一个元素,或者删除某个元素等等。这个时

Page 58: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

候,你无法在循环体内获得你需要的游标信息,于是需要回退到原先的做法。不过,有了泛型和

增强的 for 循环,我们在大多数情况下已经不用去操心那些烦人的 for 循环的表达式和嵌套了。毕

竟,我们大部分时间都不会需要去了解游标的具体位置,我们只需要遍历数组或 Collection,对吧?

1.6. 自动装箱/自动拆箱

所谓装箱,就是把值类型用它们相对应的引用类型包起来,使它们可以具有对象的特质,如

我们可以把 int 型包装成 Integer 类的对象,或者把 double 包装成 Double,等等。所谓拆箱,就是

跟装箱的方向相反,将 Integer 及 Double 这样的引用类型的对象重新简化为值类型的数据。

在 J2SE(TM) 5.0 发布之前,我们只能手工的处理装箱和拆箱。也许你会问,为什么需要装箱

和拆箱?比方说当我们试图将一个值类型的数据添加到一个 Collection 中时,就需要先把它装箱,

因为 Collection 的 add()方法只接受对象;而当我们需要在稍后将这条数据取出来,而又希望使用

它对应的值类型进行操作时,我们又需要将它拆箱成值类型的版本。现在,编译器可以帮我们自

动地完成这些必要的步骤。下面的代码我提供两个版本的装箱和拆箱,一个版本使用手工的方式,

另一个版本则把这些显而易见的代码交给编译器去完成:

public static void manualBoxingUnboxing(int i) {

ArrayList<Integer> aList = new ArrayList<Integer>();

aList.add(0, new Integer(i));

int a = aList.get(0).intValue();

System.out.println("The value of i is " + a);

}

public static void autoBoxingUnboxing(int i) {

ArrayList<Integer> aList = new ArrayList<Integer>();

aList.add(0, i);

int a = aList.get(0);

System.out.println("The value of i is " + a);

}

看到了吧,在 J2SE(TM) 5.0 中,我们不再需要显式的去将一个值类型的数据转换成相应的对

象,从而把它作为对象传给其他方法,也不必手工的将那个代表一个数值的对象拆箱为相应的值

类型数据,只要你提供的信息足够让编译器确信这些装箱/拆箱后的类型在使用时是合法的:比方

讲,如果在上面的代码中,如果我们使用的不是 ArrayList<Integer>而是 ArrayList 或者其他不兼容

的版本如 ArrayList<java.util.Date>,会有编译错误。

2005 年 1 月 1 期 总第 2 期

- 53 -

Page 59: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

当然,你需要足够重视的是:一方面,对于值类型和引用类型,在资源的占用上有相当大的

区别;另一方面,装箱和拆箱会带来额外的开销。在使用这一方便特性的同时,请不要忘记了背

后隐藏的这些也许会影响性能的因素。

2005 年 1 月 1 期 总第 2 期

- 54 -

1.7. 类型安全的枚举

在介绍 J2SE(TM) 5.0 中引入的类型安全枚举的用法之前,我想先简单介绍一下这一话题的背

景。

我们知道,在 C 中,我们可以定义枚举类型来使用别名代替一个集合中的不同元素,通常是

用于描述那些可以归为一类,而又具备有限数量的类别或者概念,如月份、颜色、扑克牌、太阳

系的行星、五大洲、四大洋、季节、学科、四则运算符,等等。它们通常看上去是这个样子:

typedef enum {SPRING, SUMMER, AUTUMN, WINTER} season;

实质上,这些别名被处理成 int 常量,比如 0 代表 SPRING,1 代表 SUMMER,以此类推。因

为这些别名 终就是 int,于是你可以对它们进行四则运算,这就造成了语意上的不明确。

Java 一开始并没有考虑引入枚举的概念,也许是出于保持 Java 语言简洁的考虑,但是使用 Java

的广大开发者对于枚举的需求并没有因为 Java 本身没有提供而消失,于是出现了一些常见的适用

于 Java 的枚举设计模式,如 int enum 和 typesafe enum,还有不少开源的枚举 API 和不开源的内部

实现。

我大致说一下 int enum 模式和 typesafe enum 模式。所谓 int enum 模式就是模仿 C 中对 enum

的实现,如:

public class Season {

public static final int SPRING = 0;

public static final int SUMMER = 1;

public static final int AUTUMN = 2;

public static final int WINTER = 3;

}

这种模式跟 C 中的枚举没有太多本质上的区别,C 枚举的局限它基本上也有。而 typesafe enum

模式则要显得健壮得多:

public class Season {

private final String name;

private Season(String name) {

Page 60: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 55 -

this.name = name;

}

public String toString() {

return name;

}

public static final Season SPRING = new Season("spring");

public static final Season SUMMER = new Season("summer");

public static final Season AUTUMN = new Season("autumn");

public static final Season WINTER = new Season("winter");

}

后一种实现首先通过私有的构造方法阻止了对该类的继承和显式实例化,因而我们只可能取

得定义好的四种 Season 类别,并且提供了方便的 toString()方法获取有意义的说明,而且由于这是

一个完全意义上的类,所以我们可以很方便的加入自己的方法和逻辑来自定义我们的枚举类。

终,Java 决定拥抱枚举,在 J2SE(TM) 5.0 中,我们看到了这一变化,它所采用的设计思路

基本上就是上面提到的 typesafe enum 模式。它的语法很简单,用一个实际的例子来说,要定义一

个枚举,我们可以这样写:

public enum Language {CHINESE, ENGLISH, FRENCH, HUNGARIAN}

接下来我们就可以通过 Language.ENGLISH 来使用了。呃…这个例子是不是有点太小儿科了,

我们来看一个复杂点的例子。使用 Java 的类型安全枚举,我们可以为所有枚举元素定义公用的接

口,然后具体到每个元素本身,可以针对这些接口实现一些特定的行为。这对于那些可以归为一

类,又希望能通过统一的接口访问的不同操作,将会相当方便。通常,为了实现类似的功能,我

们需要自己来维护一套继承关系或者类似的枚举模式。这里借用 Java 官方网站上的一个例子:

public enum Operation {

PLUS { double eval(double x, double y) { return x + y; } },

MINUS { double eval(double x, double y) { return x - y; } },

TIMES { double eval(double x, double y) { return x * y; } },

DIVIDE { double eval(double x, double y) { return x / y; } };

// Do arithmetic op represented by this constant

abstract double eval(double x, double y);

}

在这个枚举中,我们定义了四个元素,分别对应加减乘除四则运算,对于每一种运算,我们

都可以调用 eval()方法,而具体的方法实现各异。我们可以通过下面的代码来试验上面这个枚举类:

public static void main(String args[]) {

double x = Double.parseDouble(args[0]);

double y = Double.parseDouble(args[1]);

for (Operation op : Operation.values()) {

Page 61: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 56 -

System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y));

}

}

怎么样,使用枚举,我们是不是能够很方便的实现一些有趣的功能?其实说穿了,Java 的类

型安全枚举就是包含了有限数量的已生成好的自身实例的一种类,这些现成的实例可以通过类的

静态字段来获取。

1.8. 可变长度参数

顾名思义,可变长度参数就是指在方法的参数体中,只要定义恰当,我们可以使用任意数量

的参数,类似于使用数组。在 J2SE(TM) 5.0 中,一个新的语法被引入,就是在参数类型名称后面

加上"...",表示该方法可以接受多个该类型的参数。需要说明的是可变长度参数必须放在参数列表

的 后,且一个方法只能包含一个这样的参数。在方法体内部,这样的参数被当作数组处理,看

上去代码应该类似这个样子:

public String testVararg(String... args) {

StringBuilder sb = new StringBuilder();

for (String str : args) {

sb.append(str);

}

return sb.toString();

}

这样的方法签名跟你写成 testVararg(String[] args)的区别在于:在调用时,你不再需要传入一

个包装好的 String 数组,你只需要简单的写一连串 String 参数,以逗号隔开即可,就如同这个方法

正好有一个重载的版本是接受那么多个 String 参数一样。

1.9. 静态引入

所谓静态引入就是指除了引入类之外,我们现在又多了一种选择:引入某个类的静态字段。

如:

import static java.lang.Math.PI;

或者

import static java.lang.Math.*;

这样我们在接下来的代码中,当我们需要使用某个被引入的静态字段时,就不用再写上前面

的类名了。当然,出现名字冲突时,跟原来的类引入一样,还是需要前缀以示区分。我个人认为

这个新语言元素意义不大。当引入太多静态字段后,代码会变得难以阅读和维护。由于静态字段

Page 62: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

的名字通常不如类名那么具有描述性,我认为原先在静态字段前写上类名才是更好的选择。不过,

毕竟每个人的喜好和需求不同,如果你觉得它对你有用,既然提供了,那么就用咯。

1.10. 元数据(注解)

注解是 J2SE(TM) 5.0 引入的重要语言元素,它所对应的 JSR 是 JSR 175,我们先来看看 JSR 175

的文档对注解的说明:

注解不会直接影响程序的语义,而开发和部署工具则可以读取这些注解信息,并作相应处理,

如生成额外的 Java 源代码、XML 文档、或者其他将与包含注解的程序一起使用的物件。

在之前的 J2SE 版本中,我们已经使用到了一部分早期的注解元素,如@deprecated 等。这些

元素通常被用于产生 HTML 的 Javadoc。在 J2SE(TM) 5.0 中,注解被正式引入,且推到了 Java 历

史上前所未有的高度。

现在,注解不仅仅被用来产生 Javadoc,更重要的,注解使得代码的编译期检查更加有效和方

便,同时也增强了代码的描述能力。有一些注解是随着 J2SE(TM) 5.0 一起发布的,我们可以直接

使用。除此之外,我们也可以很方便的实现自定义的注解。在此基础上,很多以前我们只能靠反

射机制来完成的功能也变得更加容易实现。

我们来看现成的有哪些有用的注解:

首先是@Override,这个注解被使用在方法上,表明这个方法是从其父类继承下来的,这样的

写法可以很方便的避免我们在重写继承下来的方法时,不至于不小心写错了方法签名,且悄悄的

溜过了编译器,造成隐蔽性相当高的 bug。

其次是@Deprecated,表明该项(类、字段、方法)不再被推荐使用。

还有一个@SuppressWarnings,表明该项(类、字段、方法)所涵盖的范围不需要显示所有的

警告信息。这个注解需要提供参数,如 unchecked 等等。

2005 年 1 月 1 期 总第 2 期

- 57 -

Page 63: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

下面我通过一个例子向大家说明这些现成的注解的用法:

public class Main {

@Deprecated

public String str;

public static void main(String[] args) {

new SubMain().doSomething();

}

public void doSomething() {

System.out.println("Done.");

}

}

class SubMain extends Main {

@Override

@SuppressWarnings("unchecked", "warning")

public void doSomething() {

java.util.ArrayList aList = new java.util.ArrayList();

aList.add(new Integer(0));

System.out.println("Done by SubMain.");

}

}

当然,我们也完全可以写自己的注解。注解定义的语法是@interface 关键字。J2SE(TM) 5.0 支

持三种形式的注解:不带参数的标记注解、带一个参数的注解和带多个参数的完整注解。下面分

别举例说明:

标记注解,类似@Deprecated,如:

@interface SomeEmptyAnnotation {}

单个参数的注解,如:

@interface MySingleElementAnnotation {

String value();

}

以及多个参数的注解,如:

@interface MyAnnotationForMethods {

int index();

String info();

String developer() default "Sean GAO";

}

2005 年 1 月 1 期 总第 2 期

- 58 -

Page 64: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

我们可以看到,注解的定义跟 interface 的定义相当类似,我们还可以指定默认值。对于这些

注解,我们也可以为其添加注解,所谓“注解的注解”。比方讲,我们通常会使用@Target 指定注

解的作用对象,以及用@Retention 指定注解信息写入的级别,如源代码、类文件等等。举个例子:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface SignedMethod {

}

在使用时,我们需要在注解名称前面写上@,然后()中指定参数值,如:

@MyAnnotationForMethods (

index = 1,

info = "This is a method to test MyAnnotation.",

developer = "Somebody else"

)

public void testMethod1() {

// ...

}

注解的 大作用在于它在源代码的基础上增加了有用的信息,使得源代码的描述性更强。这

些信息可以被代码之外的工具识别,从而可以很方便的增加外部功能,以及减少不必要的相关代

码/文件的维护。这里我想简单提一个超出 J2SE(TM) 5.0 范畴的话题:在未来的 EJB 3.0 规范中会

有相当多的对注解的应用,让我们预览一下将来的无状态会话 bean 用注解来定义会是什么样子:

@Stateless public class BookShelfManagerBean {

public void addBook(Book aBook) {

// business logic goes here...

}

public Collection getAllBooks() {

// business logic goes here...

}

// ...

}

我们甚至不用写任何接口和部署描述符,这些工作将完全由外部工具通过读取注解加上反射

来完成,这不是很好吗?

1.11. C 风格格式化输出

Java 总算也有类似 C 的 printf()风格的方法了,方法名同样叫作 printf(),这一特性依赖于前边

提到的可变长度参数。举个例子来说,我们现在可以写:

System.out.printf("%s has a value of %d.%n", someString, a);

2005 年 1 月 1 期 总第 2 期

- 59 -

Page 65: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

怎么样,看上去还不错吧?需要注意的是 Java 为了支持多平台,新增了%n 标示符,作为对\n

的补充。有关 Java 格式化输出的具体语法,请参考 java.util.Formatter 的 API 文档。

1.12. 结语

在这一篇介绍性的文章中,我们一起领略了 J2SE 5.0 带来的新的语言元素,不知道大家是否

也跟笔者一样,感受到了这些新特性在提高我们的开发效率上所作的巨大努力。其实不只是语言

元素,J2SE(TM) 5.0 的发布在其他很多方面都作了不小的改进,包括虚拟机、新的 API 类库等等,

性能和功能上都有大幅提升。

对于主要靠 J2EE 吃饭的朋友来讲,也许真正意义上要在工作中充分利用这些新的元素,恐怕

要等主流的 J2EE 服务器都支持 J2EE(TM) 5.0 的那一天了,对此我充满期待。

2005 年 1 月 1 期 总第 2 期

- 60 -

Page 66: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

J2SE 5.0 新特性 之 线程

作者:汪泓([email protected]

1.1. 进程、线程与线程池

所谓进程是一种在自身定址空间中执行的相对独立的程序,是现代操作系统的基石。现在的

多任务操作系统,会周期性地将 CPU 的时间划分给每一个进程,使操作系统得以同时执行一个以

上的程序。

线程则是进程中的一个“单一连续控制的流程”,一个进程中可以拥有多个并行的线程。但线

程不能单独存在,它依附于进程,只能从进程中派生而来。如果一个进程派生出了两个线程,那

这两个线程共享此进程的全局变量和代码段,但每个线程各拥有各自的堆栈,因此它们拥有各自

的局部变量。

在了解了线程的概念后,下面我们就可以进入正题,现在先来看一下线程池究竟是怎么一回

事?其实线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的处理流程如下:先启动

若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中

的某一个睡眠的线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又恢复到睡眠

状态。这种方法的引入,会减少频繁创建与销毁线程所带来的系统负担,从而留出更多的 CPU 时

间和内存来处理实际的应用逻辑。

1.2. Java 的线程概述

在 Java 编程的早期阶段,位于 Oswego 市的纽约州立大学(SUNY) 的一位教授Doug Lea

决定创建一个简单的库,以帮助开发人员构建可以更好地处理多线程情况的应用程序。这并不是

说用现有的库就不能实现,但是就像有了标准网络库一样,用经过调试的、可信任的库更容易自

己处理多线程。在 Addision-Wesley 的一本相关书籍《Concurrent Programming in Java: Design

Principles and Patterns》的帮助下,这个库变得越来越流行了。 终,作者 Doug Lea 决定设法让

它成为 Java 平台的标准部分 —— JSR-166。这个库 后变成了 Tiger 版本的 java.util.concurrent

包。以下我们将针对J2SE(TM)5.0 中引入的关于线程方面的新内容进行详细的介绍。

2005 年 1 月 1 期 总第 2 期

- 61 -

Page 67: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.3. Collection 部分的扩容

1.3.1. Queue 接口

java.util 包为 Collection 提供了一个新的基本接口:java.util.Queue。虽然肯定可以在相对应的

两端进行添加和删除而将 java.util.List 作为队列对待,但是这个新的 Queue 接口提供了支持添

加、删除和检查集合的更多方法,如下所示:

public boolean offer(Object element)

public Object remove()

public Object poll()

public Object element()

public Object peek()

对于队列中大小限制,比如想在一个满的队列中加入一个新项,这时新的 offer 方法就可以

起到相应的作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返

回的 false。remove() 和 poll() 方法都是从队列中删除第一个元素(head)。remove() 的行为与原

有的 Collection 接口相似,但是新的 poll() 在用空集合调用时不是抛出异常,只是返回 null。因

此新的方法更适合更容易出现在有其他异常条件的情况之中。后两个方法 element() 和 peek() 用

于在队列的头部查询元素。与 remove() 方法类似,在队列为空时,element() 抛出一个异常,而

peek() 返回 null。

J2SE(TM)5.0 中,Queue 有两种实现方式:通过实现新增的 BlockingQueue 接口以及直接实

现 Queue 接口。下面是用 LinkedList 作为 Queue 使用的一种方法

1.3.1.1. Queue 的实现

Queue queue = new LinkedList();

queue.offer("1");

queue.offer("2");

queue.offer("3");

queue.offer("4");

System.out.println("Head of queue is: " + queue.poll());

再 复 杂 一 点 的 是 新 的 java.util.AbstractQueue 类 。 这 个 类 的 工 作 方 式 类 似 于

java.util.AbstractList 和 java.util.AbstractSet 类。在创建自定义集合时,不用自己实现整个接口,

只是继承抽象实现并填入细节。使用 AbstractQueue 时,必须为方法 offer()、 poll() 和 peek() 提

供实现。像 add() 和 addAll() 这样的方法修改为使用 offer(),而 clear() 和 remove() 使用 poll()。

2005 年 1 月 1 期 总第 2 期

- 62 -

Page 68: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

后,element() 使用 peek()。当然可以在子类中提供这些方法的优化实现,但是不是必须这么做。

而且,不必创建自己的子类,可以使用几个内置的(什么)实现, 其中两个是不阻塞队列:

PriorityQueue 和 ConcurrentLinkedQueue。

新的 java.util.concurrent 包在 Collection Framework 中可用的具体集合类中加入了

BlockingQueue 接口和五个阻塞队列类。BlockingQueue 接口的 Javadoc 给出了阻塞队列的基本用

法,如下图所示。生产者中的 put() 操作会在没有空间可用时阻塞,而消费者的 take() 操作会在

队列中没有任何东西时阻塞。

1.3.1.2. BlockingQueue 的使用

class Producer implements Runnable {

private final BlockingQueue queue;

Producer(BlockingQueue q) { queue = q; }

public void run() {

try {

while(true) { queue.put(produce()); }

} catch (InterruptedException ex) { ... handle ...}

}

Object produce() { ... }

}

class Consumer implements Runnable {

private final BlockingQueue queue;

Consumer(BlockingQueue q) { queue = q; }

public void run() {

try {

while(true) { consume(queue.take()); }

} catch (InterruptedException ex) { ... handle ...}

}

void consume(Object x) { ... }

}

class Setup {

void main() {

BlockingQueue q = new SomeQueueImplementation();

Producer p = new Producer(q);

Consumer c1 = new Consumer(q);

Consumer c2 = new Consumer(q);

new Thread(p).start();

new Thread(c1).start();

new Thread(c2).start();

2005 年 1 月 1 期 总第 2 期

- 63 -

Page 69: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 64 -

}

}

五个队列所提供的各有不同:

1、 ArrayBlockingQueue:一个由数组支持的有界队列。

2、 LinkedBlockingQueue:一个由链接节点支持的可选有界队列。

3、 PriorityBlockingQueue:一个由优先级堆支持的无界优先级队列。

4、 DelayQueue:一个由优先级堆支持的、基于时间的调度队列。

5、 SynchronousQueue:一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。

前两个类 ArrayBlockingQueue 和 LinkedBlockingQueue 几乎相同,只是在后备存储器方面有

所不同,LinkedBlockingQueue 并不总是有容量界限。无大小界限的 LinkedBlockingQueue 类在添

加元素时永远不会有阻塞队列的等待。新的 DelayQueue 实现可能是其中 有意思的一个了。加

入 到 队 列 中 的 元 素 必 须 实 现 新 的 Delayed 接 口 , 而 且 只 有 一 个 方 法 —— long

getDelay(java.util.concurrent.TimeUnit unit)。因为队列的大小没有界限,使得添加可以立即返回,

但是在延迟时间过去之前,不能从队列中取出元素。如果多个元素完成了延迟,那么 早失效/失

效时间 长的元素将第一个取出,实际上这个实现并不那么复杂。以下程序就是 DelayQueue 的一

个具体实现:

1.3.1.3. DelayQueue 实现

class Setup {

void main() {

BlockingQueue q = new SomeQueueImplementation();

Producer p = new Producer(q);

Consumer c1 = new Consumer(q);

Consumer c2 = new Consumer(q);

new Thread(p).start();

new Thread(c1).start();

new Thread(c2).start();

}

} return ((NanoDelay)other).trigger == trigger;

}

public boolean equals(NanoDelay other) {

return ((NanoDelay)other).trigger == trigger;

}

public long getDelay(TimeUnit unit) {

long n = trigger - System.nanoTime();

return unit.convert(n, TimeUnit.NANOSECONDS);

Page 70: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 65 -

}

public long getTriggerTime() {

return trigger;

}

public String toString() {

return String.valueOf(trigger);

}

}

public static void main(String args[]) throws InterruptedException {

Random random = new Random();

DelayQueue queue = new DelayQueue();

for (int i=0; i < 5; i++) {

queue.add(new NanoDelay(random.nextInt(1000)));

}

long last = 0;

for (int i=0; i < 5; i++) {

NanoDelay delay = (NanoDelay)(queue.take());

long tt = delay.getTriggerTime();

System.out.println("Trigger time: " + tt);

if (i != 0) {

System.out.println("Delta: " + (tt - last));

}

last = tt;

}

}

}

这个例子首先是一个内部类 NanoDelay,它实质上将暂停给定的任意纳秒(nanosecond)数,

这里利用了 System 的新 nanoTime() 方法。然后 main() 方法只是将 NanoDelay 对象放到队列中

并再次将它们取出来。如果希望队列项做一些其他事情,就需要在 Delayed 对象的实现中加入方

法,并在从队列中取出后调用这个新方法。(请随意扩展 NanoDelay 以试验加入其他方法做一些

有趣的事情。)显示从队列中取出元素的两次调用之间的时间差。如果时间差是负数,可以视为一

个错误,因为永远不会在延迟时间结束后,在一个更早的触发时间从队列中取得项。

SynchronousQueue 类是 简单的。它没有内部容量。它就像线程之间的手递手机制。在队列中加

入一个元素的生产者会等待另一个线程的消费者。当这个消费者出现时,这个元素就直接在消费

者和生产者之间传递,永远不会加入到阻塞队列中。

1.3.2. List、Set、Map 接口

新的 java.util.concurrent.ConcurrentMap 接口和 ConcurrentHashMap具体类扩展了先前的Map

Page 71: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

接口,而 ConcurrentHashMap 是对 ConcurrentMap 的直接的具体实现。新的接口增加了一组线程安

全相关的基本操作:putIfAbsent,remove,replace。 putIfAbsent() 方法用于在 map 中进行添加。

这个方法以要添加到 ConcurrentMap 实现中的键的值为参数,就像普通的 put() 方法,但是只有

在 map 不包含这个键时,才能将键加入到 map 中。如果 map 已经包含这个键,那么这个键的

现有值就会保留。像 putIfAbsent() 方法一样,重载后的 remove() 方法有两个参数 —— 键和值。

在调用时,只有当键映射到指定的值时才从 map 中删除这个键。如果不匹配,那么就不删除这个

键,并返回 false。如果值匹配键的当前映射内容,那么就删除这个键。

对于新的 CopyOnWriteArrayList 和 CopyOnWriteArraySet 类,所有可变的(mutable)操作

都首先取得后台数组的副本,对副本进行更改,然后替换副本。这种做法保证了在遍历自身更改

的集合时,永远不会抛出 ConcurrentModificationException。遍历集合会用原来的集合完成,而在

以后的操作中使用更新后的集合。这些新的集合,CopyOnWriteArrayList 和 CopyOnWriteArraySet,

适合于读操作通常大大超过写操作的情况。

1.4. 线程池

就线程池的实际实现方式而言,术语“线程池”有些使人误解,因为线程池“明显的”实现

在大多数情形下并不一定产生我们希望的结果。术语“线程池”先于 Java 平台出现,因此它可能

是较少面向对象方法的产物。然而,该术语仍继续广泛应用着。

我们通常想要的是同一组固定的工作线程相结合的工作队列,它使用 wait() 和 notify() 来通

知等待线程新的工作已经到达了。该工作队列通常被实现成具有相关监视器对象的某种链表。以

下代码实现了具有线程池的工作队列。

public class WorkQueue

{

private final int nThreads;

private final PoolWorker[] threads;

private final LinkedList queue;

public WorkQueue(int nThreads)

{

this.nThreads = nThreads;

queue = new LinkedList();

threads = new PoolWorker[nThreads];

for (int i=0; i<nThreads; i++) {

threads[i] = new PoolWorker();

2005 年 1 月 1 期 总第 2 期

- 66 -

Page 72: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 67 -

threads[i].start();

}

}

public void execute(Runnable r) {

synchronized(queue) {

queue.addLast(r);

queue.notify();

}

}

private class PoolWorker extends Thread {

public void run() {

Runnable r;

while (true) {

synchronized(queue) {

while (queue.isEmpty()) {

try

{

queue.wait();

}

catch (InterruptedException ignored)

{

}

}

r = (Runnable) queue.removeFirst();

}

// If we don't catch RuntimeException,

// the pool could leak threads

try {

r.run();

}

catch (RuntimeException e) {

// You might want to log something here

}

}

}

}

}

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建

的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,

Page 73: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

在 J2SE(TM)5.0 中,Doug Lea 编写了一个优秀的并发实用程序开放源码库 util.concurrent,它包

括互斥、信号量、诸如在并发访问下执行得很好的队列和散列表之类集合类以及几个工作队列实

现。该包中的 PooledExecutor 类是一种有效的、广泛使用的以工作队列为基础的线程池的正确实

现。Util.concurrent 定义一个 Executor 接口,以异步地执行 Runnable,另外还定义了 Executor 的

几个实现,它们具有不同的调度特征。将一个任务排入 executor 的队列非常简单:

Executor executor = new QueuedExecutor();

...

Runnable runnable = ... ;

executor.execute(runnable);

PooledExecutor 是一个复杂的线程池实现,它不但提供工作线程(worker thread)池中任务的

调度,而且还可灵活地调整池的大小,同时还提供了线程生命周期管理,这个实现可以限制工作

队列中任务的数目,以防止队列中的任务耗尽所有可用内存,另外还提供了多种可用的关闭和饱

和度策略(阻塞、废弃、抛出、废弃 老的、在调用者中运行等)。所有的 Executor 实现为您管

理线程的创建和销毁,包括当关闭 executor 时,关闭所有线程,

有时您希望异步地启动一个进程,同时希望在以后需要这个进程时,可以使用该进程的结果。

FutureResult 实用程序类使这变得很容易。FutureResult 表示可能要花一段时间执行的任务,并且

可以在另一个线程中执行此任务,FutureResult 对象可用作执行进程的句柄。通过它,您可以查明

该任务是否已经完成,可以等待任务完成,并检索其结果。可以将 FutureResult 与 Executor 组合

起来;可以创建一个 FutureResult 并将其排入 executor 的队列,同时保留对 FutureResult 的引用。

下面实例演示了一个一同使用 FutureResult 和 Executor 的简单示例,它异步地启动图像着色,并

继续进行其它处理:

1.4.1. FutureResult 实例

Executor executor = ...

ImageRenderer renderer = ...

FutureResult futureImage = new FutureResult();

Runnable command = futureImage.setter(new Callable() {

public Object call() { return renderer.render(rawImage); }

});

// start the rendering process

executor.execute(command);

2005 年 1 月 1 期 总第 2 期

- 68 -

Page 74: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 69 -

// do other things while executing

drawBorders();

drawCaption();

// retrieve the future result, blocking if necessary

drawImage((Image)(futureImage.get())); // use future

还可以使用 FutureResult 来提高按需装入高速缓存的并发性。通过将 FutureResult 放置在高

速缓存内,而不是放置计算本身的结果,可以减少持有高速缓存上写锁的时间。虽然这种做法不

能加快第一个线程把某一项放入高速缓存,但它将减少第一个线程阻塞其它线程访问高速缓存的

时间。它还使其它线程更早地使用结果,因为它们可以从高速缓存中检索 FutureTask。以下是使

用用于高速缓存的 FutureResult 示例:

1.4.2. 使用 FutureResult 来改善高速缓存

public class FileCache {

private Map cache = new HashMap();

private Executor executor = new PooledExecutor();

public void get(final String name) {

FutureResult result;

synchronized(cache) {

result = cache.get(name);

if (result == null) {

result = new FutureResult();

executor.execute(result.setter(new Callable() {

public Object call() { return loadFile(name); }

}));

cache.put(result);

}

}

return result.get();

}

}

这种方法使第一个线程快速地进入和退出同步块,使其它线程与第一个线程一样快地得到第

一个线程计算的结果,不可能出现两个线程都试图计算同一个对象。

Page 75: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.5. 结束语

线程在各个应用领域里占据着举足轻重的地位,而且它在实际应用中的复杂程度非只言片语

能够讲清楚的,这里仅仅介绍了在 J2SE(TM)5.0 中新加入的成分,如读者需要更深入的了解或更

全面地学习线程的话,请参阅相关专业书籍。

2005 年 1 月 1 期 总第 2 期

- 70 -

Page 76: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

MetaData Programme

1.1. 什么是元数据编程

什么是元数据,元数据就是描述数据的数据(data about data)。 明显的例子是 XML Schema,

xml schema 就是描述 xml 的数据,所以它是元数据。另一个例子是数据库,比如我们可以查询数

据库中有几个表,每个表都有什么字段,这些数据就是元数据。

在开发的世界里,元数据就是能够绑定到一个类的附加信息,在静态或者运行时间。JCR175

给我们提供 annotation 就是一种元数据。

不过在这之前一个我们已经广泛使用的元数据是 XML,如就是 EJB 的 XML 发布描述符中,

你需要定义基于每一个方法的事务属性。应用服务器指导什么时候,什么地方开始,挂起或者提

交一个事务,因为你在 BEAN 的 XML 的配置文件中的元数据内已经定义如方法:Required,

RequiresNew,Support 等等,它们绑定在你的 EJB 类和事务管理之间。XDoclet 是另一个元数据的

例子。

1.2. Annotation 的意义和简单例子

JDK1.5 提供的 annotation 与我们所常见的 classes、fieldss 和 methods 间是什么关系。如下:如

果说类和数据成员是名词,方法是动词,那么 annotation 就是形容词或者副词,分别描述它们的所

具有属性。

好,现在就来实现一个 annotation

import java.lang.annotation.Retention;

package sample.annotation;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)

public @interface Broker {

String name();

String address();

}

}

使用这个 annotation

Import sample.annotation.broker;

2005 年 1 月 1 期 总第 2 期

- 71 -

Page 77: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 72 -

@Broker(name="anders", address="xiamen")

public class Agent {

public String getTelPhone (){

return "010-0592-2519280";

}

}

运行期得到这个 annotation

public class Main {

public static void main(String[] args){

Agent agent = new Agent();

try{

Annotation[] a =

agent.getClass().getMethod("getBrokerName").getAnnotations();

for (int i=0; i<a.length ; i++) {

if( a[i] instanceof Broker){

Broker broker = (Broker)a[i];

System.out.println(broker.name());

}

}

}

catch(Exception e){

e.printStackTrace(System.out);

}

}

}

1.3. Annotation 的 class 文件格式

利用 sun 公司的提供的 javap,我们可以看到 annotation 的在 class 文件中的表示。以下为对比

结果:

源码:

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)

public @interface Broker {

String name();

String address();

}

Javap 结果:

Compiled from "Broker.java"

interface Broker extends java.lang.annotation.Annotation

SourceFile: "Broker.java"

minor version: 0

major version: 0

Page 78: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 73 -

Constant pool:

const #1 = class #9; // Broker

const #2 = class #10; // Object

const #3 = class #11; //Annotation

const #4 = Asciz name;

const #5 = Asciz ()Ljava/lang/String;;

const #6 = Asciz address;

const #7 = Asciz SourceFile;

const #8 = Asciz Broker.java;

const #9 = Asciz Broker;

const #10 = Asciz java/lang/Object;

const #11 = Asciz java/lang/annotation/Annotation;

{

public abstract java.lang.String name();

public abstract java.lang.String address();

}

源码:

public interface Broker {

String name();

String address();

}

Javap 结果:

Compiled from "Broker.java"

interface Broker

SourceFile: "Broker.java"

minor version: 0

major version: 0

Constant pool:

const #1 = class #8; // Broker

const #2 = class #9; // Object

const #3 = Asciz name;

const #4 = Asciz ()Ljava/lang/String;;

const #5 = Asciz address;

const #6 = Asciz SourceFile;

const #7 = Asciz Broker.java;

const #8 = Asciz Broker;

const #9 = Asciz java/lang/Object;

{

public abstract java.lang.String name();

Page 79: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 74 -

public abstract java.lang.String address();

}

源码:

@Broker(name="anders", address="xiamen")

public class Agent {

public String getTelPhone(){

return "0592-2519580";

}

}

Javap 结果:

Compiled from "Agent.java"

public class Agent extends java.lang.Object

SourceFile: "Agent.java"

RuntimeVisibleAnnotations: length = 0x10

00 01 00 11 00 02 00 12 73 00 13 00 14 73 00 15

minor version: 0

major version: 0

Constant pool:

const #1 = Method #4.#22; // java/lang/Object."<init>":()V

const #2 = String #23; // 0592-2519580

const #3 = class #24; // Agent

const #4 = class #25; // Object

const #5 = Asciz <init>;

const #6 = Asciz ()V;

const #7 = Asciz Code;

const #8 = Asciz LineNumberTable;

const #9 = Asciz LocalVariableTable;

const #10 = Asciz this;

const #11 = Asciz LAgent;;

const #12 = Asciz getTelPhone;

const #13 = Asciz ()Ljava/lang/String;;

const #14 = Asciz SourceFile;

const #15 = Asciz Agent.java;

const #16 = Asciz RuntimeVisibleAnnotations;

const #17 = Asciz LBroker;;

const #18 = Asciz name;

const #19 = Asciz anders;

const #20 = Asciz address;

const #21 = Asciz xiamen;

Page 80: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 75 -

const #22 = NameAndType #5:#6;// "<init>":()V

const #23 = Asciz 0592-2519580;

const #24 = Asciz Agent;

const #25 = Asciz java/lang/Object;

// 以下为方法域,略

源码:

public class Agent {

public String getTelPhone(){

return "0592-2519580";

}

}

Javap 结果:

Compiled from "Agent.java"

public class Agent extends java.lang.Object

SourceFile: "Agent.java"

minor version: 0

major version: 0

Constant pool:

const #1 = Method #4.#16; // java/lang/Object."<init>":()V

const #2 = String #17; // 0592-2519580

const #3 = class #18; // Agent

const #4 = class #19; // Object

const #5 = Asciz <init>;

const #6 = Asciz ()V;

const #7 = Asciz Code;

const #8 = Asciz LineNumberTable;

const #9 = Asciz LocalVariableTable;

const #10 = Asciz this;

const #11 = Asciz LAgent;;

const #12 = Asciz getTelPhone;

const #13 = Asciz ()Ljava/lang/String;;

const #14 = Asciz SourceFile;

const #15 = Asciz Agent.java;

const #16 = NameAndType #5:#6;// "<init>":()V

const #17 = Asciz 0592-2519580;

const #18 = Asciz Agent;

const #19 = Asciz java/lang/Object;

// 以下为方法域,略

补充说明:我们都知道在 java 1.0 发布时,java class file 的格式就已经定下来,要说明的是为

Page 81: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

了应未来的需要 java class file 设计了属性说的机制。一直到 j2se1.4 都没有怎么改变。但这次为了

更好的支持 metadata 技术,一共增加了 8 个属性。分別是:

「EnclosingMethod」Attribute :Anonymous Class 或 Local Inner Class 使用此 Attribute 描述

该 Class 的 Scope。

「Signature」 Attribute:Generics 的 Class、Method、或 Filed 使用此 Attribute 来记录所要

的类型,因为 java 的范型采用了擦拭法。

「LocalVariableTypeTable」 Attribute:主要是給 debugger 使用,目的和「LocalVariableTable」

Attribute 类似,只是「LocalVariableTable」 Attribute 记录所要的参数表,而「LocalVariableTypeTable」

Attribute 记录参数的类型。

「RuntimeVisibleAnnotations」 Attribute:确定该 annotation 可以被 reflection 的 API 返回,适

用对象:Class、Method、Field

「RuntimeInvisibleAnnotations」 Attribute:确定该 annotation 无法被 reflection 的 API 返回,

适用对象: Class、Method、Field。

「RuntimeVisibleParameterAnnotations」 Attribute:同「RuntimeVisibleAnnotations」 Attribute,

适用对象:Method,(该 Method 的参数

「 RuntimeInvisibleParameterAnnotations 」 Attribute : 同 「 RuntimeInvisibleAnnotations 」

Attribute,适用对象:Method,(该 Method 的参数。

「AnnotationDefault」 Attribute:适用对象:Method,记录默认值。

1.4. 为什么需要 Annotation

在 annotation 之前我们已经广泛使用另外一种元数据 xml,为什么需要 annotation。Annotation

与 xml 的作为元数据的区别是什么——位置。Annotation 写在源代码中,而 xml 写在外部。

为什么要这样?如果你开发过 EJB,你一定为你的 EJB 写过 xml 描述文件。当大量的 EJB 需

要描述时,就出现了所谓的"descriptor hell"。这个也导致了著名的 XDoclet 的出现。而 annotation

出现可以避免这种 descriptor hell。另外你更改了某个方法为其增加或者减少一个参数,你就对应

的修改 xml 文件,而使用 annotation 则不必。使用 annotation 将开发和部署更方便,提供开发效率。

另外:使用 xml 的另一个问题是:很多 Xml 配置太过 verbose。相比较 EJB 和 Hibernate 或者

Webwork 可以明显的发现不同。

2005 年 1 月 1 期 总第 2 期

- 76 -

Page 82: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

1.5. 再议 Annotation

在 EJB3 中,Annotation 把开发和部署的工作合在一起。但是在一些企业环境中,开发人员并

不控制诸如数据源名等(这些是部署部门和管理部门的工作),这样把数据源名写在 xml 中将比较

好。

Annotation 是本身静态的,一旦添加或者修改 annotation 都需要重新编译,在运行时读取,这

样就丧失了运行时配置的能力。因此 Annotations 不会取代 xml,它只是提供了另一种途径。并且

我相信 sun 公司将在未来提供一个方式可以在运行期更改 metadata。

关于这点 TSS 上有着很激烈的讨论,很多开发人员提出:利用 xml 来更改 annotation,并希望类似

的方式能被采纳为标准规范。比如使用如下格式:

<class name="org.hibernate.Item">

@Entity

@Table(name="AUCTION_ITEM")

<method sig="getBar()">@Transient</method>

<method sig="doSomething(int, String)">

@Tx(REQUIRES_NEW)

</method>

</class>

当然也有不同意见:But then, I think of "overriding" as a different problem to "xml deployment

descriptors", and so I think we need two solutions. I think Cedric and Bill are trying to kill two birds with

one stone, so maybe their proposals are better....

关于为 annotation 提供动态配置能力的问题,其中一个网友认为:Sun make it real pain to do the

deployment XML so that they can introduce annotation to fix it. The annotation can make

code/deployment coupling so strong that Sun can come out with a new way (annotation interceptor in jdk

1.6? :)) for fixing it. and the cycles goes on...这让我想起了类似的现象:JSP和TagLib。希望Annotation

不会和 TagLib 有同样的命运。

Annotation 本身引入另一种类型的接口。在 EJB3 中确实使程序更加 POJO,也消除了一些接

口。并且编译后的代码也可以直接移植到另一个并不处理这些 annotations 的环境中(感谢 VM 在

加载类时并不检查那些 annotations 的 classes,甚至这些类不在 classpath 中)。然而代码也确实增加

了另一些接口。这个表现在编译期,如果没有这些 annotation classes,是编译不过的。

2005 年 1 月 1 期 总第 2 期

- 77 -

另一个问题(还好不算很重要),关于 annotation 的 namespace。在多层应用的系统中,可能会

出现同样的全局 annotation 的 namespace 冲突。比如一些公共的 annotation,如@Transaction,将会

Page 83: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

被应用在很多个层次中。尽量让 namespace 长些,可以避免这个问题。

1.6. 元数据编程的应用:

Annotation 已经被集成到很多的 java 规范和标准中,很重要的是它已经被 J2EE 标准,如 EJB3

所采用,当然也被许多开源的组件体系如:AspectJ。

Annotation 重要的应用将是 AOP:由于 annotation 可以天然的表示系统中的另一个横切面,

同时 Annotation 的识别是通过反射得到的,所以 Annotation 很自然的应用到基于动态代理的 AOP

实现。AOP-Alliance 也支持 metadata handling。AspectJ 也发布了基于 annotation 的新版本。

在实现 AOP 上,使用 annotation 也比使用 XML 有一个优势:如前所述,annotation 更像是形

容词和副词,这样比较不容易 verbose。当然这个是相对的,在实际的实现中更依赖开发人员的努

力。

这里,笔者将展示一个不完整也不成熟的基于 annotation 的 AOP 例子代码——关于银行卡的

例子。

功能需求:银行卡业务分为转帐,查询余额,查询明细,POS 消费等。这其中转帐和 POS 消

费是要收费的(转帐收取的是用户的手续费,而 POS 消费收取的是商家的手续费),另外 POS 消

费还可能有积分的(比如笔者的牡丹贷记卡)。消费转帐都要记录明细。但查询余额就不需要记录

在明细中。

代码如下(在这个例子没有用动态代理也没有用已有的 AOP 框架,使代码看起来简单些)

// 银行卡对象

public class Card {

private String account;

//some field method

}

Annotation:

// 手续费

// type= "user", 表示收取用户手续费; type= "Biz", 表示收取商家手续费

public @interface Fee{

String type();

}

// 积分

public @interface Index {

}

// 记录明细

public @interface BizLog {

}

2005 年 1 月 1 期 总第 2 期

- 78 -

Page 84: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 79 -

// 事务处理

public @interface Transaction {

}

// 业务接口

public interface BizAction {

void execute(Card card, RunData rundata);

}

// 转帐业务

@Fee(type="user")

@Transaction

@BizLog

public class TransferAction implements BizAction {

public void execute(Card card, RunData rundata) {

//To change body of implemented methods use File | Settings | File Templates.

}

}

// POS消费

@Fee(type="Biz")

@Transaction

@BizLog

@Index

public class POSAction implements BizAction {

public void execute(Card card, RunData rundata) {

//To change body of implemented methods use File | Settings | File Templates.

}

}

// 查询明细

public class QueryDetail implements BizAction {

public void execute(Card card, RunData rundata) {

//To change body of implemented methods use File | Settings | File Templates.

}

}

// 业务操作监视器接口

public interface BizActionMonitor {

void execute(BizAction action, RunData rundata);

}

// 业务操作监视器实现

public class BizActionMonitorImpl implements BizActionMonitor{

public void execute(BizAction action, RunData rundata) {

Annotation[] annotations = action.getClass().getAnnotations();

for(Annotation annotation : annotations){

if (annotation instanceof Fee){ // 计算手续费 }

if (annotation instanceof Index){ //计算积分 }

if (annotation instanceof Transaction){ // 准备事务 }

if (annotation instanceof BizLog){ // 记录明细 }

Page 85: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 80 -

}

}

}

// 控制器对象

public class controller{

private BizActionMonitor monitor;

public void execute(BizActionUI, rundata){

BizAction action = getAction(BizActionUI);

monitor.execute(action, rundata);

}

}

// 运行时数据

public interface RunData {

// some method

}

// 用户设备(POS机, ATM或者柜台终端)接口

public class BizActionUI {

private RunData rundata;

private Controller controller;

public BizActionUI(RunData rundata, Controller controller){

this.rundata = rundata;

this.controller = controller;

}

public void execute(){ // 某个子类实现 }

public void commit(){

controller.execute(this, rundata);

}

}

public class Main{

private Rundata rundata;

private Controller controller;

public populateUI(command){

BizActionUI ui = getUI(command);

ui.execute();

}

public BizActionUI getUI(command){

//...

BizActionUI ui

if( //....){

ui = new SomeBizActionUI(rundata, controller);

}

return ui;

}

public static main(String[] args){

//...

Page 86: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net 2005 年 1 月 1 期 总第 2 期

- 81 -

Main main = new Main();

main.populateUI(command)

//...

}

}

1.7. 结束语:

本文讨论了 annotation 技术,展示了 annotation 的 class 文件格式,并讨论了 annotation 技术本

身的优势和不足,并于现有的 xml 技术加以比较,展望了 annotation 技术的应用前景 AOP。

限于笔者自身的水平(包括技术水平和写作水平),技术上对 annotation 的学习比较有限,写作上

也感觉好多话无法用文字来表达,因而本文的代码会比较多(大概有一半以上)。

Page 87: Csdn Java电子杂志第2期

《CSDN 社区电子杂志——Java 杂志》

http://emag.csdn.net

思考题

根据网友的反馈。我们特设立思考题这一栏目,当然还要待读者选择这样的形式。不过编辑

们并不想把这个栏目变得和中学的作业题一样。因而其中一些问题有确切的答案,而另一些则是

仁者见仁,智者见智。如果能让大家阅读之余思考一下,就是编辑们莫大的欢喜了!

本期题目如下:

1、哪些内容适合写在 annotation 里面,哪些不适合?

2、举出 5 个泛型能够帮助工作的场景

3、关于如何控制 annotation 和 xml 的 verbose

4、你是否同意 annotation 的动态配置

如果要,你觉怎么实现好

如果不要,为什么?

5、对于新引入的 Synth 外观你有何看法,并将其与传统的外观主题相比较,试根据具体 项目

实践进行分析 Synth 外观具有哪些优缺点。

6、Integer k1 = 2;

Integer k2 = 2;

System.out.println( k1==k2);

答案是什么? 那么

Integer j1 = 150;

Integer j2 = 150;

System.out.println( j1==j2);

的答案又是什么?如若不同,请分析其原因。

2005 年 1 月 1 期 总第 2 期

- 82 -