Your SlideShare is downloading. ×
Java Web动态图表编程
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Java Web动态图表编程

3,713
views

Published on


0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
3,713
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
31
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. 本书向读者展示如何使用 Java Applet、Java Servlet、Java Server Pages(JSP)、JavaBean 以及开放源代码的 Web 图表生成引擎——JFreeChart 和 Cewolf 来开发奇妙的 Web 动态图表 应用程序——以一种跨平台、小巧、结构清晰的模式在 Web 上生成动态图表。 随着计算机网络及编程技术的发展,使得越来越多的应用程序被移植成以 Web 应用程序 (浏览器/服务器)的方式,工作在因特网/局域网环境之中。网络开发人员发现,某些传统 IDE 开发环境,例如:在 Dephi 和 VB 中可以轻而易举地实现的图形界面,而在 Web 程序中却 很难实现,其中一个典型的例子就是 Web 图表(Chart)的生成与处理。即使 Web 程序(ASP、 ASP.NET 等)能够处理一些图表,也基本是采用 ActiveX 或者 COM 组件的结构。这种方法有 三大弊端: ? 开发 ASP 比较简单,但开发 ActiveX 和 COM 却很难; ? 基于 ActiveX 和 COM 构架的 Web 应用程序只能用于微软 Windows 的 Web 服务器 IIS 环境下, 移植性差; ? 由于 ActiveX 和 COM 与 Windows 深度紧密结合,在带来强大功能的同时,也一并带来了 Windows 固有的缺陷——安全性和稳定性差。 PHP 可以用于 Web 图表处理,它具有跨平台、安全性能高、兼容性及扩展性强的优点,但也 有其固有的缺陷,具体表现为以下几点: ? 效率不高,基于解释型,而非编译型,这一点与 ASP 类似。ASP.NET 吸取了 JSP 的优点, 属于编译型,大大提高了运行速度和效率; ? 安装复杂,每一种扩展模块不完全是由 PHP 自身来完成。当图形需要 GD 库,使用类似的扩 展库时,安装和调试是 PHP 的一大问题; ? 因为是开发源代码产品,所以缺乏企业级的商业支持。 JSP 的出现,使得上述弊端不复存在。随着 Java 对二维图形及三维图像的处理能力越来 越强大,利用 JSP 来简单、高效地开发一个 Web 图表应用程序已经不是一件难事了。为了展 示如何编写 Web 图表应用程序,本书中,我们不仅提供了多个 JSP 图表生成的实例,而且还 将从以下两个方面加以说明 Web 图表编程是如何实现的。 1.利用 Java 自身对图形的处理能力,由开发者编写代码来生成 Web 动态图表。将以大量的 例程,不同的方式(例如:Java Applet、Java Servlet、JSP、JavaBean) ,从不同的角度来 展示如何编写 Web 图表的 Java 程序。 2. 借助第三方的图表生成引擎来完成图表, 主要利用一些 Java 开放源代码组织开发的作品, 例如: ? 由 www.jfree.org 推出的 JFreeChart; ? 由 cewolf.sourceforge.net 推出的 Cewolf。 Cewolf 是基于 JFreeChart 的二次开发。 实质上,是基于 JFreeChart 的应用。JFreeChart 没有免费提供开发文档,只有英文版的相关资料,因此 JFreeChart 和 Cewolf 在国内的应用 受到了一定的限制。 根据我们使用及研究 JFreeChart 和 Cewolf 的经验,将在本书中提供大量的例程和详尽 的讲解,帮助读者轻松地掌握上述两种非常优秀的、基于 Java 的 Web 图表生成引擎。相信阅 读本书,并加以必要的练习,读者将会发现,基于 JSP 的 Web 图表编程是可以轻松完成的。 本书结构及内容 本书例程丰富、代码简洁、结构清晰、讲解准确、图文并茂。共分九章,各章具体内容 如下: 第 1 章 Java 概述 本章主要概述 Java 历史及其发展、Java 开发环境的建立,以及编写一个简单的 Java 应 用程序。
  • 2. 第 2 章 Java Applet 与绘图基础 本章简要介绍 Applet 绘图基础,例如:图形环境、图形对象、基本的字体及颜色控制, 以及如何使用 Applet 绘制一些基本的几何图形。通过本章的学习,读者将掌握基本的 Java 图像编程方法。 第 3 章 Java Applet 图表绘制实例 本章综合利用绘制基本几何图形的方法来开发以下常见的图表: ? ·垂直柱状图 ? ·饼图 探讨如何以图表的方式生成以下内容: ? ·在 Web 动态图表中加载外部图像文件 ? ·支票 ? ·如何从 HTML 文档中获取参数生成动态图表 通过本章的学习,读者将会理解任何复杂的图表都可以分解成一些基本的几何图形;通 过排列组合不同的外部图像文件,以及基本的几何图形,就可以生成复杂的图表。 第 4 章 JSP/Servlet 运行环境的搭建 本章主要介绍 JSP Web 服务器的安装。在运行 JSP/Servlet 之前,首先需要安装 JSP/ Servlet 的运行环境,它就是我们平常所说的,支持 JSP/Servlet 的 Web 服务器(容器) 。 目前有很多支持 JSP/Servlet 的 Web 服务器,我们先介绍两种免费的、高性能的、适合 中小企业 JSP/Servlet 的 Web 服务器——Tomcat 和 Resin,并将详细阐述 Tomcat 实现 JSP/Servlet 的运行机制。此外,介绍一种企业级的 J2EE 服务器——Weblogic,学习如何在 这些服务器中部署及发布基于 JSP/Servlet 的 Web 图表应用程序。 第 5 章 基于 Servlet 的 Web 图表编程 本章讲述 Web 请求/响应机制(get 和 post) 、如何部署 Servlet、如何利用 Servlet 生 成 Web 动态图表, 并且提供了模拟网站投票统计、 生成登录验证码、 普通/3D 甘特图等 Servlet 实例。 第 6 章 JSP Web 图表编程基础 本章讲述 JSP 绘图基础。 JSP 的环境下, 在 如何使用 Java.awt.Graphics 类的各种方法, 包括绘制直线、文本(字符串) 、矩形、椭圆和圆、圆弧、多边形、折线,以及如何加载外部 图像文件,等等。 在绘制基本几何图形的基础上,我们将以圆柱体和立方体为例,展示如何通过绘制多个多边 形并将其组合成复杂几何图形的方法;利用 Java.imageio 包中 ImageIO 类的支持,调用 ImageIO 来执行诸如加载图像,以及对图像进行编码输出等工作。 第 7 章 JSP 与 Java2D Web 图表编程 本章阐述如何利用 Java2D API 对于高级二维图形的处理能力,例如:笔划属性的设 定、 图形填充、渐变色、图像的透明度、字体处理、图形边缘的反锯齿、图形对象的转换以及变 形,等等,利用 B/S 应用程序中生成 Web 图表的方法,并以二维及三维图表的形式,包括折 线图、水平直方图、垂直柱状图、堆栈图、饼图,以及二次曲线来进行讲解和说明。 提供了一个绘制复杂图表的例程——股市大盘指数走势图。通过本章的学习,读者可以 不借助任何第三方的程序,编写生成各种风格、复杂的 Web 图表应用程序。 第 8 章 开放源代码与 Web 图表编程 本章详细讲述了 JFreeChart 和 Cewolf 这两个 Web 图表生成引擎的安装、配置及使用方 法。提供了在 JFreeChart 和 Cewolf 的环境下生成以下类型图表的完整例程及讲解: ? 普通/3D 水平及垂直直方图、普通/3D 饼图、普通/3D 堆栈图 ? 线段(曲线)图、区域图、时序图、蜡烛图、移动平均线图
  • 3. ? 罗盘图、温度计图、速度表图、信号图 ? 甘特图、多轴图表、组合图表 此外,还包括如何在 Cewolf 的环境中,创建自定义绘制属性的图表。 第 9 章 Web 图表生成引擎的设计思路与实现 本章讨论如何开发一个图表生成引擎; 如何设计图表生成引擎 (封装成 JavaBean 的形式) 以达到高效、重复使用的目的。图表生成引擎的设计难点是什么?该如何处理?如何优化引 擎?我们将提供具体的实例加以讲解。通过本章的学习,读者将会感受到,图表引擎的设计 原来可以这么轻松地实现! 本书的适用对象 本书适合从事 Java 及 Web 编程的开发人员, JSP/Servlet 应用程序中需要提供图表显 在 示及处理功能的 JSP/Servlet 程序开发者,以及编程爱好者阅读。对于初学入门的应用开发 人员,可以作为自学的实践指导书;对于已经从事 Web 编程工作的人员,可以作为一本供随 时翻阅查询的案头参考书。 建议 我们建议读者在学习期间避免使用可视化的开发工具, 例如: JBuilder、JCreator、KAWA、 Visual Cafe、Eclipse、IntelliJ IDEA、BEA WebLogic Workshop、Oracle JDeveloper 等, 所有工作都可以使用 JSP 服务器(Tomcat/Resin)+ WWW 浏览器(IE/Mozilla/Firefox)+文 本编辑器(EditPlus)来完成。附录中,我们向读者介绍了一种优秀的 Java IDE 开发工具— —Gel。利用 Gel,可以方便地创建、编辑、编译及运行 Java/JSP/Servlet 应用程序。 为了达到最佳的学习效果,我们建议读者在阅读本书的同时,亲自动手按照本书的示例 去操作。 所有的源程序都可以在 www.broadview.com.cn 网站上下载, 但建议读者最好对照源 程序自己动手录入一次,这样可以加深理解和记忆。 因本书的例程非常丰富,为节省篇幅,故基本上不列出每个例程的完整清单。本书详尽 地讲解了每个源程序的设计思路、 运行机制及相对应的核心代码段。 建议读者在阅读本书时, 首先用 EditPlus 之类的文本编辑器来打开相应例程的源程序, 其次阅读其完整的源程序, 最 后再阅读本书的讲解,相信这样会让读者取得事半功倍的效果。 致谢 本书由钟京馗(City University of New York)执笔主编和最后定稿。唐桓(University of Bridgeport)参与本书例程的编写与调试工作。 本书出版之际,特别鸣谢钟志祥、李美行、朱晓蕾、朱正才、胡元妹、黄桂玉、武冠军、唐 光富、彭燕给予的大力支持与协助。 在编写本书的过程中,我们尽力保持内容的科学性、先进性和实用性,同时力求做到讲 解原理,深入浅出,语言通俗易懂。但鉴于作者学识有限,不足之处在所难免,见仁见智, 恳请广大读者指正。我们将在本书的再版中使其更臻完善。  钟京馗 2005 年 9 月于纽约
  • 4. 第 1 章  Java 概述  1  1.1  Java 简介  1  1.1.1  Java 发展简史  1  1.1.2  Java 的体系  2  1.1.3  Java 的优点  3  1.2  Java 开发环境的搭建  4  1.2.1  Java 运行环境的要求  4  1.2.2  Java 的安装和配置  6  1.3  Java/JSP 开发工具  8  1.3.1  EditPlus 简介  9  1.3.2  UltraEdit 简介  11  1.3.3  其他 Java/JSP 开发工具  13  1.4  第一个 Java 程序  15  1.5  本章小结  17  第 2 章  Java Applet 与绘图基础  18  2.1  Java Applet 概述  18  2.2  Java Applet 工作流程  20  2.2.1  组件与容器  20  2.2.2  Applet 的编译与执行  22  2.2.3  包含 Applet 的 HTML 23  2.3  绘制文本(字符串)  23  2.4  绘制线段  29  2.5  色彩管理  31  2.5.1  色彩基础  31  2.5.2  Java 的色彩管理  34  2.6  字体控制  36  2.7  绘制矩形  40  2.7.1  绘制普通矩形  40  2.7.2  绘制 3D 矩形  43  2.8  绘制椭圆  46  2.8.1  绘制普通(空心)椭圆  46  2.8.2  绘制实心椭圆  48  2.8.3  绘制 3D 椭圆  49  2.8.4  绘制圆柱体  50  2.9  绘制圆弧  54  2.9.1  绘制普通(空心)圆弧  55  2.9.2  绘制实心圆弧  56  2.9.3  绘制 3D 圆弧  57  2.10  绘制多边形  59  2.10.1  绘制空心多边形  59  2.10.2  绘制实心多边形  61  2.10.3  绘制折线  61  2.10.4  绘制三角形(箭头)  62  2.10.5  绘制平行四边形及立方体  69
  • 5. 2.11  图像的载入与显示  78  2.11.1  在 Applet 中加载和  2.11.1  显示图像  78  2.11.2  使用 MediaTracker 加载并  2.11.2  显示图像  84  2.11.3  使用双缓冲技术绘制图像  89  2.12  本章小结  93  第 3 章  Java Applet 图表绘制实例  94  3.1  Java Applet 生成 Web  3.1  动态图表  94  3.1.1  垂直柱状图  95  3.1.2  饼图  102  3.2  Java Applet 生成单据  109  3.2.1  带徽标的 Web 动态图表  110  3.2.2  支票的生成  111  3.3  从 HTML 文档获取参数  3.3  生成动态图表  123  3.3.1  传递参数的 HTML 文档  123  3.3.2  获取参数并生成图表  124  3.4  本章小结  126  第 4 章  JSP/Servlet 运行环境的搭建  128  4.1  Tomcat 的安装和配置  128  4.1.1  Tomcat 的安装  129  4.1.2  测试第一个 JSP 程序  132  4.1.3  配置 Tomcat 132  4.1.4  转换后的 JSP 页面文件  145  4.2  Resin 的安装和配置  147  4.2.1  Resin 的安装  147  4.2.2  Resin 的配置  149  4.3  BEA Weblogic 的安装和配置  150  4.3.1  BEA Weblogic 的安装  150  4.3.2  BEA Weblogic 的配置  152  4.3.3  测试 BEA Weblogic  4.3.3  的配置  155  4.3.4  部署第一个 Web  4.3.4  应用程序  156  4.4  本章小结  157  第 5 章 基于 Servlet 的 Web 图表编程  158  5.1  Servlet 简介及其构架  158  5.1.1  Servlet 的特点  158  5.1.2  Servlet 的接口  159  5.1.3  HttpServlet 类简介  160  5.1.4  HttpServletRequest 接口  161  5.1.5  HttpServletResponse 接口  162
  • 6. 5.2  Servlet 处理 HTTP get 请求  163  5.3  Servlet 处理包含数据的 HTTP  5.3  get 请求及解决中文乱码  177  5.4  Servlet 处理 HTTP post 及包含  5.4  数据的 post 请求  187  5.5  Servlet 生成 Web 投票统计图  191  5.6  Servlet 生成登录验证码  198  5.6.1  Servlet 生成登录验证码  5.6.1  实例 1 199  5.6.2  Servlet 生成登录验证码  5.6.2  实例 2 209  5.7  Servlet 高级设置  214  5.7.1  Servlet 初始化参数  214  5.7.2  Servlet 加载优先级  216  5.7.3  Servlet 映射  217  5.8  Servlet 绘制甘特图  218  5.9  Servlet 绘制 3D 甘特图  222  5.10  本章小结  228  第 6 章  JSP Web 图表编程基础  229  6.1  JSP 概述  230  6.1.1  JSP 运行机制  231  6.1.2  JSP 的优点  233  6.2  JSP 语法简介  234  6.2.1  JSP 文件结构  234  6.2.2  JSP 文件中的元素简介  240  6.3  JSP 调用 Servlet 生成  6.3  动态图表  257  6.3.1  JSP 生成验证码  257  6.3.2  JSP 生成甘特图  258  6.3.3  JSP 其他相关知识  258  6.4  JSP 生成基本动态图表  260  6.4.1  JSP 绘制文本和线段  260  6.4.2  JSP 与字体控制  266  6.4.3  JSP 绘制矩形  273  6.4.4  JSP 绘制椭圆  275  6.4.5  JSP 绘制圆弧  276  6.4.6  JSP 绘制多边形和折线  277  6.4.7  JSP 绘制三角形  277  6.4.8  JSP 绘制平行四边形和  6.4.8  立方体  280  6.4.9  JSP 加载并显示图像  281  6.5  本章小结  282  第 7 章  JSP 与 Java2D Web 图表编程  283  7.1  Java2D 概述  283
  • 7. 7.2  Java AWT 与 Java2D 285  7.3  Java2D 与填充属性  287  7.3.1  设置填充属性  287  7.3.2  填充属性的用法  287  7.4  Java2D 与笔划属性  291  7.4.1  线段端点的形状风格  292  7.4.2  线段转折处的形状风格  292  7.4.3  虚线风格  293  7.4.4  BasicStroke 构造器  294  7.4.5  Java2D Web 图表实例  7.4.5  之折线图  294  7.5  创建基本 Java2D 图形  309  7.5.1  Java2D 图形(Shape)  7.5.1  接口概述  309  7.5.2  Point2D 310  7.5.3  Line2D 311  7.5.4  Rectangle2D 312  7.5.5  RoundRectangle2D 314  7.5.6  Java2D Web 图表实例之  7.5.6  柱状图  315  7.5.7  Ellipse2D 333  7.5.8  Arc2D 334  7.6  Java2D 实例饼图类图表  337  7.6.1  普通饼图  338  7.6.2  圆圈图  342  7.6.3  3D 饼图  344  7.7  图形重叠  346  7.8  alpha 复合  348  7.8.1  alpha 复合概述  348  7.8.2  AlphaComposite 类的使用  350  7.8.3  AlphaComposite 应用实例  352  7.9  图形对象的转换  357  7.9.1  图形对象转换(transformation)  7.9.2  概述  357  7.9.2  平移(translation)  359  7.9.3  旋转(rotation)  360  7.9.4  缩放(scale)  362  7.9.5  扭曲(shear)  364  7.10  图形渲染线索  366  7.11  Java2D 与高级文本处理  368  7.11.1  空心文本  368  7.11.2  弯曲文本  369  7.11.3  单行长文本自动分行  371  7.12  Java2D 创建复杂图形  373
  • 8. 7.12.1  Area 374  7.12.2  曲线  375  7.12.3  通用路径  377  7.13  Web 图表实例解析  379  7.13.1  透明 3D 饼图  379  7.13.2  股市指数走势图  381  7.14  本章小结  391  第 8 章 开放源代码作品与 Web 图表编程  392  8.1  开放源代码作品简介  392  8.2  JFreeChart 与 JSP 图表编程  394  8.2.1  JFreeChart 简介  394  8.2.2  JFreeChart 的安装及其  8.2.2  核心类  395  8.2.3  JFreeChart 生成直方图表  398  8.2.4  JFreeChart 生成饼型图表  411  8.2.5  JFreeChart 生成线段图表  416  8.2.6  JFreeChart 生成区域图表  420  8.2.7  JFreeChart 生成时序  8.2.7  (Time Series)图表  424  8.2.8  JFreeChart 生成甘特图表  430  8.2.9  JFreeChart 生成多轴  8.2.9  (Multiple Axis)图表  432  8.2.10  JFreeChart 生成组合  8.2.10  (Combined Axis)图表  435  8.2.11  JFreeChart 生成其他类型  8.2.11  的图表  441  8.3  JFreeChart 与 Servlet  8.3  图表编程  444  8.3.1  简单的 Servlet 图表编程  444  8.3.2  交互式 Servlet 图表编程  446  8.4  Cewolf 与 JSP 图表编程  448  8.4.1  Cewolf 简介  448  8.4.2  Cewolf 的下载与安装  448  8.4.3  Cewolf 生成直方图表  450  8.4.4  Cewolf 生成基于 DefaultCategory  8.4.4  Dataset 数据集的图表  454  8.4.5  Cewolf 生成饼图  460  8.4.6  Cewolf 生成基于 XYDataset  8.4.6  数据集的图表  462  8.4.7  Cewolf 生成基于 OHLCDataset  8.4.7  数据集的图表  465  8.4.8  Cewolf 生成甘特图表  466  8.4.9  Cewolf 生成信号图表  467  8.4.10  Cewolf 生成速度图表  468
  • 9. 8.4.11  Cewolf 生成 OverLay 类型  8.4.11  的图表  468  8.4.12  Cewolf 生成组合图表  470  8.4.13  生成自定义风格的  8.4.13  Cewolf 图表  472  8.5  本章小结  473  第 9 章  Web 图表生成引擎的设计思路与实现  475  9.1  Web 动态图表生成引擎的  9.1  设计思路  475  9.2  Web 动态图表生成引擎的  9.2  设计模型  480  9.2.1  生成普通线段图的  9.3.2  JavaBean 480  9.2.2  生成 3D 线段图的  9.3.2  JavaBean 483  9.2.3  生成普通直方图的  9.3.2  JavaBean 484  9.2.4  生成 3D 直方图的  9.3.2  JavaBean 487  9.2.5  生成普通饼图的  9.3.2  JavaBean 488  9.2.6  生成 3D 饼图的  9.3.2  JavaBean 490  9.3  数据分离  491  9.3.1  创建及调用 CategoryDataset  9.3.2  类数据集对象  491  9.3.2  创建及调用 PieDataset 类  9.3.2  数据集对象  495  9.4  引擎的优化概述  496  9.5  本章小结  498  附录  Gel 使用指南  499
  • 10. 第 1 章  Java 概述  Java 是美国 Sun 微系统公司(Sun  Microsystems,  Inc.)开发,近年来飞速发展的一项崭 新的计算机技术。  1.1  Java 简介  Java 既是一种程序设计语言,也是一个完整的平台。作为一种程序语言,它简洁、面向 对象、 安全、 健壮,以及适用于 Internet 技术;而作为一个平台(JRE,  Java Runtime Environment,  Java 运行环境或者说是 Java 虚拟机)对于符合 Sun 公司 Java 标准的应用程序, , 都可以在 Java  平台上正确运行,与程序运行的操作系统无关。  Java  为什么能够成为目前编写  Web  应用程序的首选设计语言呢?并且具备跨平台的能 力呢?我们先简单地了解一下 Java 的发展历史和体系结构。  1.1.1  Java 发展简史  Java 起初并非叫做 Java,而是叫做 Oak。早期是为了嵌入式系统而设计的一项产品。20  世纪 90 年代初期,  公司预测微处理器的发展将会对家用电器以及智能化电子消费设备起 Sun 到决定性的作用,于是,在 1990 年 12 月,Sun 公司就由 Patrick Naughton、Mike Sheridan 和  James  Gosling 成立了一个叫做 Green  Team 的小组。其主要目标就是开发一种分布式系统架 构,使其能够在智能化电子消费设备作业平台上执行。例如,PDA、手机、信息家电(IA,  Internet/Information  Appliance)等。一开始,准备采用 C++,但 C++太过复杂,安全性差。 最后,基于 C++开发出一种新的语言 Oak,Oak 是一种用于网络、精巧而安全的语言。  1992 年,Green Team 发表了一款名叫 Star Seven(*7)的机器,它有点像现在我们熟悉 的 PDA,但市面上的 PDA 几乎都不是它的对手,更不要说是早在 10 年前那个计算机还不普 及的时代了。Java 语言的前身 Oak 就这样诞生了,主要目的是用来编写在 Star Seven 上的应 用程序。为什么叫 Oak 呢?原因是 James  Gosling 办公室的窗外,正好有一棵橡树(Oak) , 就取了这个名字。至于为什么 Oak 又会改名为 Java 呢?这是因为在 Oak 注册商标时,发现 已经被另外一家公司使用了。那要取什么新名字呢?工程师们边喝着咖啡边讨论着,看着手 中的咖啡,突然灵机一动,就叫  Java  好了。就这样它就变成了大名鼎鼎、如日中天的  Java  了。 但是,Green Team 的项目发展却不尽如人意。智能化电子消费设备的市场发展远远低于  Sun 的预期设想。 Sun 公司曾依此投标一个交互式电视项目, 结果被 SGI 打败了。 可怜的 Oak  几乎无家可归,就在举步维艰,随时会被取消的时刻,情况却发生了巨大变化。1993 年,因 特网在美国开始大规模的发展,基于因特网的 WWW 也爆炸性地流行起来。Sun 公司发现,  Green  Team 的项目研究成果——Java 似乎天生就是为因特网而诞生的,因为恰巧就是 Mark  Andreessen 开发的 Mosaic 和 Netscape 启发了 Oak,他们利用 Java 开发了 HotJava 浏览器,得 到了 Sun 公司首席执行官 ScottMcNealy 的支持,触发了 Java 进军 Internet。 于是,Java 第一次以 Applet 的形式在因特网上出现了。Applet 不但使 WWW 页面显示 静态的内容,而且可以显示动态的内容和动画。同时实行了本地计算机,从远程联网的服务
  • 11. 器上下载资料并正确地显示出来。这些技术在当时引起了巨大的震撼,促使 Java 在因特网上 得以飞速地发展。  1995 年 5 月 23 日,  在 SunWorld’95 上正式发布 Java 和 HotJava 浏览器。  Sun 1.1.2  Java 的体系  Java 发展到今天,已从编程语言发展成为全球第一大通用开发平台。Java 技术已被计算 机行业主要公司所采纳。  1999 年,  公司推出了以 Java2 平台为核心的 J2EE、  Sun J2SE 和 J2ME  三大平台。随着三大平台的迅速推进,全球形成了一股巨大的 Java 应用浪潮。  1.Java 2 Platform, Micro Edition(J2ME)  Java  2 平台微型版。Sun 公司将 J2ME 定义为“一种以广泛的消费性产品为目标、高度 优化的 Java 运行环境”自 1999 年 6 月在 JavaOne Developer Conference 上声明之后,  。 J2ME  进入了小型设备开发的行列。 通过 Java 的特性, 遵循 J2ME 规范开发的 Java 程序可以运行在 各种不同的小型设备上。  2.Java 2 Platform, Standard Edition(J2SE)  Java 2 平台标准版,适用于桌面系统应用程序的开发。本书例程就是利用 J2SE 5.0 版的 相关图形 API 包来开发的。  3.Java 2 Platform, Enterprise Edition(J2EE)  J2EE 是一种利用 Java 2 平台来简化企业解决方案的开发、 部署和管理等相关复杂问题的 体系结构。J2EE 技术的核心就是 Java 平台或 Java 2 平台的标准版,J2EE 不仅巩固了标准版 的许多优点,例如: “一次编写、随处运行”的特性、方便存取数据库的 JDBC API、CORBA  技术,以及能够在  Internet  应用中保护数据的安全模式等,同时还提供了对 EJB(Enterprise  JavaBeans)、Java Servlets API、JSP(Java Server Pages) ,以及 XML 技术的全面支持。 本书第 4 章,阐述了如何将我们开发的 JSP 图表应用程序,在 J2EE 平台上进行部署和 管理。  1.1.3  Java 的优点  Java 是一种面向对象、分布式、解释、健壮、安全、可移植、性能优异,以及多线程的 语言。下面简单介绍其中的几个优点。  1.Write Once, Run Anywhere  “一次编写, 随处运行” 这是程序设计师们喜爱 Java 的原因之一, 。 核心就是 JVM(Java  虚拟机)技术。 编写好一个 Java 程序, 首先,要通过一段翻译程序, 编译成一种叫做字节码的中间代码。 然后经 Java  平台的解释器,翻译成机器语言来执行——平台的核心叫做 JVM。Java  的编译 过程与其他语言不同。例如,C++在编译的时候,是与机器的硬件平台信息密不可分的。 编译程序通过查表将所有指令操作数和操作码等,转换成内存的偏移量,即程序运行时的 内存分配方式,以保证程序运行。而 Java 却是将指令转换成为一种扩展名为 class 的文件, 这种文件不包含硬件的信息。只要安装了 JVM,创立内存布局后,通过查表来确定一条指 令所在的地址,这就保证了 Java 的可移植性和安全性。 上述 Java 程序的编译和运行流程,如图 1.1 所示。
  • 12. Interpreter 编译器 ①:编译 MyApp.java  ②:运行 源文件  Ø 装载类 ① Ø 生成字节码 Ø 及时编译 Ø 解释执行  ② Java  Java  Java  应用 应用 应用 程序  程序  程序  JVM  JVM  JVM  (J2SE)  (J2SE)  (J2ME)  Unix/Linux  MS Windows  Palm OS  图 1.1  Java 的编译和运行流程 
  • 13. 2.简单 纯粹的面向对象,加上数量巨大的类所提供的方法(函数)库的支持,使得利用 Java 开 发各种应用程序,可以说是易如反掌。此外,在程序除错、修改、升级和增加新功能等方面, 因其面向对象的特性,使得这些维护也变得非常容易。  3.网络功能  Java 可以说是借助因特网而重获新生的,自然具备编写网络功能的程序。不论是一般因 特网/局域网的程序,如 Socket、Email、基于 Web  服务器的 Servlet、JSP 程序,甚至连分 布式网络程序,如 CORBA、RMI 等的支持也是非常丰富的,使用起来也很方便。  4.资源回收处理(Garbage Collection)  Garbage  Collection 是由 JVM 对内存实行动态管理的。程序需要多少内存、哪些程序的 内存已经不使用了,需要释放归还给系统,这些烦琐且危险的操作全部交由  JVM  去管理。 让我们能够更专心地编写程序,而不需要担心内存的问题。内存的统一管理,对于跨平台也 有相当大的帮助。  5.异常处理(Exception) 为了使 Java 程式更稳定、更安全,Java 引入了异常处理机制。能够在程序中产生异常情 况的地方,执行相对应的处理,不至于因突发或意外的错误造成执行中断或是死机。通过这 种异常处理,不仅能够清晰地掌握整个程序执行的流程,也使得程序的设计更为严谨。  1.2  Java 开发环境的搭建 现在我们开始搭建 Java 开发环境。本章仅涉及 J2SDK 开发环境的安装与调试,并不涉 及 JSP 及 J2EE 服务器的安装与调试,这部分内容将在第 4 章进行说明。  1.2.1  Java 运行环境的要求 首先,需要了解 Java 对计算机主要硬件要求的最低配置,如表 1.1 所示。该表右边部分 为笔者写作本书时所使用的计算机配置。因为涉及大量在计算机内存中进行绘制各种图表的 运算与操作,建议使用较高配置的计算机以及尽可能多的内存,以便流畅地运行本书中的程 序。 表 1.1  Java 的计算机硬件配置 硬 件 最低要求 笔者的配置  CPU  Intel 或者兼容微处理器,奔腾 166MHz 及其以上  AMD Athlon 3200+  最低 32MB,可以运行图形界面的 Java Application; 512 MB DDR,Kingston 内存 内存 最低 48MB,可以运行 Java Applet;内存不足,将会导 (如果读者希望把本书中的例程运行在 J2EE 的 致系统(尤其是服务器)的性能大幅下降  服务器,如 WebLogic,最好不低于 256 MB) 硬盘  242 MB  80 GB  图形显示卡 无要求  Nvidia Ti 4200 64 MB DDR  网卡 普通网卡  3Com OfficeConnect 10/100 兆位网卡 其他部件,如图形显示卡、光驱及显示器等,Java 并没有特别的要求。因为本书涉及图
  • 14. 形的实时生成, Java 的图形生成是通过图形显示卡调用 OpenGL 功能来加速图像的渲染和 而 处理的。因此,如果系统拥有一块高质量的图形显示卡,将会大大地提高系统的性能。目前 市面上的图形显示卡,无论是在计算机主板上集成的显卡,还是普通的独立图形显示卡,都 具有强大的图形处理功能,足以满足流畅地运行本书中的所有例程。  Java 具有跨平台的特点,支持所有的主流操作系统,如下所示。  1.微软公司  Windows 系列 Ø  Windows 98 / Me  Ø  Windows NT  Ø  Windows 2000 / Windows 2000 Server  Ø  Windows XP  Ø  Windows 2003 Server  2.UNIX 系列 Ø  AIX,  IBM 的 UNIX, 是根据 SVR2 以及一部分 BSD 延伸而来, 加上各种硬件的支持。 具备特有的系统管理功能(SMIT,系统管理接口工具) 。 :基于 4.3BSD,包含许多来自 System  V 的东西。Sun  Ø  SunOS(680x0,Sparc,i386) 公司开发的 UNIX 操作系统,该系统对 UNIX 的贡献是:NFS,OpenLook GUI 标准, 现演变为 Solaris。 Ø  HP­UX(HP) :惠普公司的 UNIX。 Ø  SCO UNIX。  3.类 UNIX 系列 Ø  Linux:包括各种版本的 Linux,如 RedHat、Turbo 和 Mandrake Linux。 Ø  FreeBSD:由美国加州大学伯克利分校计算机系统研究小组设计和维护。
  • 15. 4.其他操作系统 Ø  Mac OS:苹果电脑公司推出的操作系统,主要用于 Power PC。 上述操作系统都可以安装 Java。 笔者的操作系统是微软 Windows XP SP2。本书的所有例 程都是在 Windows XP 环境下进行编写和调试的。  1.2.2  Java 的安装和配置 本书采用 Sun 公司发布的最新版本,也是近年来 Java 最重要的一个升级版本——Java  2  Platform Standard Edition 5.0,即 J2SE 5.0 作为我们的 Java 开发平台。至于 J2SE 5.0 具备哪些 新的功能和特性,请读者自行查阅相关资料。本书将尽量使用 J2SE 5.0 中的新特性来增强代 码的性能,如 Image I/O、Java 2D 等,具体将在其他章节中说明。  J2SE 5.0 的下载地址:http://java.sun.com/j2se/1.5.0/download.jsp。双击下载文件:jdk­1_  5_0­windows­i586.exe 就开始了 J2SE 5.0 开发环境的安装过程,如图 1.2 所示。 默认 图 1.2    Java 的安装过程者一:改变默认的安装路径 安装过程如下: (1) 安装程序经过自解压后, 就会出现安装协议的对话框, 【I accept the terms in the  选择 license agreement】并单击【Next】按钮; (2)在出现选择安装路径的对话框时,我们改变 J2SE  5.0 的默认安装路径。单击右边 的【Change…】按钮,在出现的路径修改对话框中,输入“c:jdk1.5.0”后退回到先前的对话 框; (3)单击【Next】按钮,继续剩余部分的安装。接着 J2SE 5.0 会提示 Java 运行环境(即  JRE 5.0)的安装路径,这里不做任何的改变,采用其默认设置。 然后,连续单击【Next】按钮直到完成安装。最后系统会提示重新启动计算机,再重新 启动计算机,需要对运行 Java 的环境变量进行设置。这是非常重要的一个步骤,如果没有设 置成功,在运行 Java 时会出现错误。 环境变量的设置,如图 1.3、图 1.4 所示。
  • 16. 图 1.3    Java 的安装过程之二:环境参数的设置 (1)进入【控制面板】,单击【系统】 ,在出现的【系统属性】对话框中,单击【高级】 选项。 (2)单击【环境变量】按钮。 (3)在环境变量对话框中,单击位于【系统变量】组中的【新建】按钮。 (4)新建一个系统变量, “变量名”为 JAVA_HOME,“变量值”为 C:JDK1.5.0,然后 单击【确定】按钮,如图 1.4 所示。这个 JAVA_HOME 的值就是 J2SE 5.0 的安装路径。 图 1.4    Java 的安装过程之三:环境参数的设置 (5) 重复第 4 步, “变量名” 在 中输入“CLASSPATH” 在 , “变量值”中输入 “.  %JAVA _HOME%lib”。注意,这里的“.”是要求 JAVA 编译器在查找 JAVA class 文件时,首 先从当前目录开始。 (6)最后是修改系统变量“PATH”的值, PATH”的新值是在原有值前面加上 “ “%JAVA_HOME%bin;”。 提示:在 Java  1.2 版以后,不再需要 CLASSPATH  来设置系统类的路径。CLASSPATH  是为了设置用户编写的类或者第三方开发的类库而设置的。 现在,为确保系统环境变量生效,重新启动计算机。重启之后,测试 J2SE 5.0 的安装与 环境变量设置是否正确。单击【开始】→【所有程序】→【附件】→【命令提示符】 ,启动命 令提示符后,在该窗口中输入如下命令: echo %java_home%  echo %classpath%  echo %path%
  • 17. java –version 在笔者本机上,得到如图 1.5 所示的结果。 图 1.5  测试 Java 运行环境系统参数的设置 如果读者得到如图 1.5 一样的结果,就说明最新版的 Java 开发环境已经安装成功。 提示:读者计算机中“Path”值可能同笔者计算机的值不一样,但其中一定要包含 “C:jdk1.5.0bin”的内容。  1.3  Java/JSP 开发工具 为了获得 Java 图表编程学习的最佳效果, 本书所有源程序都没有使用任何一种可视化的  Java  开发环境来进行开发。建议读者在学习期间不要使用可视化的开发工具,如  JBuilder、  JCreator、KAWA、Visual  Cafe、Eclipse、IntelliJ  IDEA、BEA  WebLogic  Workshop、Oracle  JDeveloper  等。本书所有的操作,都可以用  JSP  服务器(Tomcat/Resin)+WWW  浏览器 (IE/Mozilla/Firefox)+文本编辑器(EditPlus)来轻松完成。这样,我们才可以深刻地理解 系统的运行机制。等到读者对此非常熟悉的时候,再使用可视化开发工具进行开发,定会达 到事半功倍的效果。此外,在本书的附录中,我们将向读者介绍一款优秀的 Java IDE 开发工 具——Gel。利用 Gel,可以方便地创建、编辑、编译及运行 Java/JSP/Servlet 应用程序。建议 读者循序渐进地学习之后,再来学习 Gel,相信一定会喜欢上这款精巧而强大的 Java 开发环 境。 基于文本编辑器的 Java/JSP 开发工具有很多种,如记事本、UltraEdit、EditPlus 等。但最 简单及成本最低廉的非  Windows  自带的记事本(NotePad)莫属了。可以直接使用记事本进 行 Java/JSP 的程序设计, 它具有简单、方便的特点。 但其毕竟不是为程序员准备的开发工具, 因此,并不适用于程序设计。 现在,向读者推荐一款韩国的文本编辑器——EditPlus 作为我们的 Java 及 JSP 编辑器。  1.3.1  EditPlus 简介  EditPlus  是由韩国人编写的一款共享软件,官方网址是  www.editplus.com。最新版本是  EditPlus 2.12。EditPlus 是功能全面的文本、HTML、程序源代码编辑器。主要特点如下: (1)默认支持  HTML、CSS、PHP、ASP、Perl、C/C++、Java、JavaScript  和  VBScript  的语法高亮显示,通过定制语法文件,可以扩展到其他程序语言。 (2)EditPlus 提供了与 Internet 的无缝连接,可以在 EditPlus 的工作区域中打开 Intelnet  浏览窗口。
  • 18. (3)提供了多工作窗口。不用切换到桌面,便可在工作区域中打开多个文档。 (4)正确地配置 Java 的编译器“Javac”以及解释器“Java”后,使用 EditPlus 的菜单 可以直接编译执行 Java 程序。 所谓的文本编辑器,最终还是要归结在文本编辑上。EditPlus  提供了比普通编辑器更加 强大的编辑功能。首先可以无限制地 Undo,其次具有更强大的删除功能,EditPlus 不仅可以 删除一个字,而且还可以删除选择部分、删除整个行等。提供了更强大的复制功能,可以复 制整个行,如单击【Edit】菜单,选择【Duplicate line】命令,即可实现此功能。这些大大地 超越了记事本的功能。  EditPlus 查找/替换功能也是一流的。不但可以进行一般的查找和替换,而且支持以正则 表达式的方式进行查找与替换, 甚至可以在磁盘某一目录的所有文件中, 进行字符串的查找。 比如说,笔者要查找 D:webchart 目录(包括其子目录)下,所有的 jsp 文件中,哪些文件包 含了字符串: “charset=GB2312”,我们就可以单击菜单栏上的【Search】 ,选择【Find in Files】 命令。如图 1.6 所示,在出现的查找对话框中,分别填入以下内容: Ø【Find what】填入“charset=GB2312”,表示要查找的内容。 Ø【File  type】填入“*.jsp” ,表示要查找所有扩展名为  jsp 的文件,如果这一栏不输入 任何条件,则表示在所有的文件中查找,而忽略文件的格式。 Ø【Folder】填入“d:webchart” ,表示希望查找文件位于 d:webchart 这个目录下。 Ø 如果要查找  d:webchart  及其子目录下的所有  jsp  文件,就需要选中该对话框中的 “include subfolders”选项。 填好上述栏目后,单击该对话框中的【Find】按钮,就可以在  EditPlus  下方的输入输出 窗口中,得到查找的结果了。在输出窗口中,会提供一个被查找到的匹配文件的清单。在此 窗口的左侧,指出与被查找的字符串“charset=GB2312”相匹配文件的所在目录以及文件名, 然后给出字符串在该文件中出现的位置,该位置用行列坐标表示。在输出窗口的最底部,  EditPlus 会提供一个统计结果——查找到的匹配的文件总数。 图 1.6    EditPlus 的 Find in Files 功能 双击查找结果清单中出现的任一文件名,EditPlus  会在主窗口中显示出该文件的内容, 并将鼠标指针定位到被查找的字符串第一次出现的位置。 该功能在大量文件中进行查找时,提供了强大的支持。 前面谈到,设置好 Java 的编译器“Javac”和解释器“Java”后,通过 EditPlus 的菜单可
  • 19. 以直接编译执行 Java 程序。现在就来看看如何在 EditPlus 中设置。单击菜单栏中的【Tools】 →【Configure User Tools】→【Preferences】→【User tools】 ,如图 1.7 所示。
  • 20. 运行 Java 的配置 图 1.7    EditPlus 设置编译与运行 Java 程序的功能 Ø【Add Tool】 :表示新增一个用户自定义工具。 Ø【Program】 :表示该工具与程序有关。 Ø【Menu text】 :表示要求为添加的工具命名。这里填入:编译 JAVA。 Ø【Command】 :表示该工具运行时,所调用的可执行程序。Java 中的编译命令是 javac, 所以这里选择 Java 安装目录中 bin 子目录中的 javac.exe 程序。 Ø【Argument】 :表示执行  javac.exe  程序时,所需要的参数。单击旁边的带有向下三角 形的按钮来选择“$(FilePath) ”即可。 Ø 【Initial  directory】:选择初始化目录。单击旁边的带有向下三角形的按钮来选择“$  (FileDir) ”即可。 Ø【Capture output】 :选择捕获屏幕输出。 通过 EditPlus 来编译 Java 程序完成。现在继续增加一个新的工具,设置通过 EditPlus 来 运行编译成功的 Java  程序。方法同前面讲述的设置编译 Java  程序的过程基本相同。按照图  1.7 右下方所示的“运行 Java 的配置”内容,进行设定即可。 正确设置后,无论在 EditPlus 编译还是运行 Java 程序的时候,EditPlus 的输出窗口中都 会显示出相应的编译和运行结果。 关于 EditPlus 就简要介绍到这里。  1.3.2  UltraEdit 简介  UltraEdit 是共享软件,官方网址是 www.ultraedit.com,最新版本是 v11.00a。与 EditPlus  类似,UltraEdit  是一款功能强大的文本、HTML、程序源代码编辑器。作为源代码编辑器, 其默认配置可以对 C/C++、VB、HTML、Java 和 Perl 进行语法高亮显示。设计 Java 程序也 很方便,具有复制、粘贴、剪切、查找、替换、格式控制等编辑功能。  UltraEdit 同样可以在【Advanced】菜单的【Tool Configuration】菜单项配置 Java 编译器  Javac 和解释器 Java,直接编译运行 Java 程序。  UltraEdit 设置 Java 编译器“Javac”的方法如图 1.8 所示。
  • 21. 图 1.8    UltraEdit 设置编译 Java 程序的功能 Ø【Command  Line】 :表示命名行的执行方式。n%表示 Java 源程序的文件名,%e 表示  Java 源程序的扩展名。因为已经正确设置了 Java 的运行路径,所以使用 javac 就可以 了,否则就要用 C:jdk1.5.0binjavac.exe。 Ø【Working Directory】 :表示工作目录。%p 表示工作目录就是当前目录。 Ø【Menu Item Name】 :要求给添加的工具命名。填入:编译 JAVA。 Ø【Save Active File】:表示该工具运行之前,首先保存当前 Java 源程序。 Ø【Output to List Box】:表示将结果输出到列表框中。 Ø【Capture Output】 :选择捕获屏幕输出。 Ø【Insert】:Java 编译的工具菜单就做好了。 同理,设置 Java 解释器“Java”的方法如图 1.9 所示。与前面的步骤类似,只需要更改 第一和第三个步骤即可。 【Command Line】输入“java %n”【Menu Item Name】输入“运行  , Java”。 关于 UltraEdit 就简单介绍到这里。
  • 22. 图 1.9    UltraEdit 设置运行 Java 程序的功能  1.3.3  其他 Java/JSP 开发工具 下面向读者简要介绍两个开放源代码的作品 Eclipse 和 Gel,虽然本书并不使用这两种开 发工具,但笔者觉得有必要向读者介绍一下,目前国外非常流行的 Java IDE 开发工具。  1.Eclipse 简介  Eclipse 是一个开放源代码的项目, 可以在 www.eclipse.org 免费下载 Eclipse 的最新版本。 一般 Eclipse 提供几种下载版本:Release、Stable Build、Integration Build 和 Nightly Build。建 议下载 Release 或 Stable 版本。Eclipse 本身是用 Java 语言编写的,但下载的压缩包中并不包 含 Java 运行环境,需要用户自行安装 JRE,并且需要在操作系统的环境变量中,指明 JRE 中  bin 的路径。如果已经安装了 JDK,就不用安装 JRE 了。 安装 Eclipse 的步骤非常简单,只需将下载的压缩包按原路径直接解压即可。注意,如果 要更新版本,请先删除旧版本重新安装,不能直接解压到原来的路径覆盖旧版本。解压缩之 后,可以到相应的安装路径去找 Eclipse.exe 运行。如果下载的是 Release 或 Stable 版本,并 且 JRE 环境安装正确无误, 一般来说不会有什么问题, 在闪现一个很酷的月蚀图片后,  Eclipse  会显示它的默认界面。  Eclipse 的界面有点像 JBilder,实际操作进程中会发现它更像 IVJ。毕竟开发 Eclipse 的主导 是开发  IVJ  的原班人马(可参考  www.oti.com) 。值得一提的是,Eclipse 项目的参与者除了  IBM  以外,还有 Borland、Rational Software、RedHat、Merant,以及 BEA 等一大批业界的佼佼者,为  Eclipse 的未来奠定了良好的基础。  Eclipse 不仅可以开发 Java 应用程序,还可以安装 Lomboz 插件,并整合 Tomcat 以实现 对 JSP 以及 Java Servlets 的开发。 本书不在这里讨论 Eclipse 的下载、安装、使用、开发、各种插件的安装和使用,以及与  JBoss、Tomcat、WebLogic 等 JSP 或者 J2EE 服务器的整合。有兴趣的读者,请自行查阅相关 资料。 2.Gel 简介  Gel  是由  Gerald  Nunn  发布的一款  Java  IDE  开发工具。Gel  的官方主页是  http://  memescape.co.uk/gexperts/index.html。Gel 是完全免费的,读者可到该网站下载。在本书写作 时,Gel 的最新版本是 RC39。目前该 IDE 工具,在国外众多的 Java、JSP 及 J2EE 程序员中
  • 23. 引起了极大的反响,但国内很少有人使用这款优秀的 Java IDE。  Gel 的运行环境如下: Ø  Windows 95/98/ME/2000/XP  Ø  Internet Explorer 4+  Ø  JDK 1.1+  Gel 的特点如下: (1)编辑器(Editor) Ø 支持对  Java、JSP、HTML、XML、C、C++、Perl、Python,以及更多编程语言的语 法高亮显示。 Ø 无限制地取消及恢复功能。 Ø 整段代码的减少缩进量与增加缩进量。 Ø 高亮显示配对的大括号。 Ø 实时地拼写检查。 Ø 可以显示控制字符。 Ø 整合的源代码管理及控制, 支持 CVS、VSS、Clearcase、Perforce、QVCS、CS­RCS  以 及更多的格式。 Ø 正则表达式搜索。 Ø 文件查找。 Ø 将被选择代码完整地输出为 HTML 及 RTF 文件。 Ø 保持本地程序的更新历史,易于在需要的时候恢复原状。 (2)代码援助(Code Assistants) Ø 自动完成 Java 和 JSP 的编码。 Ø  Java 和 JSP 的参数提示。 Ø  Java 和 JSP 的标识符提示。 Ø 查找声明,并快速跳转到声明所属的变量、方法或类。 Ø 类浏览器。 (3)项目管理(Project Management) Ø 智能文件夹,当系统文件发生改变的时候,自动同步更新。 Ø 可以运行 Applications、Applets 以及应用服务器。 (4)J2EE  Ø 直接预览 Servlets 及 JSP。 Ø 自动完成 JSP 标签,支持 HTML 及自定义 JSP 标签。 (5)其他 Ø 支持集成 ANT。 Ø 支持集成 JUnit。 Ø 定制工具栏及快捷方式。 Ø 支持创建自定义工具。 Ø  Javadoc 浏览器。 总的来说,Gel 虽然小巧,但速度奇快,更重要的是它拥有许多大型 Java IDE 的完整功 能。本书附录,将详细介绍 Gel 的设置及如何利用 Gel 来开发 JSP 程序。  1.4  第一个 Java 程序 前面我们讲解了 J2SE 5.0 的安装及环境变量的设置,现在我们就编写一个很简单的 Java
  • 24. 应用程序 Application 来结束本章的学习。 在 EditPlus 或 UltraEdit 中输入如下代码(chap01Fig1.4WelcomeJavaChart.java): 1: // Fig. 1.4: WelcomeJavaChart.java  2: // 第一个 Java 程序 3:  4: public class WelcomeJavaChart{  5:   // Java 主程序入口 6:  public static void main(String args[]){  7: System.out.println("**************************************");  8:     System.out.println("欢迎来到<<精通 Java Web 动态图表编程>>的世界! ");  9: System.out.println("**************************************");  10:  11:   } //主程序结束 12:  13: } // end class WelcomeJavaChart 该源程序仅仅在命令窗口显示三条文本。可以用传统的方法,在  DOS  下对该文件进行 编译及运行,也可以在 EditPlus 及 UltraEdit 中进行。 将 WelcomeJavaChart.java 拷贝到 D:webchartchap01Fig1.4 子目录下,运行如下命令: D:webchartchap01Fig1.4>javac WelcomeJavaChart.java  D:webchartchap01Fig1.4>java WelcomeJavaChart 编译及运行结果如图 1.10 所示。 在 UltraEdit 中,编译和运行的方法是运行【Advanced】→【编译 JAVA】→【运行 JAVA】 工具,如果全部设置正确,就可以看到如图 1.11 所示的结果。 图 1.10    WelcomeJavaChart.java 的运行结果
  • 25. 图 1.11    UltraEdit 下 WelcomeJavaChart.java 的运行效果 在 EditPlus 中,编译和运行的方法是运行【Tools】→【编译 JAVA】→【运行 JAVA】工 具,如果全部设置正确,就可以看到如图 1.12 所示的结果。
  • 26. 图 1.12    EditPlus 下的 WelcomeJavaChart.java 的运行结果  1.5  本章小结 本章主要介绍 Java 的历史及其运行机制、技术特点,讲解了各种 Java 开发环境的搭建, 包括 JDK 的下载、安装和环境变量的设置,在 EditPlus 和 UltraEdit 中如何设置编译及运行。 此外,简要介绍了两款目前国外流行的 Java IDE 开发环境:Eclipse 和 Gel。最后用一个简单 的 Java Application 来测试搭建的 Java 开发环境是否成功。 只有在正确搭建  Java  开发环境的基础上,才能顺利地搭建  JSP  服务器。下面学习  Java  Applet 的编写和调试,让我们一起进入 Web 图表编程的精彩世界! 第 2 章  Java Applet 与绘图基础  Java 最初在因特网上流行,很大程度上归功于 Java Applet。Java 对图形处理的应用最早 就是从 Applet 开始的,对于希望掌握 Web 动态图表编程的读者来说,从 Applet 着手学习是 一个很好的途径。理解 Applet 的绘图基础,就可以顺利地过渡到如何运用 JSP 来进行图表的 绘制。  2.1    Java Applet 概述  Java 共有两种类型的程序。一种称为 Java  Application,如第 1 章 1.4 节所示的 Welcome
  • 27. JSPChart.java。本章介绍另外一种 Java 程序,这类程序称为 Java Applet。Java Applet 是一种 可以嵌入 HTML 文档(即 Web 页)中并可以被执行的 Java 程序。支持 Java 的浏览器,在浏 览一个包含有 Java Applet 的 Web 页面时,该 Applet 就被下载到该浏览器中,并被 JVM 解释 执行。 目前,支持 Java2 的浏览器有:Mozilla、Netscape7 及其以上版本、Opera 和 FireFox。微 软的 Internet  Explorer 或者早期的 Netscape 版本,需要安装 Java 的插件(Plug­in)才可以正 常浏览 Java Applet 的 Web 页面。 提示:如果读者的计算机中已经安装了 J2SE  5.0 开发环境,那么 IE 就不需要再安装插 件。该插件是作为 J2SE 5.0 的一部分(JRE 5.0)安装在浏览器上的。 我们先来看 J2SE  5.0 中提供的一些 Applet 范例,展示了 Applet 的强大功能。其中每个  Applet 还提供了源代码。通过阅读这些源代码,可以了解 Java 最新的、令人激动的新特性及 其用法。  J2SE  5.0 提供的范例位于 J2SE  5.0 安装目录中的 demo 子目录下。在 MS  Windows 中, 该范例的默认安装位置是:C:jdk1.5.0demoapplets;在 Unix/Linux/Mac  OS 中,该范例的默 认安装位置是:/usr/local/jdk1.5.0/demo/applets。 在 applets 子目录下又包含了一些子目录,每一个子目录对应着一个相应的 Java Applet, 每个 Applet 展示了不同的功能。表 2.1 列出了这些范例及其功能。 表 2.1    applets 目录中的范例 范 例 说 明  Animator  演示了四种不同类型的动画  ArcTest  演示了圆弧的绘制,可以与该 Applet 交互并改变圆弧的显示属性  BarChart  演示了一个简单的条形图  Blink  演示了以不同颜色显示并闪烁的文本  CardTest  演示了几个 GUI 的部件及布局方式  Clock  演示了一个可以显示当前日期和时间的时钟  演示了用图形抖动(dithering)技术进行图形绘制,该技术实现的是从一种颜色逐渐转 DitherTest  变成另一种颜色(渐变色)  DrawTest  演示了使用鼠标在 applet 上,用不同的颜色绘制线段和点  Fractal  演示了绘制一个不规则图形  GraphicsTest  绘制不同的图形,以演示 Java 对图形的处理能力  绘制一个通过直线连接,由许多个节点(用矩形表示)组成的图形。可以拖动其中一个 GraphLayout  节点,观察其他节点是如何重新调整它们位置的情形,以演示复杂的图形交互  演示了一个有热点的图像。当鼠标移动到热点区域,该区域将会高亮显示,并在  ImageMap  appletviewer(或者浏览器)的左下角(状态栏)显示一条消息。当鼠标定位在图像中人像 嘴上的时候,会听见 applet 说“hi”  JumpingBox  在屏幕上随机移动一个矩形,可以试一试用鼠标去捕获它  演示了几个不同化学分子结构的三维视图,可以拖动鼠标从不同的角度来观察这些化学 MoleculeViewer  分子的结构  NervousText  绘制在屏幕上跳动的文本  SimpleGraph  绘制一条复杂的曲线
  • 28. 比较三种不同的排序技术。执行该 Applet 时,会出现三个 appletviewer 窗口。单击其中 SortDemo  任意一个窗口,都会开始排序,不同的排序技术所花费的时间是不同的  SpreadSheet  演示了一个简单的,由行和列组成的电子表格  TicTacToe  演示了一个叫做 TicTacToe 的游戏,由计算机和用户互弈  WireFrame  演示了四个由许多线段组成的三维图形,分别是立方体、恐龙、直升飞机和军舰 运行上述例程的方法有两种。我们以运行表 2.1 中所讲述的 ArcTest Applet 程序为例: (1)用 Java 自带的 Applet 容器 appletview.exe 来运行上述例程。 在【命令提示符】下,输入如下命令: C:Documents and SettingsJKZhong>cd C:jdk1.5.0demoapplets ArcTest  C:jdk1.5.0demoappletsArcTest>appletviewer example1.html (2)会看到 ArcTest 的运行结果,再进行如下操作,会看到如图 2.1 所示的结果。 Ø 在第一个文本框中输入 10,表示圆弧的起始角度。 Ø 在第二个文本框中输入 145,表示圆弧的终止角度。 Ø 按钮【Fill】表示绘制并填充这个圆弧,按钮【Draw】表示仅仅绘制圆弧。单击【Fill】 按钮可以看到,绘制的结果是一个从 10 开始到 145 结束的实心圆弧。 (3)直接用浏览器打开 C:jdk1.5.0demoappletsArcTest 子目录下的 example1.html 文 档,就可以运行上述 Java Applet 程序,这里以微软的 IE 为例。 Ø 如果与笔者相同,使用微软 Windows XP SP2 下的 IE,可能会看到如图 2.2 所示的结 果。IE 可能会有这样一个提示: “为帮助保护您的安全,Internet Explorer 已经限制此 文件显示可能访问您的计算机的活动内容。单击此处查看选项…” 。 Ø 这时无法看到 Java Applet 的运行结果,因为被 IE 屏蔽了。解决的方法是,在 IE 的提 示下单击鼠标右键,在弹出的菜单中选择【允许阻止的内容】 。 Ø 然后会弹出一个【安全警告】对话框,单击【是】按钮,即可看到运行如图 2.2 所示 的 Java Applet 结果。 图 2.1    appltviewer 的运行结果 图 2.2    ArcTest Applet 的运行结果 提示:如果 appletviewer 命令不能工作,或者收到一条消息,表示没有找到 appletviewer  命令,则计算机可能没有正确地设置 PATH 的环境变量。 读者需要按照 1.2.2 节讲述的方法重 新设置 PATH 变量,修改 PATH 变量后,需要重新启动计算机。
  • 29. 2.2    Java Applet 工作流程  2.2.1  组件与容器 一个组件 (Component)代表可以在屏幕上显示出来的一个图形实例, 也就是 Component  类的任意一个子类的对象。图 2.3 是以 Component 类为基础类的继承关系结构图,图中箭头 所指向的类为超类。 从图 2.3 可以看到,JApplet、JFrame、Dialog 等就是一个组件,它们都属于 Component  类的子类。所有从 Container 派生出来的类都可以包含由 Component 类派生出来的任意一个 类的对象,所以我们称之为容器(Container) 。又因为 Container 类是 Component 类的一个子 类,所以任意一个容器对象同时又是一个 Component 对象, 因此一个容器可以包含其他容器。 下面以 Panel 类对象来说明这个关系。我们把一些图形实例如矩形、椭圆等放置在一个 Panel  类的对象——panelA 中, 再把另外一些 Component 对象如菜单、 按钮等放置在另外一个 Panel  类的对象——panelB 中,最后把 panelA 和 panelB 放置在另外一个 Panel 类的对象——panelC  中。这里的例外是,Windows 类及其子类的对象,不能包含在其他容器中,否则,系统会抛 出一个异常。 Component  (抽象类)  Container  (抽象类)  JComponent  (Swing 组件)  Panel  Window  (旧式容器)  (无边框或标题栏)  Applet  Frame  (旧式 Applet 类)  (旧式框架类)  Dialog  (旧式对话框类)  JWindow  JApplets  JFrame  JDialog  (无边框或标题栏)  Applet 的基类 应用程序 对话框的基类 窗口的基类 图 2.3  以 Component 类为基础类的继承关系结构图  JComponent  类是所有在一个窗口中作为  GUI  一部分使用的  Swing  组件的基类。因为  JComponent 也是从 Container 类继承下来的,故此所有的 Swing 组件也是容器。  JApplet 类是所有 Swing Applet 类对象的基类,它由 Component 派生的 Container 类派生 出来。因此,一个 Applet 程序就从 Container 和 Component 类继承所有的方法。此外,它也 继承了旧的 Applet 类的方法。 这里需要注意以下几点: Ø  JApplet 类、JFrame 类、JDialog 类以及 JComponent 类,包含在 javax.swing 包中。 Ø  Applet 类包含在 java.applet 包中。 Ø 除此之外的类,包含在 java.awt 包中。  java.applet  包很小,它只包含这一个类和三个相关的接口,我们很少直接使用该包中的 方法。在  JApplet  类中,对旧的  Applet  类的方法进行了扩充和改进(旧类与  javax.swing  包中的 GUI 组件不完全兼容) 。所以本书所有相关的 Applet 类都扩展于 JApplet 类。 
  • 30. 2.2.2  Applet 的编译与执行 如同 Java Application 程序一样, 在执行前必须对 Applet 类的源程序进行编译, 编译的方 法与 1.4 节讲述的方法相同。  Applet 被嵌入到 HTML 页面中, 并由 Applet 容器(appeltviewer 或者 Web 浏览器)执行。  Applet 的运行由浏览器控制, 不由 Applet 中的代码控制。 当浏览器载入包含有 Applet 的 Web  页面时,它将生成一个 Applet 类的对象,然后利用 Applet 类对象的五个方法控制 Applet 的 执行。这五个方法都是 public 类型,不返回任何结果,以及不需要任何参数的方法。 表 2.2 列出了 5 个方法及其执行的相应功能。 表 2.2    Applet 默认的 5 个方法 方 法 说 明  启动 Applet。浏览器总是调用 Applet 类的默认构造器生成对象,然后调用 init()方法进行初始 void init()  化。一般在这个方法中生成 Applet 运行所需的对象并初始化 Applet 类的所有数据成员  方法由浏览器调用,启动或者重新启动  Applet。当  Applet  第一次启动时,start()方法将紧跟 void start()  在  init()方法后被浏览器调用。如果用户离开当前的  HTML  页面后,在重新返回到当前  HTML  页面时,Start()方法也将被调用。start()方法一般用来启动 applet 需要的任何附加线程  在 init()方法执行结束,  start()方法启动后,就调用此画图方法。另外,每次需要重新绘制 Applet  void paint( Graphics g )  时,也将调用此方法。本方法的典型应用,包括使用  Applet  容器传递给  paint  方法的  Graphics  对象 g 画图  当用户离开包含有该 Applet 的 HTML 页面时,浏览器调用此方法。stop()方法被调用后,将 void stop()  立即停止所有在 start()方法中启动的操作  在终止 Applet 运行时,调用 destroy()方法,以便释放 Applet 占用的,由本地操作系统管理的 void destroy()  任何系统资源。此方法执行之前,总是先调用 stop()方法 我们编写的 Applet 程序都将继承上述 5 种默认的方法。对于每个 Applet,方法调用的顺 序总是 init、start 和 paint,这就保证了每个 Applet 开始执行时都将调用这些方法。但是,并 非每个 Applet 都需要调用 5 种方法。我们可以重载上述的 5 种方法,也可以重载其中的某种 方法,以便 Applet 实现我们所设计的方法。 浏览器通过包含有 Applet 的 HTML 页面来运行 Applet 程序。 那么,在执行 Applet 之前, 首先要创建 HTML 文档。
  • 31. 2.2.3  包含 Applet 的 HTML  通常,HTML 文档以“.html”或者“htm”的扩展名结尾。一个在 HTML 页面中包含有  Applet 的 HTML 文档,必须指明 Applet 容器应该装入和执行哪一个 Applet。 下面是一个完整的包含 Applet 的 HTML 源代码: 1: <!DOCTYPE HTML PUBLIC "­//W3C//DTD HTML 4.0 Transitional//EN">  2: <HTML>  3: <HEAD>  4:  <TITLE>包含有 Applet 的 HTML 页面</TITLE>  5:  </HEAD>  6:  7:  <BODY>  8:          <APPLET  CODE = "MyAppletName.class"  9:  WIDTH = "Applet_width_in_pixels"  10:  HEIGHT = "Applet_height_in_pixels" >  11:         </APPLET>  12:  </BODY>  13: </HTML> 我们可以看到,代码第 8 行~第 11 行是隶属于<applet>和</applet>标记之间的内容,也 是一个包含 Applet 的 HTML 页面所需的最少内容,其余部分都是标准的 HTML 标记。  Applet 标记内的“CODE”属性,表示该 Applet 的.class 文件,它假设 Applet 位于包含 该 Applet 的网页所在的相同目录下。 本例指明 Applet 容器, 载入一个名为 “MyApplet Name” 的  Applet  字节码文件。如果.class  文件和  HTML  文件不在同一目录下,则需要使用一个  CODEBASE 属性, 它拥有一个 URL 值, 指明 applet 源代码的位置,  URL 可以是绝对值 (以 http://  开头) ,也可以是相对值。属性“WIDTH”表示该 Applet 在网页上显示的宽度,用像素表示。属 性“HEIGHT”表示该 Applet 在网页上显示的高度,也用像素表示。 了解前面我们介绍的 Applet 的相关知识,就可以编写一些 Applet 程序了。  2.3  绘制文本(字符串) 现在,让我们来开发一些  Applet。首先介绍一个简单的  Applet  程序——DrawString  Applet.java,就是在 Applet 上绘制一段文本(字符串) “欢迎来到《精通 Java  Web 动态图表 编程》的世界!。  ” DrawStringApplet.java 的源程序清单如下(chap02Fig2.3Fig2.3_01): 1: // Fig. 2.3_01: DrawStringApplet.java  2: // 第一个 Java Applet 程序, 绘制文本(字符串)  3:  4: // 引入相关的包 5: import java.awt.Graphics; // 引入 Graphics 类 6: import javax.swing.JApplet; // 引入 JApplet 类 7:  8: public class DrawStringApplet extends JApplet  9: {  10:  11:   // 在 applet 上绘制文本 12:  public void paint(Graphics g)  13:   {  14:       // 调用父类的 paint 方法 15:  super.paint(g);  16:  17:       g.drawString("欢迎来到 《精通 Java  Web 动态图表编程》的世界!",  25,  25);
  • 32. 18:  19:     } // paint 方法结束 20:  21: } // DrawStringApplet 类结束 提示:本书后面的例程将不再列出完整的源代码清单,请读者从 www.broadview.com.cn 网 站下载本书配套代码,查看相关目录下完整的源代码清单。 包含这个 Applet 的 web 页面文件 DrawStringApplet.html 的源程序清单如下: 1: <!DOCTYPE HTML PUBLIC "­//W3C//DTD HTML 4.0 Transitional//EN">  2: <HTML>  3: <BODY>  4:  <APPLET  CODE  "DrawStringApplet.class"  WIDTH  "320  HEIGHT  "45"  =  =  "  =  >  5:  </APPLET>  6:  </BODY>  7:  </HTML> 提示:本书所有的 Java  Applet 都将使用与本例相类似的 HTML 文档。对于不同的 Java  Applet,读者只需要修改上述代码第四行的内容, 重新设置“CODE”、 “WIDTH” “HEIGHT” 和 的属性值即可。 图 2.4 展示了名为 DrawStringApplet 的 Applet 在四个不同的 Applet 容器中的运行结果。 分别是 Appletviewer、  Netscape Web 浏览器(现在更名为 Mozilla) Opera, 、  以及微软的 Internet  Explorer Web 浏览器。 该程序说明了 Java 的几个重要特性。程序第 17 行完成的实际工作,即在屏幕上绘制一行文 本“欢迎来到《精通 Java Web 动态图表编程》的世界!。下面我们逐行来分析本程序。 ” 程序第 1 行~第 2 行: // Fig. 2.3: DrawStringApplet.java  // 第一个 Java Applet 程序, 绘制文本(字符串) 是注释。第 1 行说明例程的编号,以及源程序的文件名。第 2 行描述程序所实现的功能。
  • 33. x 轴  appletviewer 窗口 y 轴  applet 菜单 绘制区左上角是(0,0) ,绘 制区边缘在状态栏上 x 坐标从 状态栏显示 applet  左向右递增,y 坐标从上向下 当前的运行状态 递增 文本在该处的坐 标是(25,25) 绘制区域在左上角 坐标是(25,25) 状态栏 绘制区域在左上角 坐标是(25,25) 绘制区域在左上角 坐标是(25,25)  状态栏 图 2.4    DrawStringApplet 在四个不同的 Applet 容器中的运行结果 程序第 5 行: import java.awt.Graphics; // 引入 Graphics 类 是一个 import 声明, 表明 Applet 使用 java.awt 包中的 Graphics 类。Java 包含了很多预定 义的类,并按包(类的集合)的形式进行分组。Graphics  类是一个具备图形处理能力的类, 可以让 Java Applet 能够绘制直线、文本、矩形及椭圆形等图形对象。 程序第 6 行: import javax.swing.JApplet; // 引入 JApplet 类 也是一个 import 声明,表明 applet 使用 javax.swing 包中的 JApplet 类。在 Java 2 中创建 一个 Applet 时,必须引进 JApplet 类。 程序第 8 行: public class DrawStringApplet extends Japplet 声明了一个名为 DrawStringApplet 的公有 (public) 并继承了 javax.swing 包中的 JApplet  类, 类。该类的主体从程序第 9 行的左花括号“{”开始,到程序第 21 行的右花括号“}”结束。 在继承关系中,JApplet 类称为超类(也称为基类或父类) ,DrawStringApplet 被称为子类或派 生类。因为,DrawStringApplet 继承了 JApplet 类,所以,它不仅具有 JApplet 类的所有属性 
  • 34. 和方法,而且还可以增加新的方法和属性,以及扩展被继承的方法。 程序第 12 行: public void paint(Graphics g) 声明 Java Applet 的 paint 方法。前面我们提到的 paint 方法是 Java Applet 容器在载入 Applet  时,自动调用三个方法中的一个(其他两个是 init 和 start 方法) 。如果我们不在 Applet 中重 新定义该方法,那么 Applet 容器将调用继承下来的默认超类的 paint 方法。因为,默认超类 (JApplet)的 init、start 和 paint 方法为空(指明该方法的方法体中没有任何语句) ,也就是 说,超类的 paint 方法不会在 Applet 上绘制任何东西,所以在屏幕上也就不会有任何显示。  void 表示 paint 方法执行完后,不返回任何结果。paint 方法需要一个 Graphics 类的对象  g(对象也称为“类实例”或者“实例”。paint 方法使用 Graphics 类的对象 g 在 applet 上绘 ) 制各种图像对象, 如文本及各种几何图形等。  Graphics 类的对象 g 的创建由 Applet 容器负责。 为了实现在屏幕上绘制出一段文本,程序第 13 行~19 行,重载(重新定义)了超类的  paint 方法。 程序第 15 行: super.paint(g); 调用超类 JApplet 的 paint 方法。该语句是 Java Applet 程序中 paint 方法体的第一个语句。 早期的 Java Applet 在没有该语句的情况下仍然可以正常运行, 但在一个拥有众多绘图组件和  GUI 组件的复杂 Java Applet 中, 忽略该语句可能会导致严重的错误。 所以, 在编写 Java Applet  程序的时候,一定在 paint 方法的第一行设置这条语句,这是一个很好的编程习惯。 程序第 17 行: g.drawString(“欢迎来到《精通 Java Web 动态图表编程》的世界!”, 25, 25); 通过 Graphics 类的对象 g 来调用该类的 drawString 方法,以实现在 Applet 上绘制文本的 功能。drawString 方法的具体使用如表 2.3 所示。 表 2.3    drawString 方法 方法  public abstract void drawString(String str, int x, int y)  说明 用当前的字体和颜色,在指定的坐标(x,y)处,绘制字符串 str 的内容 调用的具体方法是 Graphics  类的对象名称(这里是  g)加上一个点操作符“.” ,再加入 所调用的方法名称(这里是 drawString) 。方法名后面跟着一对括号,括号内包含有执行该方 法所需的参数。  drawString 方法需要三个参数,第一个是  String 类的字符串参数,表示要绘制的文本。 最后两个是  int  类型的整数型参数,表示要绘制文本的第一个字符左下角所在的位置,该位 置用 Java 的平面坐标系来表示。 图 2.5 说明了 Java 坐标系。坐标原点位于左上角,以像素为单位。像素是计算机屏幕上 最小的显示单位。在 Java 的坐标系中,第一个是 x 坐标,表示当前位置为水平方向,距离坐 标原点 x 个像素;第二个是 y 坐标,表示当前位置为垂直方向,距离坐标原点 y 个像素。在  Appletviewer 中,坐标原点(0,0)正好位于 Applet 菜单的下方。在 Web 浏览器中,原点(0,  0)位于 Applet 执行的矩形区域的左上角(如图 2.5 所示) 。
  • 35. 坐标原点(0,0)  25 +X  X 轴  25 欢迎来到《精 (x,y) +Y  Y 轴 图 2.5    Java 坐标系 计算机在屏幕上显示的内容都是由屏幕上的每一个像素组成的。例如,计算机显示器的 分辨率是 800´600,表示计算机屏幕上的每一行由 800 个点组成,共有 600 行,整个计算机 屏幕共有  480 000  个像素。现在的计算机可以支持更高的分辨率,也就是说,屏幕上可以显 示更多的像素。Java Applet 在屏幕上的大小,依赖于屏幕的大小与分辨率。对于大小相同的 屏幕来说,分辨率越高,Java Applet 在屏幕上的显示就越小,这是因为屏幕上的像素更多了。 程序第 17 行,将在坐标(25,25)上绘制“欢迎来到《精通 Java Web 动态图表编程》 的世界!”如图 2.4、图 2.5 所示。  Java Applet 可以在 paint 方法中多次调用 drawString 方法,在屏幕的任意位置绘制文本。 我们就可以利用这种方法来显示多行文本。  DrawMultiStringApplet.java  演示了如何绘制多行文本(chap02Fig2.3Fig2.3_02) 。通过 调用该目录下的 DrawMultiStringApplet.html 的运行结果如图 2.6 所示。 坐标(25,25) 坐标(25,40) 图 2.6  在 Java Applet 上绘制多行文本 本例同第一个 Java  Applet 程序相比,仅仅增加了第 18 行。图 2.7 所示的两行文本之所 以能够左对齐, 就是因为第 17 行和第 18 行的 drawString 方法, 都使用了相同的 x 坐标(25)。 此外,两者使用不同的 y 坐标(第 17 行是 25,第 18 行是 40)。因此在 Applet 相同的水平 位置,不同的垂直位置上显示文本,也就实现了左对齐的功能了。如果把第 17 行和第 18 行 的内容相互交换,则运行的结果也是相同的,这说明文本的位置由其坐标决定。 绘制文本时,不能通过换行符(n)来实现文本换行的功能。如  DrawMultiStringApplet2.  java(chap02Fig2.3Fig2.3_03)和 DrawStringApplet.java 相比,主要是第 17 行语句不同,如下 所示: g.drawString(“欢迎来到《精通 Java Web 动态图表编程》的世界!n 不用不知道,Java 真奇妙!”, 25, 25); 即使绘制的文本中包含有换行符(n),运行结果如图 2.7 所示,字符串并没有分行显示。
  • 36. 图 2.7  用n 绘制多行文本 提示: 在某些 Java 版本中,运行结果会在字符串中的换行符位置处显示一个黑色小方框, 表示 drawString 方法不能识别该字符。  2.4  绘制线段 如果要在 Java Applet 中绘制一段直线,就需要调用 Graphics 类的 drawLine 方法。 先来 看 drawLine 的用法,如表 2.4 所示。 表 2.4    drawLine 方法 方法  public abstract void drawLine(int x1, int y1, int x2, int y2)  说明 用当前颜色在端点 1(x1,y1)和端点 2(x2,y2)之间绘制一条直线  drawLine 方法需要四个参数,表示该线段的两个端点。第一个端点的 x 坐标和 y 坐标分 别由第 x1、y1 个参数决定;第二个端点的 x 坐标和 y 坐标分别由第 x2、y2 个参数决定。所 有的坐标值都相对于该 Applet 的左上角坐标(0,0)。drawLine 方法就是在这两个端点之间 绘制一条直线。 程序 drawLineApplet.java(chap02Fig2.4)绘制了 11 条直线、1 个三角形和 1 个矩形。源 程序的运行结果如图 2.8 所示。 坐标(160,10) 坐标(90,10) 坐标(10,10) 坐标(230,10) 坐标(90,110) 坐标(330,10) 坐标(110,110) 坐标(210,110) 坐标(230,110) 坐标(330,110) 图 2.8    drawLineApplet.java  的运行结果 程序第 5 行: import java.awt.Graphics; // 引入 Graphics 类 导入 Applet 所需的 Graphics 类(来自 java.awt 包)实际上, 。 如果总是使用完整的 Graphics  类名(java.awt.Graphics),即包含完整的包名和类名,就不需要第 5 行的 import 语句了。也 就是说,如果没有第 5 条语句,那么第 12 行就可以改写成如下形式:
  • 37. public void paint(java.awt.Graphics g) 程序第 6 行: import javax.swing.*; // 引入 javax.swing 包中所有的类 这里告诉编译器,Applet  可以使用  javax.swing 包中所有的类。星号(*)表示  javax.swing  包中所有的类都可以被编译器所使用。不过,在程序开始运行时,它并不会将该包中所有的 类都载入内存中,而只是装载程序使用的那些类。本例仅加载了 JApplet 类。 程序第 15 行: super.paint(g); 调用超类 JApplet 的 paint 方法。编写 Java  Applet 程序时,在 paint 方法的第一行设置这 条语句。 程序第 18 行~第 21 行: for (int i = 1; i <= 11 ; i++ )  {  g.drawLine(10, 10, 90, i * 10 );  } 运行了一个循环语句,调用了 11 次 drawLine 方法,在 Applet 上绘制了 11 条线段。这  11 条线段第 1 个端点的坐标都是相同的,即(10,10) ,第 2 个端点坐标的 x 坐标也是完全 相同的。只是 y 坐标不同, 每循环一次,y 坐标依次增加 10 像素。该循环的运行结果如图 2.8  中最左边的图形。 程序第 24 行~第 26 行: g.drawLine(160, 10, 110, 110);  g.drawLine(160, 10, 210, 110);  g.drawLine(110, 110, 210, 110); 直接调用 3 次 drawLine 方法,在 Applet 上绘制了由 3 条线段而组成的一个三角形。这 个三角形的三个端点坐标分别是(160,10)(110,110)和(210,110) 、 。用 drawLine 方法 在两个端点之间绘制一条直线,运行结果如图 2.8 中的三角形。 程序第 29 行~第 32 行: g.drawLine(230, 10, 230, 110);  g.drawLine(230, 10, 330, 10);  g.drawLine(330, 10, 330, 110);  g.drawLine(230, 110, 330, 110); 直接调用 4 次 drawLine 方法,在 Applet 上绘制了由 4 条线段组成的一个矩形。这个矩 形的 4 个端点坐标分别是: (230,10)(330,10)(330,110)和(230,110) 、 、 。用 drawLine  方法绘制该矩形的四条边,运行结果如图 2.8 中右边的矩形。
  • 38. 2.5  色彩管理 早期的电脑系统是没有色彩管理这个概念的, 直到 90 年代苹果电脑推出了 ColorSync 1.0  色彩管理,它局限于苹果电脑之间的色彩控制。1993  年国际色彩联盟(Interational  Color  Consortium)成立,制定了开放式色彩管理规则──ICC,使色彩在不同品牌的电脑输出/输 入设备之间能够互相沟通,产生一致的色彩效果。随后,苹果电脑系统、微软的  Windows  系统、Sun 微电脑系统等相继支持 ICC 规则。  2.5.1  色彩基础 当我们从事设计的时候,最想了解的是如何有效地使用色彩。例如,使用什么色彩会产 生什么效果,用什么色彩代表什么性格,色彩如何搭配才美观,等等。要了解这些内容,首 先要认识色彩,知道色彩具有什么物理特性,以及色彩是如何形成的。  1.什么是色彩 我们生活在一个多彩的世界里。白天,在阳光的照耀下,各种色彩争奇斗艳,并随着照 射光的改变而变化无穷。但是,每当黄昏,大地上的景物无论多么鲜艳,都将被夜幕吞没。 在漆黑的夜晚,我们不但看不到物体的颜色,甚至连物体的外形也分辨不清。同样,在暗室 里,我们什么色彩也感觉不到。这些事实告诉我们,没有光就没有色,光是人们感知色彩的 必要条件。色彩来源于光,所以,光是色彩的源泉,色彩是光的表现。要了解色彩产生的原 理,必须对光做进一步的了解。 光的物理性质由它的波长和能量来决定。波长决定了光的颜色,能量决定了光的强度。 光映像到我们的眼睛时,波长不同决定了光的色相不同。波长相同能量不同,则决定了色彩 明暗的不同。 只有波长 380nm~780nm(1nm=10-6 mm)光的辐射才能引起人们的视觉感知,这段光波 叫做可见光。我们看到的色彩,事实上是以光为媒体的一种感觉。色彩是人在接受光的刺激 后,视网膜的兴奋传送到大脑中枢而产生的印象。 英国科学家牛顿在 1666 年发现,把太阳光经过三棱镜折射,然后投射到白色屏幕上,会 显出一条像彩虹一样美丽的色光带谱,从红色开始,依次是橙、黄、绿、青、蓝、紫七色, 如图 2.9 所示。 这条依次排列的彩色光带称为光谱。这种被分解过的色 光,即使再通过三棱镜也不会分解为其他的色光,我们把光 谱中不能再分解的色光叫做单色光。由单色光混合而成的光 叫做复色光。自然界的太阳光,白炽灯和日光灯发出的光都 图 2.9  光谱 是复色光。  2.色彩的三属性 我们不仅观察物体的色彩,同时还会注意到形状、面积、体积,以致该物体的功能和所 处的环境。这些对色彩的感觉都会产生影响。为了寻找规律,人们抽出纯粹色知觉的要素, 认为构成色彩的基本要素是色相、明度、纯度,这就是色彩的三属性。 Ø 色相(Hue) 色相指色的相貌与名称。色轮是表示最基本色相关系的色表。色轮上  90 度角内的几种色彩称作同类色,也叫做近邻色或姐妹色;90 角以外的色 图 2.10  色相
  • 39. 彩称为对比色。色轮上相对位置的色叫做补色,也称为相反色,如图 2.10 所示。 Ø 明度(Value 或 Brightness) 人眼之所以能够看到物体的明暗,是因为物体所反射色光的光量(热量)存在差异。光 量越多,明度越高,反之明度越低。色料的色彩明度,取决于混色中白色和黑色含量的多少, 如图 2.11 所示。 Ø 纯度(Chroma) 所谓色彩的纯度,是指色彩鲜艳与混浊的程度,也称为彩度。色轮上各颜色都是纯色, 纯度最高。色料的混合中,越混合,色彩的纯度越低,如图 2.12 所示。 图 2.11  明度变化 图 2.12  纯度变化  3.混色理论 Ø 原色 无法用其他色彩混合得到的基本色彩称为原色。原色按照一定比例混合可以得到各种色 彩。究竟原色有多少种,又是哪几种颜色?历史上不同的学者有不同的说法,但基本上分为 三原色说和四原色说。 色彩的混色有两种类型:加法混色和减法混色。 Ø 加法混色 色光三原色(红、绿、蓝)按照一定的比例混合可以得到各种色光。三原色光等量混合 可以得到白色。色光混色和色料混色刚好相反,色光混色是越混合越明亮,所以称为加法混 色。我们熟悉的电视机和电脑的 CRT 显示器产生的色彩方式就属于加法混色,如图 2.13A 所 示。 Ø 减法混色 色料三原色(品红、黄、青蓝)按照一定的比例混合可以得到各种色彩。理论上三原色 等量混合可以得到黑色。因为色料越混合越灰暗,所以称为减法混色。水彩、油画、印刷等, 它们产生各种颜色的方法都是减法混色,如图 2.13B 所示。  A:加法混色  B:减法混色 图 2.13  色彩的混合 这两种混色方法,通过对相对应的色彩取反操作,就可以相互转换了。
  • 40. 4.色彩空间及色彩模型 “色彩空间”一词源于英语“Color Space” ,也称做“色域” 。实际上就是各种色彩的集 合,色彩的种类越多,色彩空间越大,能够表现的色彩范围(即色域)越广。对于具体的图 像设备而言,其色彩空间就是它所能表现色彩的总和。 自然界中的色彩千变万化,我们要准确地表示色彩空间就要使用到色彩模型。人们建立 了多种色彩模型,以一维、二维、三维甚至四维空间坐标来规范这些色彩。 最常用的色彩模型有:  HSB、  RGB、  CMYK 以及 CIE Lab 等色彩模型。 其中 RGB、  CMYK  是惟一两种专门面向数码设计和出版印刷的色彩模型。这里我们介绍  RGB、CMYK  色彩模 型,认识这两个色彩模型,有助于我们在设计中准确地把握色彩。 所谓 RGB 就是:红(Red) 、绿(Green) 、蓝(Blue)三种色光原色。RGB 色彩模型的 混色属于加法混色。每种原色的数值越高,色彩越明亮。R、G、B  都为  0  时是黑色,都为  255 时是白色。  RGB 是电脑设计中最直接的色彩表示方法。电脑中的 24 位真彩图像,就是采用  RGB  模型来精确记录色彩的。所以,在电脑中利用 RGB 数值可以精确取得某种颜色。  R、G、B 数值和色彩的三属性没有直接的联系,不能揭示色彩之间的关系。所以在进行 配色设计时,RGB 模型就不是那么适用了。  RGB 色彩模型又有 AdobeRGB、AppleRGB、sRGB 等几种,这些 RGB 色彩模型大多与 显示设备、输入设备(数码相机、扫描仪)相关联。 Ø“sRGB”是所谓的“标准  RGB  色彩模型” 。这种色彩空间由微软公司与惠普公司于  1997 年联合确立,被许多软件、硬件厂商所采用,逐步成为许多扫描仪、低档打印机 和软件的默认色彩空间。同样采用 sRGB 色彩空间的设备之间,可以实现色彩相互模 拟,但却是通过牺牲色彩范围来实现各种设备之间色彩的一致性的,这是所有  RGB  色彩空间中最狭窄的一个。 Ø“Apple RGB”是苹果公司早期为苹果显示器制定的色彩模式。 其色彩范围并不比 sRGB  大多少。这种显示器已经很少使用,这一标准已逐步被淘汰。 Ø“Adobe RGB(1998) ”由 Adobe  公司制定。其最早使用在 Photoshop 5.x 中,被称为  SMPTE­240M。它具备非常大的色彩范围,绝大部分色彩又是设备可呈现的,这一色 彩模型全部包含了  CMYK  的色彩范围,为印刷输出提供了便利,可以更好地还原原 稿的色彩,在出版印刷领域得到了广泛应用。 Ø“ColorMatch RGB”是由 Radius 公司定义的色彩模型,与该公司的 Pressview  显示器 的本机色彩模型相符合。 “Wide Gamut RGB”是用纯谱色原色定义的色彩范围的 RGB  色彩空间。这种空间的色域包括了几乎所有的可见色,比典型的显示器所显示的色域 还要宽。但是,由于这一色彩范围中的很多色彩不能在  RGB  显示器或印刷上准确重 现,所以这一色彩模型并没有太大的实用价值。  RGB 只是光色的表示模型,在印刷行业中,使用另外一种色彩模型 CMYK。CMYK 分 别是青色(Cyan) 、品红(Magenta) 、黄色(Yellow) 、黑色(Black)三种油墨色。每一种颜 色都用百分比来表示,而不是 RGB 那样的 256 级度。 理论上将印刷三原色,即青色(Cyan) 、品红(Magenta) 、黄色(Yellow)混合之后, 应该可以将红绿蓝光全部吸收而得到纯正的黑色,只是现实生活中找不到这种光线吸收,反 射特性都十分完美的颜料,将三种颜色混合后还是会有些许光线反射出来,呈现暗灰色或深 褐色。事实上除了黑色外,用颜料三原色也无法混合出许多暗色系的颜色,为了弥补这个缺 点,在实际印刷的时候,额外加入黑色的颜料,以解决无法产生黑色的问题。因此就有所谓  CMYK 的色彩模式,K 表示黑色(Black) 。
  • 41. 2.5.2  Java 的色彩管理  Java 以跨平台,与硬件无关的方式支持色彩管理。Java 的色彩管理功能来自于 java.awt  包中的 Color 类。Color 类允许在应用程序中指定自己需要的任意色彩,不用担心因计算机的 硬件设备所支持的绘制方式不同而引起的颜色差别。Java 支持 sRGB 色彩模型,将自动找到 最接近的颜色。  Java 对色彩的管理都被封装在 java.awt.Color 类中。  Color 类提供了对颜色控制的常量和方法。表 2.5 列出了 Color 类中部分预定义的颜色常 量及其 RGB 值。 表 2.5  部分 Color 颜色常量及其 RGB 值 颜色常量 颜 色  RGB 值  public final static Color ORANGE/orange  橙色  255, 200, 0  public final static Color PINK/pink  粉红色  255, 175, 175  public final static Color CYAN/cyan  青色  0, 255, 255  public final static Color MAGENTA/magenta  品红色  255, 0, 255  public final static Color YELLOW/yellow  黄色  255, 255, 0  public final static Color RED/red  红色  255, 0, 0  public final static Color BLACK/black  黑色  0, 0, 0  public final static Color WHITE/white  白色  255, 255, 255  public final static Color LIGHT_GRAY/lightGray  淡灰色  192, 192, 192  颜色常量的名称有两种写法,一种是小写,另一种是大写。我们建议采用大写的形式表 达 Java 的颜色常量,因为它们更符合常量的命名约定。  Color  类通过设置不同的三个原色:红、绿、蓝(RGB)的值而创建各种颜色。这三个 原色的合并值称为 RGB 值。RGB 中的每个值又可以表示为 0~255 范围内的某个整数,或者 表示为 0.0~1.0 之间的浮点数。RGB 中的 R 表示颜色中红色的数值,G 表示绿色的数值,而  B 表示蓝色的数值。数值越大,表示相应的色彩含量就越多。RBG 总共可以产生的颜色总数 为 256´256´256,即 167 77 2 16 种颜色。表 2.6 列出了 Color 的方法以及 Graphics 类中与之相 关的方法。 表 2.6  Color 的方法以及 Graphics 类中与之相关的方法  Color 的构造器及部分方法  public Color(int r, int g, int b)      // Color class  创建一种基于红色、绿色和蓝色的颜色,每种颜色的表示方法为 0~255 之间的一个整数  public Color(float r, float g, float b)    // Color class  创建一种基于红色、绿色和蓝色的颜色,每种颜色的表示方法为 0~1 之间的一个浮点数  public int getRed()  // Color class  返回一个 0~255 之间的,表示 RGB 中红色的整数值  public int getGreen()  // Color class  返回一个 0~255 之间的,表示 RGB 中绿色的整数值  public int getBlue()  // Color class  返回一个 0~255 之间的,表示 RGB 中蓝色的整数值
  • 42. 续表  Graphics 类中操作 Color 对象的方法  public abstract Color getColor()    // Graphics class  返回一个表示在图形环境中当前绘图颜色的对象  public abstract void setColor(Color c)      // Graphics class  将当前的绘图颜色设置为 Color 对象 c 所定义的颜色  Color 类提供了两个构造器,一个接收三个 int 参数,另一个接收三个 float 参数。每个参 数分别表示  RGB  中红色、绿色和蓝色的数值。int  的值介于  0~255  之间,float  的值介于  0  和 1 之间。  Color 类的 getRed、getGreen、getBlue 方法都返回一个 0~255 之间的一个整数,该整数 分别代表图像环境中,当前颜色对象的红色、绿色和蓝色的数值。  Graphics 类的 getColor 方法,返回一个代表当前绘制颜色的 Color 对象。setColor 方法设 置当前的绘制颜色。  2.6  字体控制 大多数用于字体控制的方法和常量都属于 Font 类。 表 2.7 列出了 Font 类中部分预定义的 字体常量、方法,以及 Graphics 类中与 Font 类相关的一些方法。 表 2.7    Font 的方法以及 Graphics 类中与之相关的方法  Font 的构造器及部分方法  public final static int PLAIN  // Font class  表示普通字体风格的常量  public final static int HOLD  // Font class  表示粗体字体风格的常量  public final static int ITALIC  // Font class  表示斜体字体风格的常量  public Font(String name, int style, int size)  // Font class  用指定的字体,风格和大小创建一个新的 Font 对象  public int getStyle()  // Font class  返回一个表示当前字体风格的整数值  public int getSize()  // Font class  返回一个表示当前字体大小的整数值  public String getName()  // Font class  续表  Font 的构造器及部分方法 返回一个表示当前字体的字体名称的字符串  public String getFamily()  // Font class  返回一个表示当前字体的字体家族名称的字符串  public boolean isPlain()  // Font class  如果字体的风格为普通字体,则返回真,否则返回假  public boolean isBold()  // Font class
  • 43. 如果字体的风格为粗体字体,则返回真,否则返回假  public boolean isItalic()  // Font class  如果字体的风格为斜体字体,则返回真,否则返回假  Graphics 类中操作 Font 对象的方法  public Font getFont()      // Graphics class  返回一个表示当前字体的 Font 对象引用  public void setFont(Font font)      // Graphics class  将当前字体设置为 Font  对象  font  所定义的字体  Font 构造器接收三个参数:字体名称、字体风格和字体大小。字体名称是运行程序的系 统当前所支持的任意字体,如 Courier、Helvetica 和 TimesRoman(注:字体随操作系统的不 同而相差较大,Java 确保了 Courier、Helvetica、TimesRoman、Dialog 和 DialogInput 在不同 的操作系统中有相同的显示) 。如果应用程序指定的字体不可用,则 Java  将使用系统的默认 字体来取代它。  Font 类的 static 常量 PLAIN、BOLD、ITALIC 用于说明字体风格,分别代表普通、粗体 及斜体字体。字体风格可以组合使用,如 Font.PLAIN+Font.BOLD 等。字体大小用磅(point) 来表示,1 磅是 1/72 英寸(注:这里说的磅,就是计算机显示器上所显示的一个像素。该像 素的大小,与计算机当前设置的分辨率相关) 。  Font 类的 getStyle、getSize、getName 和 getFamily 四个方法,分别返回一个当前字体对 象的风格、字体大小、字体名称及字体隶属的字体家族的结果。字体对象的风格及字体的大 小,返回的结果都是整数值,而字体名称及字体隶属的字体家族返回的结果为字符串。  Font 类的 isPlain、isBold 及 isItalic 方法返回一个布尔值。这三个方法分别测试当前字体 是否是普通、粗体或者斜体风格,并返回相应的结果。  Graphics 类的 getFont 方法用于获得当前字体, 即获得在当前图形环境下绘制文本所用的 字体。Graphics 类的 setFont 方法用于设置当前字体,即指定当前图形环境下绘制文本所用的 字体。setFont 方法以一个 Font 对象为参数。  DrawFontWithColor.java(chap02Fig2.6Fig2.6_01)演示了如何使用不同字体和颜色来绘 制文本,运行结果如图 2.14 所示。 图 2.14  用不同颜色及字体绘制文本 程序第 5 行~第 6 行: import java.awt.*; // 引入 java.awt 包中所有的类 import javax.swing.*; // 引入 javax.swing 包中所有的类 分别引入了 java.awt 包和 javax.swing 包中所有的类。 程序第 16 行, 声明了一个名为 text 的字符串变量, 并初始化它的值为“欢迎来到 Java Web
  • 44. 动态图表编程的世界!”。 程序第 18 行~第 20 行: g.setColor(Color.BLUE);  g.setFont(new Font("宋体", Font.PLAIN, 30));  g.drawString( text, 10, 25 ); 程序第 18 行,使用 Graphics 类的 setColor 方法,设置当前图形环境下,绘制图形对象 或文本对象的颜色为蓝色。颜色的设定方法是直接调用 Color 类里面的静态变量。 程序第 19 行,使用 Graphics 类的 setFont 方法,设置当前图形环境下,绘制文本的字体 为宋体,字体风格为普通字体,字体大小为 30 磅。 程序第 20 行,在 Applet 坐标(10,25)处,绘制文本“欢迎来到 Java Web 动态图表编 程的世界!。该文本的字体为宋体,字体风格为普通字体,大小为 30 磅,颜色为蓝色。 ” 程序第 22 行~第 24 行、第 26 行~第 28 行、第 30 行~第 32 行,使用类似的方法,绘 制了不同颜色及字体的文本。 注意,程序第 22 行: g.setColor(new Color(0.0f, 0.0f, 0.0f)); 设置颜色的方法与第 18 行不同, 采用的是由一个 Color 类接收三个浮点数的构造器所创 建的 Color 类对象。程序第 30 行,采用另外一个接收三个整数的构造器来创建 Color 对象。 每次调用 Color 的构造器后,都将 Color 类的对象传递给 Graphics 类的 setColor 方法,以改 变绘制对象的颜色。程序第 23 行、第 27 行和第 31 行,分别重新设定字体对象,都是调用  Font 类的构造器来创建一个新的 Font 对象,并将该对象传递给 Graphics 类的 setFont 方法。 在调用 Font 类的构造器创建 Font 对象时,都提供了三个参数,分别是字体名称、字体风格 和字体大小。 在设置好绘制图形对象或文本颜色及字体对象后,应用程序就将一直使用定义好的颜色 和字体进行绘制,除非重新定义新的颜色和字体。我们在  DrawFontWithColor.java  程序中一 共设置了四种不同的颜色和字体,运行结果如图 2.15 所示,共绘制了四种不同颜色及字体的 文本。 在创建一个  Font  对象之前,我们需要知道本地操作系统中究竟有哪些是可供使用的字 体。ShowFonts.java  (chap02Fig2.6Fig2.6_02)将列出本地操作系统中所有可供使用的字体清 单,运行结果如图 2.15 所示。 图 2.15  获取本地操作系统的可用字体 我们调用 java.awt 包中 GraphicsEnvironment 类中的 getAllFonts 方法,来获得本地操作系 统中所有可供使用的字体清单。getAllFonts 方法,返回一个 Font 对象数组,其中包含了当前 操作系统中所有可用的字体对象。数组中每个 Font 实例,默认的字体大小为 1 磅,一般需要
  • 45. 重新设置字体大小,否则会因字体太小而无法看清楚。 程序第 10 行~第 12 行:声明了三个变量。 一个用于绘制字符串对象 text,一个 Graphics  Environment 实例 e,以及一个 Font 数组对象 fonts。 程序第 14 行~第 18 行: public void init()  {  e = GraphicsEnvironment.getLocalGraphicsEnvironment();  fonts= e.getAllFonts();  } 在 Java  Applet 默认的第一个调用的 init 方法中,对 GraphicsEnvironment 实例 e,使用  GraphicsEnvironment 的静态方法 getLocalGraphicsEnvironment 进行初始化。然后在 e 中调用  GraphicsEnvironment 类的 getAllFonts 方法, Font 数组对象 fonts 进行初始化。 Java Applet  对 在 类的 paint 方法中,将系统的可用字体全部绘制出来。 程序第 26 行~第 31 行: for (int i = 0; i< fonts.length ­ 1 ; i++)  {  g.setFont(new Font(fonts[i].getName(), Font.PLAIN  ,15));  g.drawString("Font name:" + fonts[i].getName() +  ", 效果:" + text, 40, 30 + 20 * i);  } 使用一个循环,将 fonts 字体数组中的 Font 对象一一取出,并调用 Font 的构造器来创建 新的字体对象。注意,我们设置的字体风格都是普通字体,字体大小为 15 磅。然后用我们 设定的字体,绘制出一段相同的文本,如图 2.15 所示。某些字体并不能正确地显示中文, 在这种情况下,Java 用一个方框来表示无法用当前设定的字体来绘制该字符。  2.7  绘制矩形 在 Web 图表的绘制中,矩形是最基本的几何图形之一。在第 2.4 节,我们阐述了如何利 用 Graphics 类的 drawLine 方法,通过绘制四条直线来组成一个矩形的例子。其实 Graphics  类提供了直接绘制矩形的各种方法,我们直接调用这些方法,就可以方便地绘制矩形。  2.7.1  绘制普通矩形  1.绘制空心矩形 本节提供一些绘制矩形的 Graphics 方法。表 2.8 列出了绘制普通(空心)矩形的方法。 表 2.8  绘制普通矩形的 Graphics 方法  Public void drawRect(int x, int y, int width, int height)  用当前颜色绘制一个矩形,其左上角坐标为(x,y),宽度为 width,  高度为 height(如图 2.16A 所示)  public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)  用当前颜色绘制一个圆角矩形,其左上角坐标为(x, y),宽度为 width,  高度为 height,arcWidth 以及 arcHeight 指定圆角 的弧宽和弧高(如图 2.16B 所示)
  • 46. 弧宽(arcWidth) (x,y) (height) (x,y) 度高 度高 弧高 (arcHeight)   (height) 宽度(width) 宽度(width) A: drawRect  B: drawRoundRect 图 2.16  普通(空心)矩形的绘制方法 程序 DrawRectApplet.java(chap02Fig2.7Fig2.7_01)绘制了三个矩形:一个方角矩形, 两个圆角矩形。 程序中使用了一种名为“汉真广标”的字体,程序第  17  行,如果读者的计算机中没有 这种字体,Java  将自动调用默认的系统字体(宋体)来代替“汉真广标” ,源程序的运行结 果如图 2.17 所示。 图 2.17  DrawRectApplet.java 的运行结果 程序第 17 行: g.setFont(new Font("汉真广标", Font.PLAIN, 35)); 设置字体为“汉真广标”,字体风格为普通,字体大小为  35  磅。程序第 18  行,用设置 好的“汉真广标”字体在坐标(10,40)处,绘制文本“普通矩形的画法” 。如图 2.17①所示。 程序第 21 行~第 22 行: g.setColor(Color.RED);  g.drawRect(10, 60, 150, 120); 绘制一个左上角顶点坐标为(10,60) ,宽度为 150 磅,高度为 120  磅的矩形,注意, 程序第 21 行,设置当前的绘制颜色为 Color.RED,所以该矩形的边框是红色(如图 2.17②)。 程序第 25 行~第 28 行: g.setColor( Color.LIGHT_GRAY );  g.drawRect( 210, 60, 150, 120 );  g.setColor( Color.BLUE );  g.drawRoundRect( 210, 60, 150, 120, 60, 40); 这里我们绘制了一个圆角矩形。为了说明圆角矩形和方角矩形,以及圆角矩形和圆角的 关系,程序第 25 行~第 26 行,绘制了一个边框为浅灰色,宽度为 150 磅,高度为 120 磅的 普通矩形。程序第 27 行,重新设置当前的绘制颜色为蓝色。程序第 28 行,绘制了一个圆角 矩形。注意,圆角矩形的构造器接收 6 个参数,前面四个参数和程序第 26 行,绘制的矩形语 句完全一样。它定义了圆角矩形左上角坐标,以及宽度和高度,也就是说,圆角矩形的绘制
  • 47. 区域就在这四个参数决定的矩形之中。圆角矩形四个边角的弧宽和弧高,是由最后两个参数 决定的。本例中,圆弧的宽度为 60 磅,高度为 40 磅。从图 2.17③中,可以看到,最后两个 参数实际是上绘制了一个长边长为 60 磅,短边长为 40 磅的一个椭圆。圆角矩形的弧线的形 状正好和这个椭圆的 1/4 相吻合。如果弧宽和弧高的数值相同,则椭圆就变成了一个正圆形。 程序第 25 行~第 28 行: g.setColor( Color.LIGHT_GRAY );  g.drawRect( 410, 60, 150, 120 );  g.setColor( Color.BLUE );  g.drawRoundRect( 410, 60, 150, 120, 80, 80); 这里我们又画了一个圆角矩形,除了圆角矩形左上角顶点坐标不同外,其他部分基本和 前例相同。另外,程序第 28 行的语句中,drawRoundRect 方法最后两个参数,弧宽和弧高是 相同的,所以,本圆角矩形的四个边角弧线就变成正圆形了,如图 2.17④所示。如果 width、  height、arcWidth 和 arcHeight 的取值完全相同,则最终生成一个正圆;如果最后两个参数的 取值同时为 0,则最终生成一个正方形。  2.绘制实心矩形 绘制实心矩形就是填充矩形。表 2.9 列出了 Graphics 类中绘制实心矩形的方法。 表 2.9  绘制实心矩形的 Graphics 方法  Public void fillRect(int x, int y, int width, int height)  用当前颜色绘制一个实心矩形,其左上角坐标为(x, y),宽度为 width,  高度为 height  public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)  用当前颜色绘制一个实心圆角矩形,其左上角坐标为(x, y),宽度为 width,  高度为 height,arcWidth 以及 arcHeight 指定 圆角的弧宽和弧高 程序 FillRectApplet.java (chap02Fig2.7Fig2.7_02)演示了如何绘制实心矩形,运行结果如 图 2.18 所示。 图 2.18    FillRectApplet.java 的运行结果 本程序和前例几乎相同,仅仅是在程序第 22 行将原来的 drawRect 方法替换成了 fillRect  方法: g.fillRect(10, 60, 150, 120); 将程序第 28 行,drawRoundRect 方法替换成 fillRoundRect 方法:
  • 48. g.fillRoundRect( 210, 60, 150, 120, 60, 40); 程序第 34 行,进行了同样的替换,这样就实现了填充矩形的功能。  2.7.2  绘制 3D 矩形  1.利用 draw3DRect  来绘制空心 3D 矩形 表 2.10 列出了 Graphics 类中绘制空心 3D 矩形的方法。 表 2.10  绘制空心 3D 矩形的 Graphics 方法  Public void draw3DRect(int x, int y, int width, int height, Boolean b)  用当前颜色绘制一个空心 3D 矩形,其左上角坐标为(x,  y),宽度为 width,高度为 height,如果 b 为真,则该矩形向外 凸出;如果 b 为假,则该矩形向内凹陷 程序 Draw3DRectApplet.java  (chap02Fig2.7Fig2.7_03)  演示了如何绘制空心 3D 矩形, 运行结果如图 2.19 所示。  draw3DRect 方法接收 5 个参数。其中前两个表示矩形左上角顶点坐标,接下来的两个参 数表示矩形的宽度和高度。 最后一个参数是个布尔值, 它表示要绘制的矩形是向外凸出 (true) 的,还是向内凹陷(false)的。  draw3DRect 方法,通过用原色绘制并填充两条矩形边,再用稍深的颜色绘制另外两条矩 形边。通过这种方式来表现三维效果。向外凸出的矩形用原色绘制上面和左边两条矩形边, 而向内凹陷(false)的矩形是用原色绘制下面和右边的两条矩形边。注意,某些颜色并不能 体现三维效果。  Draw3DRectApplet.java 演示了上述方法的用法。为了取得比较明显的三维效果,我们在 程序第 20 行,设置当前的绘制颜色为浅灰色,从程序的实际运行结果来看,获得了明显的三 维效果,如图 2.19 所示。  2.利用 fill3DRect 来绘制实心 3D 矩形 绘制实心 3D 矩形与 draw3DRect 方法所接收的参数一样,只是将 draw3DRect 方法名替 换成了 fill3DRect 方法名。表 2.11 列出了 Graphics 类中绘制实心 3D 矩形的方法。 表 2.11  绘制实心 3D 矩形的 Graphics 方法  Public void fill3DRect(int x, int y, int width, int height, Boolean b)  用当前颜色绘制一个实心 3D 矩形,其左上角坐标为(x,  y),宽度为 width,高度为 height,如果 b 为真,则该矩形向外 凸出;如果 b 为假,则该矩形向内凹陷 程序 Fill3DRectApplet.java (chap02Fig2.7Fig2.7_04)  演示了如何绘制实心 3D 矩形,运 行结果如图 2.20 所示。
  • 49. 图 2.19    FillRectApplet.java 的运行结果 图 2.20    Fill3DRectApplet.java 的运行结果  3.利用绘制多个实心矩形来生成 3D 矩形 上节所讲述的  3D  矩形,只是具有一些凸出和凹陷感觉的平面图形。本节提供一个真正 绘制实心 3D 矩形的方法。我们通过绘制由一组红色、重叠在一起的、有凸出感的 3D 矩形所 组成的图形,来实现绘制 3D 矩形的功能。  New3DRectApplet.java(chap02Fig2.7Fig2.7_05)演示了如何绘制不同风格  3D  矩形的方 法,运行结果如图 2.21 所示。 ① ② ③ ④ 图 2.21    New3DRectApplet.java 的运行结果 程序第 20 行, 调用 Color 的构造器, 创建了一个 Color 对象 red。  RGB 取值设定为  255、  255、0,表示红色。 程序第 21 行: g.setColor(red.darker()); //重新设置当前颜色为深红色 调用 Color 类的 darker 方法,重新创建一个比当前颜色更深一些的颜色作为当前颜色。 因为程序第 20 行,创建了一个红色的颜色,所以在程序执行完第 21 行语句后,当前颜色就 重新设置为深红色了。 程序第 23 行: int thickness = 20; //设置矩形的厚度 程序第 25 行~第 31 行: for (int i = thickness ­ 1; i >= 0; i­­)  {  g.fill3DRect(10 + i, 80 ­ i, 150, 120, true);  g.fill3DRect(210 + i, 60 + i, 150, 120, true);
  • 50. g.fill3DRect(30 ­ i, 230 ­ i, 150, 120, true);  g.fill3DRect(230 ­ i, 210 + i, 150, 120, true);  } 调用一个循环方法来绘制 3D 矩形。 循环的次数由程序第 23 行, 声明的整型变量 thickness  决定。 该数值越大,  矩形的厚度就越厚。 3D 在循环的方法体内,共有四条 fill3DRect 绘图语句, 它们分别绘制了四种不同风格的 3D 矩形。这里我们用程序第 27 行,以及如图 2.22 所示的  3D 矩形示意图为例来说明。
  • 51. 第 1 个矩形(30,60) 最后 1 个矩形(10,80)  height 120  thickness 20 weight 150  图 2.22    3D 矩形的画法示意图 第一次循环,i 的值是 20,所以程序第 27 行,执行的是: g.fill3DRect(30, 60, 150, 120, true); 这样就绘制了一个有凸出感的,左上角坐标为(30,60) ,宽度为 150,高度为 120,并 且颜色为深红色的 3D 矩形。之后,每循环一次,就绘制一个这样的 3D 矩形,只是矩形左上 角的坐标不同而已。最后一次循环,i 的值为 0,这时程序第 27 行,实际执行的是: g.fill3DRect(10, 80, 150, 120, true); 这样就绘制了一个有凸出感的,左上角坐标为(10,80) ,宽度为 150,高度为 120,并 且颜色为深红色的 3D 矩形。这二十个重叠在一起的 3D 矩形,最终就形成了如图 2.21①所示 的图形了。 同理,程序第 28 行、第 29 行和第 30 行分别绘制如图 2.21 中②、③、④所示的图形。 读者可以看到,用这种方法绘制的  3D  矩形,既简单又美观。此方法除了可以绘制  3D  矩形以外,还适用于其他 3D 图形的绘制,如椭圆、圆弧、粗线等。这些 3D 图形的绘制方法 将在以后的代码中向读者阐述。 注意,程序第 27 行~第 30 行,生成不同风格的 3D 矩形,同样也可以生成 3D 的直方图 和条形图。  2.8  绘制椭圆 在 Web 图表的绘制中,椭圆也是最基本的几何图形之一。  2.8.1  绘制普通(空心)椭圆 我们直接调用 Graphics 类中提供的绘制椭圆的方法,就可以方便地绘制椭圆。表 2.12  列出了 Graphics 类中绘制椭圆的方法。 表 2.12  绘制椭圆的 Graphics 方法  Public void drawOval(int x, int y, int width, int height)  用当前颜色绘制一个空心椭圆,其所在的边界矩形的左上角顶点坐标为(x,  y),宽度为 width,高度为 height,椭圆与边 界矩形的各边中心内切,如图 2.23 所示 从图 2.24 可以看出,如果椭圆边界矩形的 width 和 height 的取值大小相同,该边界矩形 就会变成正方形,而该边界矩形内的椭圆也将变成正圆形。这样,就实现了绘制正圆图形的 功能。
  • 52. (x,y)  height width  图 2.23  椭圆及其边界矩形 图 2.24    DrawOvalApplet.java 的运行结果 程序 DrawOvalApplet.java(chap02Fig2.8Fig2.8_01)演示了如何绘制椭圆及正圆图形,运 行结果如 2.24 所示。 程序第  21 行~第  22  行,绘制了一个浅灰色的矩形,左上角顶点坐标为(10,60) ,宽 度为 150,高度为 120。 程序第 23 行~第 24 行,绘制了一个边框为红色的椭圆。我们可以看到,程序第 24 行  drawOval 方法和第 22 行 drawRect 方法,接收的参数相同。该椭圆的外接矩形边界和程序第  22 行语句,所绘制的矩形图形完全重叠。图 2.24 展示了椭圆及其边界矩形的关系。 程序第 27 行~第 30 行: g.setColor(Color.LIGHT_GRAY);  g.drawRect(210, 60, 120, 120);  g.setColor(Color.RED);  g.drawOval(210, 60, 120, 120); 这段代码演示了如何绘制正圆形,与程序第 21 行~第 24 行的代码内容基本相同。同样 是先绘制了一个和欲绘制椭圆的外接边界矩形完全重叠的浅灰色矩形。注意,程序第 28 行,  drawRect 方法的最后两个参数的取值相同,这样就改变了矩形的形状为正方形。同理,程序第  24 行的 drawOval 方法的最后两个参数也相同,所以椭圆的形状也就改变成了正圆形。
  • 53. 2.8.2  绘制实心椭圆 绘制实心椭圆就是用某种颜色向一个空心椭圆中进行填充操作。我们直接调用 Graphics  类中提供的绘制实心椭圆的方法绘制实心椭圆。 2.13 列出了 Graphics 类中绘制实心椭圆的 表 方法。 表 2.13  绘制实心椭圆的 Graphics 方法  Public void fillOval(int x, int y, int width, int height)  用当前颜色绘制一个实心椭圆,其所在的边界矩形的左上角顶点坐标为(x,  y),宽度为 width,高度为 height,椭圆与边 界矩形的各边中心内切,如图 2.23 所示 程序  FillOvalApplet.java(chap02Fig2.8Fig2.8_02)演示了如何绘制椭圆及正圆图形运行 结果如图 2.25 所示。 图 2.25    FillOvalApplet.java 的运行结果 程序第  21 行~第  22  行:绘制了一个浅灰色的矩形,左上角顶点坐标为(10,60) ,宽 度为 150 像素,高度为 120 像素。 程序第 23 行~第 24 行, 绘制了一个红色的实心椭圆。我们可以看到程序第 24 行 fillOval  方法和第 22 行 drawRect 方法,接收的参数相同。该椭圆的外接矩形边界和程序第 22 行语句 所绘制的矩形的图形完全重叠。 程序第 27 行~第 30 行: g.setColor(Color.LIGHT_GRAY);  g.drawRect(210, 60, 120, 120);  g.setColor(Color.RED);  g.drawOval(210, 60, 120, 120); 这段代码演示了如何绘制实心正圆形,与程序第 21 行~第 24 行的代码内容基本相同。 也是先绘制了一个与将绘制的椭圆的外接边界矩形完全重叠的浅灰色矩形。注意,程序第 28  行,drawRect 方法的最后两个参数的取值相同,这样就改变了矩形的形状为正方形。同理, 程序第 24 行的 drawOval 方法的最后两个参数也相同,所以椭圆的形状也就改变成了正圆形。  2.8.3  绘制 3D 椭圆 我们通过绘制一组由不同颜色,并重叠在一起的椭圆所组成的图形,来实现绘制  3D  椭 圆的功能。 程序 Draw3DOvalApplet.java (chap02Fig2.8Fig2.8_03)演示了如何绘制 3D 椭圆,运行结
  • 54. 果如图 2.26 所示。 图 2.26    Draw3DOvalApplet.java 的运行结果 程序第 22 行~第 29 行: for (int i = thickness ­ 1; i >= 0; i­­)  {  g.setColor(new Color(255, i * 10, i));  g.fillOval(10 + i, 80 ­ i, 150, 120);  g.fillOval(210 + i, 60 + i, 150, 120);  g.fillOval(30 ­ i, 230 ­ i, 150, 120);  g.fillOval(230 ­ i, 210 + i, 150, 120);  } 循环次数由程序第 20 行定义的整型变量 thickness 决定。该数值越大,3D 椭圆的厚度就 越厚。在该循环体内,程序第 24 行: g.setColor(new Color(255, i * 10, i)); 每循环一次, 当前的绘图颜色会根据 RGB 的值而重新设定一次。 接着一共是 4 条 fillOval  绘图语句,它们分别绘制了 4 种不同风格的 3D 椭圆。这里我们用程序第 24 行和第 25 行为 例,来说明其绘制过程。 第 1 次循环,i 的值是 20,所以,第 24 行和第 25 行执行以下两条语句: g.setColor(new Color(255, 200, 20));  g.fillOval(30, 60, 150, 120); 第 24 行执行完后,当前颜色就被设置成一种近似于土黄色(RGB 值:255,200,20) 的颜色。接着用当前颜色绘制了一个实心椭圆,该椭圆边界矩形的左上角的顶点坐标在(30,  60),宽度为 150,高度为 120。之后,每循环一次,就会重新设置当前的颜色,再绘制一个 这样的椭圆, 只是椭圆的边界矩形的左上角顶点的坐标不同而已。 最后一次循环, i 的值为 0, 这时,第 24 行和第 25 行语句实际执行的是: g.setColor(new Color(255, 0, 0));  g.fillOval(10, 80, 150, 120);  new Color(255, 0, 0)定义了红色为当前的绘图颜色,这样就绘制了边界矩形左上角的坐标 在(10,80) ,宽度为 150,高度为 120,并且颜色为红色的椭圆。20 个重叠在一起的椭圆, 最终形成了如图 2.26①所示的图形。该 3D 椭圆的底部是土黄色,从椭圆的底部到顶部,颜 色逐步过渡到红色。
  • 55. 同理,程序第 26 行、第 27 行和第 28 行分别绘制如图 2.26 中②、③、④所示的图形。  2.8.4  绘制圆柱体 圆柱体可以看成是由上下两个椭圆,再加上一个矩形所组成的图形,如图 2.27 所示。 椭圆的高  上方椭圆外切矩形顶点  ovalHeight  (ovalX1, ovalY1)  矩形左上角顶点  (rectX, rectY)  矩形的高  rectHeight  下方椭圆外切矩形顶点  椭圆的高  (ovalX2, ovalY2)  ovalHeight  width 图 2.27  圆柱体的绘制方法 矩形左上角的顶点坐标是(rectX, rectY)。上下两个椭圆左上角的顶点坐标分别是(ovalX1,  ovalY1)及(ovalX2, ovalY2)。 矩形的宽度和椭圆边界矩形的宽度相同,都是 width,矩形的高度为 rectHeight,椭圆的 边界矩形高度为 ovalHeight。 矩形及上下椭圆边界矩形的左上角坐标之间存在如下关系: Ø 上面一个椭圆边界矩形的左上角坐标 ovalX1 = rectX, ovalY1 = rectY – ovalHeight / 2 ; Ø 下面一个椭圆边界矩形的左上角坐标 ovalX2 = rectX, ovalY2 = ovalY1 + rectHeight ; 所以,只要知道矩形左上角顶点的坐标、矩形的宽度、矩形的高度,以及椭圆的高度就 可以绘制出一个圆柱体了。  DrawCylinderApplet.java(chap02Fig2.8Fig2.8_04)演示了如何利用上面所讲的方法来绘 制一个圆柱体,本程序中使用了下列几个方法: Ø  calculateOvalCoordinate——利用上面的公式计算上、下椭圆的坐标。 Ø  resetRectCoordinate——重新设置矩形左上角顶点的坐标。 Ø  drawCylinder——绘制圆柱体。 Ø  paint——Java Applet 默认的自动调用的方法之一, 在此方法体内, 再调用 draw Cylinder  方法,进行实际圆柱体的绘制工作。  DrawCylinderApplet.java 的运行结果如图 2.28 所示。
  • 56. 图 2.28    DrawCylinderApplet.java 的运行结果 程序第  10  行~第  15  行,定义并初始化了  rectX、rectY、rectWidth、rectHeight,以及  ovalHeight 这 5 个整型变量。分别定义了矩形的左上角坐标,宽度和高度,以及椭圆的高度。 然后声明了 5 个整型变量,ovalX1、ovalY1、ovalX2、ovalY2、ovalWidth。分别代表上面一 个椭圆的边界矩形左上角坐标(ovalX1,ovalY1) ,下面一个椭圆的边界矩形左上角坐标 (ovalX2,ovalY2)以及椭圆的宽度。然后声明并初始化了两个颜色变量,outlineColor  和  fillColor,outlineColor 代表上面一个椭圆的边框颜色,我们设定其默认值为淡灰色。fillColor  代表矩形和下面一个椭圆的填充颜色,我们设定其默认值为红色。 然后,程序执行第 57 行的 paint 方法,程序第 60 行,调用父类的 paint 方法。运行完程 序第 63 行和第 64 行后,在 Applet 上,用 15 磅的“汉真广标”字体绘制了一条文本“圆柱 体画法” 。 之后,程序执行第 67 行: drawCylinder(g); 然后,程序转向执行第 35 行的 drawCylinder 方法,该方法接收一个 Graphics 类的对象 g。 程序第 38 行: calculateOvalCoordinate(); 程序立即转向执行第 18 行的 calculateOvalCoordinate 方法,该方法只有一个目的,就是 根据程序第 10 行~第 12 行, 定义并初始化的 rectX、  rectY、  rectHeight 以及 ovalHeight  rectWidth、  这 5 个整型变量值, 来计算并将计算结果赋值给程序第 13 行声明的 ovalX1、  ovalY1、  ovalX2、  ovalY2、ovalWidth 这 5 个整型变量。 计算过程在程序第 20 行~第 24 行: ovalWidth = this.rectWidth;  ovalX1 = this.rectX;  ovalY1 = this.rectY ­ ovalHeight / 2;  ovalX2 = this.rectX;  ovalY2 = ovalY1 + rectHeight; 程序返回到 drawCylinder 方法中,接着继续执行。 程序第 41 行~第 44 行: g.setColor(fillColor);  g.fillOval(ovalX2, ovalY2, ovalWidth, ovalHeight);
  • 57. g.setColor(outlineColor);  g.drawOval(ovalX2, ovalY2, ovalWidth, ovalHeight); 先将红色(fillColor)设置为当前的绘图颜色,然后用当前颜色填充圆柱体中下面的一个 椭圆。首先绘制最下方的一个椭圆,因为 Java 默认的绘图模式是覆盖模式,也就是说,如果 绘制的图形相互重叠,那么默认图形重叠部分就仅仅显示为最后绘制的图形。这里,椭圆在 调用 calculateOvalCoordinate 方法后,得到的绘制参数为: ovalWidth = 100;  ovalX1 = 10;  ovalY1 = 120 – 40 / 2 = 100;  ovalX2 = 10;  ovalY2 = 100 + 250 = 350; 填充完椭圆后,我们又绘制了一个淡灰色的空心椭圆。注意,这里绘制空心椭圆的参数 和前面绘制实心椭圆的参数完全相同,程序执行完这 4 条语句,就绘制了一个边缘颜色为淡 灰色,其余颜色为红色的实心椭圆。 程序第 47 行~第 48 行: g.setColor(fillColor);  g.fillRect(rectX, rectY, rectWidth, rectHeight); 用红色绘制了一个实心矩形。 程序第 51 行~第 54 行,绘制矩形上方的一个椭圆: g.setColor(g.getColor().darker());  g.fillOval(ovalX1, ovalY1, ovalWidth, ovalHeight);  g.setColor(outlineColor);  g.drawOval(ovalX1, ovalY1, ovalWidth, ovalHeight); 与程序第 41 行~第 44 行,绘制下方椭圆的过程几乎一样,不同的是,为了更好地显示 圆柱体的外观,程序第 51 行,调用了 Graphics 类的 getColor 方法,得到当前的绘图颜色的 对象——红色。然后,调用 Color 类的 darker 方法,生成一个比当前颜色更深(暗)一些的 颜色对象。程序执行完第 51 行后,当前的绘制颜色就转变成了深红色。程序执行完这 4 条语 句,就绘制了一个边缘颜色为淡灰色的深红色实心椭圆。 之后,程序返回到  paint  方法中。这时,一个由上、下两个椭圆及一个矩形组成的圆柱 体就绘制完成了,如图 2.28①所示。 程序继续运行到第 69 行: rectHeight = 350; 对变量  rectHeight  进行直接赋值,以达到重新设置矩形高度的目的。现在,矩形的高度 就被设置为 350 了。 程序第 70 行: resetRectCoordinate(250, 50); 调用 resetRectCoordinate 方法,重新设定矩形左上角顶点坐标。这里,程序转向 到第 28 行的 resetRectCoordinate 方法。 程序第 30 行~第 31 行: this.rectX = rectX;  this.rectY = rectY; 将接收到的两个整型参数分别赋值给程序第 10 行所声明的变量 rectX 和 rectY。这时,左上 角顶点坐标就更新为 250 和 50 了。之后,程序返回到 paint 方法中,继续执行第 72 行: fillColor = Color.BLUE;
  • 58. 重新设定绘图时的填充颜色为蓝色。 程序第 70 行: drawCylinder(g); 再次调用 drawCylinder 方法,程序再次转到第 35 行的 drawCylinder 方法。同理,程序 立即转向执行第 18 行的 calculateOvalCoordinate 方法,根据新的矩形参数来重新计算并将计 算结果再次赋值给程序第 13 行,声明的 ovalX1、ovalY1、ovalX2、ovalY2 和 ovalWidth 这 5  个整型变量。这次,椭圆在调用 calculateOvalCoordinate 方法后,得到的绘制参数为: ovalWidth = 100(没有改变);  ovalX1 = 250;  ovalY1 = 50 – 40 / 2 = 30;  ovalX2 = 250;  ovalY2 = 30 + 350 = 380; 然后,根据新的绘制参数,再次以如下的顺序绘制图形: (1)下方实心椭圆,颜色为蓝色; (2)中间的实心矩形,颜色为蓝色; (3)上方的实心椭圆,颜色为深蓝色,并且该椭圆的边缘为浅灰色。 圆柱体绘制完成后的最终效果如图  2.28②所示。之后,程序再次返回到  paint  方法,并 结束整个程序的运行。  2.9  绘制圆弧 圆弧就是一个空心椭圆的一部分,如图 2.29 所示。 左上角顶点(x,y)  外切矩形高度 height  arcAngle  StartAngle  圆弧起始角度 外切矩形宽度 width  图 2.29  圆弧的绘制方法
  • 59. 2.9.1  绘制普通(空心)圆弧 我们直接调用  Graphics  类中提供的绘制圆弧的方法,来绘制空心圆弧。表  2.14  列出了  Graphics 类中绘制空心圆弧的方法。 表 2.14  绘制圆弧的 Graphics 方法  Public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)  用当前颜色绘制一段空心圆弧,该圆弧所在椭圆的外切矩形的左上角坐标为(x,  y),宽度为  width,高度为  heigh,以角 度 startAngle 所在的地方为起点,绘制一段角度为 arcAngle 的空心圆弧,如图 2.29 所示 一段圆弧是椭圆或圆的一部分,圆弧的角度通过度数来衡量。一段圆弧在两个角度之间 绘制,一个为起始角度,另一个为圆弧角度,起始角度为圆弧开始的角度,圆弧角度为圆弧 最后的度数。图 2.30 展示了两段圆弧,左边的坐标系显示圆弧从 0°画到大约 110°,以逆 时针方向画过的弧用正角度衡量。右边的坐标系显示圆弧从  0°画到大约-110°,以顺时针 方向画过的圆弧用负角度衡量。如图 2.30 所示的正、负角度圆弧的绘制方向。  drawArc 方法接收 6 个参数。前两个参数指明了包含圆弧的外切矩形的左上角坐标,第  3 个和第 4 个参数定义了外切矩形的宽和高,第 5 个参数定义了圆弧的开始角度,第 6 个参 数定义了圆弧的角度,并用当前颜色绘制圆弧。  DrawArcsApplet.java (chap02Fig2.9Fig2.9_01)演示了圆弧的画法,运行结果如图 2.31 所 示。 正角度  负角度  90°  90°  180°  0°  180°  0°  270°  270° 图 2.30  正、负角度圆弧的绘制方向 图 2.31  几种不同圆弧的绘制方法 程序第 20 行~第 24 行,绘制了两个浅灰色的矩形和两个浅灰色的正方形。 程序第 26 行,重新设置当前的绘图颜色为蓝色。 程序第 29 行: g.drawArc(10, 60, 150, 120, 0, 360); 绘制了一个圆弧。该语句接收 6 个参数中,前 4 个参数和程序第 21 行的 drawRect 方法 的参数完全相同,后两个参数指定圆弧的起始角度在  0°,圆弧的角度为  360°,正好形成 一个首尾封闭的圆弧,最后就形成了一个椭圆,如图 2.31①所示。 程序第 32 行:
  • 60. g.drawArc(210, 60, 120, 120, 90, 270); 这里圆弧所在椭圆的外切矩形的宽度和高度相同,所以变成了一个正方形。最后两个参 数指定圆弧的起始角度在 90°,并按逆时针方向绘制一个角度为 270°的圆弧,所以圆弧的 起始角度在 90°,终点在 90°+270°=360°,如图 2.31②所示。 程序第 34 行: g.setColor(Color.RED); 重新设置当前的绘图颜色为红色。 程序第 37 行: g.drawArc(10, 210, 150, 120, 30, ­ 150); 绘制了一个圆弧。该语句接收 6 个参数中,前 4 个参数和程序第 23 行的 drawRect 方法 的参数完全相同,后两个参数指定圆弧的起始角度在 30°,并按顺时针方向绘制一个角度为  150°的圆弧,如图 2.31③所示。 程序第 40 行: g.drawArc(210, 210, 120, 120,  ­ 90,  ­ 270); 同样,这里圆弧所在椭圆的外切矩形的宽度和高度相同,所以变成了一个正方形。最后 两个参数指定圆弧的起始角度在-90°(也就是 270°) 并按顺时针方向绘制一个角度为 270  , °的圆弧,所以圆弧的起始角度在 270°,终点在 270°-270°=0°,如图 2.31④所示。  2.9.2  绘制实心圆弧  Graphics 类中提供了绘制圆弧的 fillArc 方法,可以非常方便地绘制实心圆弧。表 2.15 列出 了 Graphics 类中绘制实心圆弧的方法。 表 2.15  绘制圆弧的 Graphics 方法  Public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)  用当前颜色填充一段圆弧,该实心圆弧所在椭圆的外切矩形的左上角坐标为(x,  y),宽度为 width 和高度为 heigh,以角 度 startAngle 所在的地方为起点,绘制一段角度为 arcAngle 的实心圆弧  fillArc 方法同样接收 6 个参数。前两个参数指明了包含圆弧的外切矩形的左上角坐标, 第 3 个和第 4 个参数定义了外切矩形的宽和高,第 5 个参数定义了圆弧的开始角度,第 6 个 参数定义了圆弧的角度,并用当前颜色填充圆弧。  FillArcsApplet.java(chap02Fig2.9Fig2.9_02)演示了实心圆弧的画法,程序运行结果如图  2.32 所示。  FillArcsApplet.java 和前面介绍的 FillArcsApplet.java 程序几乎完全相同, 只是将 drawArc  方法替换成 fillArc 方法。程序第 29 行、第 32 行、第 37 行和第 40 行分别调用 fillArc 方法, 绘制了不同形式的实心圆弧。程序的运行结果如图 2.32 所示。
  • 61. 图 2.32  几种实心圆弧的绘制方法  2.9.3  绘制 3D 圆弧  Java.awt 包中的 Graphics 类中,并没有提供绘制 3D 圆弧的方法,我们可以利用绘制多 个重叠在一起的实心圆弧来实现这一功能。Draw3DArcApplet.java(chap02Fig2.9 Fig2.9 _03)  演示了如何绘制 3D 圆弧,运行结果如图 2.33 所示。
  • 62. 图 2.33    3D 圆弧的绘制方法 程序第 19 行: int thickness = 20; //设置 3D 圆弧的厚度 声明了一个整型变量 thickness,并初始化其值为 20。thickness 代表 3D 圆弧的厚度。 程序第 21 行~第 36 行,是一个循环,该循环的次数由前面设定的变量 thickness 决定, 本例共循环 20 次。 程序第 23 行~第 30 行: if (i == 0)  {  g.setColor(new Color(255, 200, 20));  }  else  {  g.setColor(new Color(255, i * 10, i));  } 每一次循环,就重新设定当前实心圆弧的填充颜色。注意,本例设定最后绘制的实心圆 弧的绘制颜色为 new Color  (255, 200, 20) 其余圆弧的绘制颜色则根据 i 值的变化而变化 , (程 序第 29 行) 。 程序第 33 行~第 35 行,绘制 3D 圆弧的核心语句。 程序第 33 行: g.fillArc(40, 60 + i, 250, 160, 10, 70); 按从下到上的顺序 (因为 i 的值是由大到小递减的)填充圆弧。圆弧的起始角度为 10°, 圆弧的弧度为 70°。绘制结果如图 2.33①所示。同理,程序第 34 行和第 35 行,分别绘制了 两个如图 2.33②及 2.33③所示的不同的 3D 圆弧。  2.10  绘制多边形 我们绘制的很多图形实际上不全都是规则的图形,在这种情况下,Java 绘制多边形和折 线的功能就可以大显身手了。此外,绘制某些 3D 图形时,我们也可以借助多边形来实现。
  • 63. Graphics 类中提供了绘制多边形的 drawPolygon 方法,可以方便地绘制多边形。  2.10.1  绘制空心多边形 表 2.16 列出了 Graphics 类中绘制空心多边形的方法。 表 2.16  绘制空心多边形的 Graphics 方法  public abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)  使用当前颜色绘制  xPoints、yPoints 数组指定的多边形,每个顶点的  x 坐标由 xPoints 数组指定,每个顶点的  y 坐标由  yPoints 数组指定  drawPolygon 方法用来绘制一个多边形,多边形(polygon)是多条边相互连接的封闭图 形。drawPolygon 方法接收 3 个参数:一个包含 x 坐标的整型数组,一个包含 y 坐标的整型 数组,最后是多边形顶点的数目。注意,在多边形中,最后一个顶点会自动连接第一个顶点, 也就是说,最后一个顶点的坐标和第一个顶点的坐标不必相同。  DrawPolygon.java(chap02Fig2.10Fig2.10_01)演示了多边形的画法,运行结果如图 2.34 所 示。 图 2.34    DrawPolygon.java 的运行结果 程序绘制两个多边形,第 1 个多边形是五角星。程序第 21 行~第 36 行: int[] xValues = { 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 };  int[] yValues = { 70, 106, 106, 124, 166, 142, 166, 124, 106, 106 }; 声明了两个数组 xValues 和 yValues,分别表示将绘制的五角星每一个顶点的横坐标及纵 坐标。 程序第 24 行: Polygon fiveStar = new Polygon(xValues, yValues, xValues.length); 调用了 java.lang 包中的 Polygon 的构造器,创建了一个 Polygon 类的实例 fiveStar。该构 造器接收 3 个参数,前面两个参数是程序第 22 行和第 23 行定义好的两个整型数组,代表多 边形上所有顶点的 x 坐标和 y 坐标的集合,最后一个参数是 1 个整型变量,表示该多边形有 多少个顶点。因为五角星共有 10 个顶点,所以程序第 24 行也可写成如下形成: Polygon fiveStar = new Polygon(xValues, yValues, 10); 而数组  xValues  和  yValues  正好包含了  10  个元素,所以我们直接调用数组的静态变量  length,用 xValues.length 的方式,来获得数组中所有元素的个数。 程序第 25 行:
  • 64. g.drawPolygon(fiveStar); 将刚刚创建的 Polygon 对象 fiveStar,传递给 Graphics 类的 drawPolygon 方法。然后,程 序就在这里执行绘制多边形的工作。 程序第 28 行~第 36 行,用另外一种方式绘制了一个多边形。 程序第 28 行: Polygon sharp = new Polygon(); 调用 Polygon 类默认的构造器,创建了一个 Polygon 类的实例 sharp,默认的构造器不接 收任何参数。由 Polygon 类默认的构造器,创建的 Polygon 类的实例是不包括任何顶点的, 需要手工添加该多边形的各个顶点的坐标。 程序第 29 行~第 33 行: sharp.addPoint(185, 45);  sharp.addPoint(195, 110);  sharp.addPoint(290, 160);  sharp.addPoint(220, 180);  sharp.addPoint(150, 140); 调用 Polygon 类的 addPoint 方法,向对象 sharp 中添加顶点,这里共添加了 5 个顶点。 程序第 35 行,重新设定当前的绘图颜色为红色。 程序第 36 行: g.drawPolygon(sharp); 将创建好的 Polygon 实例 sharp 再次传递给 Graphics 类的 drawPolygon 方法,绘制多边形。  2.10.2  绘制实心多边形 表 2.17 列出了 Graphics 类中绘制实心多边形的方法。 表 2.17  绘制实心多边形的 Graphics 方法  public abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)  使用当前颜色绘制并填充由 xPoints、yPoints 数组指定的多边形,每个顶点的 x 坐标由 xPoints 数组指定,每个顶点的 y  坐标由 yPoints 数组指定  fillPolygon  方 法 , 用 当 前 的绘 图 颜色 来 填 充一 个实 心 多 边 形。 fillPolygon  方 法 和  drawPolygon 方法一样, 都接收 3 个参数——一个包含 x 坐标的整型数组, 一个包含 y 坐标的 整型数组,最后是表示该多边形顶点的数目。  FillPolygon.java  (chap02Fig2.10Fig2.10_02)演示了实心多边形的画法,运行结果如图  2.35 所示。
  • 65. 图 2.35    FillPolygon.java 的运行结果  FillPolygon.java 和 DrawPolygon.java 的源程序几乎完全相同。只是在 FillPolygon.java 程 序中,将原来在 DrawPolygon.java 源程序中的 drawPolygon 方法全部替换成了 fillPolygon 方法。 程序其余部分代码没有变化。  2.10.3  绘制折线 使用 drawPolyline 的方法绘制一条折线,折线是一条由多条线段连接起来的首尾不相连 的开放图形。如果折线的第一个顶点和最后一个顶点的坐标相同,那么该折线也就变成了一 个封闭的图形——多边形。 表 2.18 列出了 Graphics 类中绘制折线的方法。 表 2.18  绘制折线的 Graphics 方法  public abstract void drawPolyline (int[] xPoints, int[] yPoints, int nPoints)  使用当前颜色绘制一系列相互连接的线段,线段端点的坐标相应由数组  xPoints  和  yPoints  指定,线段的端点个数由  nPoints 指定  drawPolyline 方法和 drawPolygon 方法一样, 都接收 3 个参数——第 1 个参数包含 x 坐标 的整型数组,第 2 个参数包含 y 坐标的整型数组,最后 1 个参数指明该折线总共有多少个端 点。  DrawPolyline.java(chap02Fig2.10Fig2.10_03)演示了实心多边形的画法,运行结果如图  2.36 所示。 图 2.36    DrawPolylin.java 的运行结果  2.10.4  绘制三角形(箭头) 本节将以绘制 4 种不同风格的等腰三角形(箭头)为例。 假设等腰三角形 ABC 的底边 AB 的边长为 L,底角的度数为a,则等腰三角形 ABC 的 三个顶点的坐标 A、B 和 C 分别可以用以下公式计算,如图 2.37 所示。  1.等腰三角形①:  Ax =x-L/2,Ay =y  Bx =x+L/2,By =y  Cx =x,Cy =y-tga*L/2
  • 66. C  A  中点坐标 a ① (x,y) 底边 L  中点坐标 (x,y) C  a ② a a B  A  B  底边 L  底边 L  中点坐标 A  a a B  (x,y) A  ④ a 底边 L  中点坐标 ③ (x,y) C  a C  B 图 2.37    DrawPolylin.java 的运行结果  2.等腰三角形②:  Ax =x,Ay =y-L/2  Bx =x,By =y +L/2  Cx =x+tga*L/2,Cy =y  3.等腰三角形③:  Ax =x-L/2,Ay =y  Bx =x+L/2,By =y  Cx =x,Cy =y+tga*L/2  4.等腰三角形④:  Ax =x,Ay =y-L/2  Bx =x,By =y+L/2  Cx =x-tga*L/2,Cy =y  DrawTriangleApplet.java(chap02Fig2.10Fig2.10_04)演示了如何利用上述计算来绘制三 角形,运行结果如图 2.38 所示。 源程序共包含了 5 种方法,如下所示: Ø  drawTrigangle  Ø  setBaseLine  Ø  setFillColor  Ø  setOutlineColor  Ø  paint 
  • 67. 图 2.38    DrawPolylin.java 的运行结果  5 种方法分别执行了不同的任务。paint 方法是 Java Applet 自动执行的方法之一,我们在  paint  方法内再调用其他的方法以完成绘制不同大小、不同方向、不同风格的三角形。  drawTrigangle 方法,负责进行计算三角形 3 个顶点坐标,并根据用户提供的参数来决定三角 形的填充颜色、三角形轮廓的绘制颜色,以及三角形的方向等信息来进行图像绘制。  setBaseLine 负责设置等腰三角形底边的长度。  setFillColor 负责设置等腰三角形的填充颜色 (即 绘制实心三角形的颜色) 。setOutlineColor 负责设置绘制等腰三角形轮廓的颜色(即绘制空心 三角形的颜色) 。 程序第 9 行~第 10 行: int[] TriangleXValues = new int[3];  int[] TriangleYValues = new int[3]; 声明并初始化两个整型数组。TriangleXValues 代表三角形 3 个顶点的横坐标,TriangleY  Values 代表三角形 3 个顶点的纵坐标。 程序第 11 行: int alpha = 60; 声明了一个值为  60  的整型变量  alpha。alpha  表示等腰三角形底角。这里设置默认底角  alpha 的度数为 60°。 程序第 12 行: int baseLine = 80; 声明了一个值为 80 的整型变量 baseLine,baseLine 表示等腰三角形底边。这里设置默认 底边 baseLine 的长度为 80。 程序第 15 行: final int UP = 1, RIGHT = 2, DOWN = 3, LEFT = 4; 声明了 4 个整型变量 UP、RIGHT、DOWN、LEFT,代表三角形的方向。分别如图 2.38  所示的向上、向右、向下和向左的三角形。这里我们使用了 final 关键字,表示这些变量的值 在声明后,不能再被修改(相当于常量) 。 程序第 18 行: final int OUTLINE = 1, FILL = 2, MIXED = 3; 这里声明 3 个整型变量 OUTLINE、FILL 和 MIXED,分别代表三角形的绘制风格,分 别表示为: Ø  OUTLINE:绘制三角形轮廓(即绘制空心三角形) ; Ø  FILL:绘制实心三角形;
  • 68. Ø  MIXED:绘制一个带轮廓的实心三角形(即先绘制一个实心三角形,然后在其上再 绘制一个空心三角形) 。 程序第 20 行~第 21 行: Color fillColor = Color.BLACK; // 默认的填充颜色 Color outlineColor = Color.BLACK; //默认的轮廓颜色 声明了两个 Color 类的对象实例,fillColor 和 outlineColor 分别表示绘制实心三角形和三 角形轮廓的颜色。两者的默认绘制颜色被设置为黑色。 当程序运行时,完成上述变量的创建工作后,首先执行程序第 108 行默认的 paint 方法。 在该方法体内,首先进行图形标题的绘制工作,即程序第 115 行,绘制“三角形的画法”这 个文本。 程序第 117 行: setFillColor(Color.RED); 设置填充实心三角形的颜色为红色,注意,这里没有设置绘制三角形轮廓的颜色,所有  outlineColor 对象仍然保持黑色不变。 程序第 120 行: drawTrigangle(50, 120, UP, OUTLINE, g); 调用 drawTrigangle 方法,并传递 5 个参数。前面 4 个参数是整型参数,最后 1 个参数是 由 applet 容器创建的 Graphics 对象 g。前面 4 个参数的具体含义是: Ø  50,100:表示将要绘制的三角形底边中点的坐标为(50,100) ; Ø  UP:表示该三角形的形状是一个向上的三角形; Ø  OUTLINE:表示仅绘制该三角形的轮廓(即空心三角形) 。 然后,程序转向到第 23 行,并执行 drawTrigangle 方法。drawTrigangle 方法,接收刚刚 由 paint 方法调用并传递过来的 5 个参数。 在 drawTrigangle 方法体内,首先运行程序第 27 行: int offset = (int)(baseLine / 2 * Math.tan(alpha * Math.PI / 180)); 定义了一个局部的整型变量 offset。offset 是一个偏移量,它表示三角形顶点 C 到底边中 点的距离。套用图 2.37 所示的计算方法,可知该偏移量等于底角a乘以底边长的一半,即: offset = tgα* L/2 因为,Java.lang.Math 包中的 tan 方法接收 double 类型的弧度值,所以这里使用: alpha * Math.PI / 180 将角度值转化成弧度值。执行: Math.tan(alpha * 180 / Math.PI)) 得到的结果是一个 double 类型的值,再用(int)的方法,将其从双精度型数值强制转换 为整型数值,并将转换后的结果赋值给局部整型变量 offset。 程序第 30 行,根据用户传递过来的第 3 个整型参数,进入一个 switch 多重选择语句: switch (triangleDirection) 根据用户传递过来的 triangleDirection 值,进行下一步操作,即计算三角形 3 个顶点的坐 标。这里程序传入 triangleDirection 的值为 UP。 根据程序第 15 行的定义,UP 的值为 1,所以,程序会执行第 32 行~第 39 行:
  • 69. case 1:  TriangleXValues[0] = x ­ baseLine / 2;  TriangleYValues[0] = y;  TriangleXValues[1] = x + baseLine / 2;  TriangleYValues[1] = y;  TriangleXValues[2] = x;  TriangleYValues[2] = y ­ offset;  break; 根据前面介绍的计算公式,计算出形状向上的三角形的 3 个顶点的坐标,并将这 3 个坐 标分别赋值给数组 TriangleXValues 和  TriangleYValues。计算完 3 个顶点的坐标之后,执行 程序第 39 行的 break 语句。退出当前的 switch 语句后,程序进入第 70 行,另外一个 switch  语句: switch (triangleStryle) 根据用户传递的第 4 个参数 triangleStyle 进行三角形绘制风格的选择。当前用户传递的 是“OUTLINE”,根据程序第 18 行的定义,OUTLINE 的值为 1。所以,程序执行第 72~第  75 行: case 1:  g.setColor(outlineColor);  g.drawPolygon(TriangleXValues, TriangleYValues, 3);  break; 程序第  73  行,先设置当前的绘制颜色为  Color  类的对象  outlineColor  所指定的颜色。  outlineColor 在程序第 21 行声明后并没有改变,仍然是黑色。 程序第 74 行: g.drawPolygon(TriangleXValues, TriangleYValues, 3); 调用 Graphics 类的 drawPloyon 方法,绘制一个空心多边形。执行完程序第 74 行语句后, 程序在 Applet 上绘制出一个形状为向上的空心三角形,颜色为黑色,并且该三角形底边的长 度为默认的 80 磅。实际运行结果如图 2.38①所示。 程序第 75 行是一个 break 语句。在这里,程序跳出 switch 语句,然后,再返回到先前的  paint 方法里。程序运行第 123 行: drawTrigangle(100, 120, RIGHT, FILL, g); 再次调用并转向至程序第 23 行,执行 drawTrigangle 方法。同理,drawTrigangle 方法先 计算三角形顶点 C 距离底边中心的偏离量 offset。 因为 RIGHT 的值为 2(见程序第 15 行) ,所以。程序执行第 30 行的 switch 语句中的第  41 行~第 48 行: case 2:  TriangleXValues[0] = x;  TriangleYValues[0] = y ­ baseLine / 2;  TriangleXValues[1] = x;  TriangleYValues[1] = y + baseLine / 2;  TriangleXValues[2] = x + offset;  TriangleYValues[2] = y;  break; 对数组 TriangleXValues 和 TriangleYValues 再次进行赋值后,又进入程序第 70 行,另外 一个 switch 语句。 因为整型参数 Fill 的值为 2,所以,执行该 switch 语句中的第 77 行~第 80 行: case 2:
  • 70. g.setColor(fillColor);  g.fillPolygon(TriangleXValues, TriangleYValues, 3);  break; 程序第 78 行,重新设置当前的绘图颜色。因为 fillColor 已经在程序第 117 行,被重新设 置成了红色,当前的绘制颜色就已经变成了红色。 程序第 79 行, 调用 Graphics 类的 fillPolygon  方法,再次绘制三角形,这次绘制的三角形是一个形状向右、颜色为红色并且底边长度为 80  磅的实心三角形,实际运行效果如图 2.38②所示。 程序再次返回到 paint 方法,继续执行下面的语句,程序第 125 行: setBaseLine(40); 调用 setBaseLine 方法,程序转向到第 92 行~第 95 行: public void setBaseLine(int baseLine)  {  this.baseLine = baseLine;  }  setBaseLine 方法很简单,只是将用户在 setBaseLine 方法中传递整型参数 baseLine 的值 重新赋值给类的数据成员  baseLine。注意,这里用  this  这个关键字来区分用户提交的参数  baseLine 和类的数据成员 baseLine。这时,类的数据成员变量 baseLine 的值就改变为 40 了。 接着程序返回到 paint 方法中,继续执行下面的语句。 程序第 126 行: setOutlineColor(Color.BLUE); 调用 setOutlineColor 方法,程序会转向到第 102 行~105 行: public void setOutlineColor(Color outlineColor)  {  this.outlineColor = outlineColor;  } 同样地,用 this 关键字将用户传递的 outlineColor 对象赋值给系统变量 outlineColor。此 时,系统 outlineColor 变量就被设置为 Color.BLUE。然后,程序再返回到 paint 方法,执行第  127 行: setFillColor(Color.GREEN); 调用 setOutlineColor 方法,程序转向到第 97 行~第 100 行: public void setFillColor(Color fillColor)  {  this.fillColor = fillColor;  } 同样地,用 this 关键字将用户传递的 fillColor 对象赋值给系统变量 fillColor。这时,系 统变量 fillColor 就被设置成为 Color.GREEN。 然后,程序再返回到 paint 方法,执行第 130 行: drawTrigangle(200, 120, DOWN, MIXED, g); 再次调用 drawTrigangle 方法,重复前面我们所讲的步骤,运行完程序第 130 行,程序会 在 Applet 上绘制一个颜色为绿色、向下的、三角形的轮廓颜色为蓝色、底边长度为 40 磅的 实心三角形。实际运行效果如图 2.38③所示。 同理,程序第 132 行~第 136 行: setBaseLine(60);
  • 71. setFillColor(Color.ORANGE);  // 绘制三角形 4  drawTrigangle(300, 120, LEFT, FILL, g); 根据前面所介绍的绘制过程,我们可以得到一个底边长度为  60  磅、颜色为橙色、方向 向左的实心三角形,如图 2.38④所示。  2.10.5  绘制平行四边形及立方体 平行四边形是创建图形三维效果的一个有效途径, Web 图表的设计中起着非常重要的 在 作用。比如说,一个立方体(如 2.7.2.节“利用绘制多个实心矩形来生成 3D 矩形”所绘制的  3D 矩形)由一个正面的矩形,顶部一个平行四边形以及右边侧面的一个平行四边形组成,如 图 2.39 所示。 图 2.40 是立方体对象 Cube 的结构视图。我们把立方体对象 Cube 分解成 3 部分,如图  2.40 所示。 P5  P3  顶部平行四边形 pTop  a  pRight  P6  P2  侧面平行四边形 高(height)  rFront  P4 基准点 正面矩形  (baseXPts,  厚度  baseYPts) P1  thickness  P0 宽(width)  图 2.39  立方体与平行四边形之间的关系 图 2.40  立方体对象 Cube 的结构视图 我们假设以立方体左下角的顶点为基准点 P0,P0 的坐标为(baseXPts,baseYPts) ,立方 体高度为 height,宽度为 weight,厚度为 thickness,顶部平行四边形 pTop 斜边与水平边的夹 角为a。该立方体各个顶点与基准点 P0 的关系就可以用下面的数学公式来表示。  1.正面矩形 rFront 的 4 个端点分别是:  P1x=baseXPts+width  P1y=baseYPts  P2x=P1x  P2y=baseYPts-height  P6x=baseXPts  P6y=P2y  正面矩形 rFront 可以使用前面所学的 Graphics 类的相关方法完成:  g.drawRect(P6x,P6y,width,height)或者  g.fillRect(P6x,P6y,width,height)或者  g.fill3DRect(P6x,P6y,width,height,true)或者  g.fill3DRect(P6x,P6y,width,height,false)  2.顶部平行四边形 pTop  4 个端点由 P6、P2、P3 和 P5 构成,P2 和 P6 已经计算过了,只 需要计算 P3 和 P5,分别是:
  • 72. P3x=P2x+cosa*thickness  P3y=P2y-sina*thickness  P5x=P6x+cosa*thickness  P5y=P3y  通过前面绘制多边形的方法来绘制这个平行四边形。将 4 个端点 P2、P3、P5 和 P6 的坐标 值分别放入两个整型数组 pTopX 和 pTopY,然后,使用以下方法来绘制:  g. drawPolygon(pTopX,pTopY,4)或者  g. filldrawPolygon(pTopX,pTopY,4)  3.右侧面平行四边形 pRight 的 4 个端点由 P1、P2、P3 和 P4 构成,P1、P2 和 P3 已经计算 过了,这里只需计算 P4  :  P4x=P3x  P4y=P3y+height  同理,将 pRight 的 4 个端点 P1、P2、P3 和 P4 的坐标值分别放入两个整型数组 pRightX  和 pRightY,然后,使用以下方法来绘制:  g. drawPolygon(pRightX,pRightY,4)或者  g. filldrawPolygon(pRightX,pRightY,4)  我们可以按照下面的步骤来绘制这个立方体: (1)先计算出各个端点的坐标; (2)绘制顶部的平行四边形 pTop; (3)绘制右侧面的平行四边形 pRight; (4)绘制正面的矩形 rFront。 源程序 DrawCubeApplet.java(chap02Fig2.10Fig2.10_05)演示了如何绘制一个立方体,运 行结果如图 2.41 所示。该程序采用了同上一节讲述的 DrawTriangleApplet.java 相类似的结构 来完成绘制工作。其中主要的方法、方法名及完成的相应功能如下: 图 2.41    DrawCubeApplet.java 的运行结果 Ø setOutlineColor:设置整个立方体的轮廓颜色。 Ø setFrontFillColor:设置正面实心矩形的填充颜色。 Ø setTopFillColor:设置顶部实心平行四边形的填充颜色。 Ø setRightFillColor:设置右侧部实心平行四边形的填充颜色。 Ø setBasePoint:设置基准点的坐标。
  • 73. Ø setWidth:设置立方体的宽度。 Ø setHeight:设置立方体的高度。 Ø setThickness:设置立方体的厚度。 Ø setAngle:设置平行四边形的夹角a; Ø calculateOffset:计算 cosa* thickness 和 sina*thickness 的值。 Ø buildRFront:计算绘制正面矩形所需的数据。 Ø buildPTop:计算绘制顶部平行四边形所需的数据。 Ø buildPRight:计算绘制右侧部平行四边性所需的数据。 Ø drawFront:绘制正面矩形。 Ø drawTop:绘制顶部平行四边形。 Ø drawRight:绘制右侧部平行四边形。 Ø DrawCube:通过调用上面的方法来绘制完整的平行四边形。 程序第 9 行: int baseXPts = 10, baseYPts = 200; // 立方体基准点坐标 声明了两个整型变量 baseXPts 和 baseYPts,表示立方体左下角那一点的坐标值,就是图  2.40 中所示的点 P0。我们默认设置该点的坐标为(10, 200) 。 程序第 10 行~第 12 行: int width = 100; // 立方体的宽度 int height = 120; // 立方体的高度 int thickness = 30; // 立方体的厚度 声明 3 个整型变量 width、height 和 thickness,分别表示立方体的宽度、高度和厚度。 程序第 14 行~第 17 行: int alpha = 45; // 平行四边形的夹角 int offsetX = 0; // 偏移量 X  int offsetY = 0; // 偏移量 Y  final int PARALLELOGRAM_POINT_NUMBER = 4; // 平行四边形的端点数目 程序第 14 行,声明了整型变量 alpha,将图 2.40 中立方体顶部平行四边形斜边和水平边 的夹角a,设置为 45°。程序第 15 行和第 16 行,声明变量 offsetX,表示图 2.40 中点 P5 和 点 P6 之间水平方向上的距离,offsetY 表示点 P5 和 P6 之间在垂直方向上的距离。同理,点 P3  和  P2  之 间 也 存 在 着 这 种 关 系 。 程 序 第  17  行 , 使 用 了 一 个  final  修 饰 的 整 型 变 量  PARALLELOGRAM_POINT_NUMBER,表示要绘制的多边形的端点数目。因为平行四边形 总共有 4 个端点,所以,这里我们将其值设置为 4。 程序第 19 行~第 27 行: int[] pTopX = new int[PARALLELOGRAM_POINT_NUMBER];  // 顶部平行四边形端点的 Y 坐标数组 int[] pTopY = new int[PARALLELOGRAM_POINT_NUMBER];  // 右侧部平行四边形端点的 X 坐标数组 int[] pRightX = new int[PARALLELOGRAM_POINT_NUMBER];  // 右侧部平行四边形端点的 Y 坐标数组 int[] pRightY = new int[PARALLELOGRAM_POINT_NUMBER]; 程序第 19 行~第 21 行,用整型数组 pTopX 和 pTopY 分别保存顶部平行四边形 4 个端 点的横坐标和纵坐标。程序第 25 行~第 27 行,用整型数组 pRightX 和 pRightY 分别保存右 侧部平行四边形 4 个端点的横坐标和纵坐标。 程序第 33 行:
  • 74. final int OUTLINE = 1, FILL = 2, MIXED = 3; 声明 3 个整型变量 OUTLINE、FILL 和 MIXED,分别代表立方体的绘制风格: Ø  OUTLINE:绘制立方体轮廓(即绘制空心立方体) ; Ø  FILL:绘制实心立方体; Ø  MIXED:绘制一个带轮廓的实心立方体(即先绘制一个实心立方体,然后在其上再 绘制一个空心的立方体) 。 程序第 35 行~第 37 行: Color rFrontFillColor = new Color(178, 0, 0); // 默认的填充颜色 Color pTopFillColor = Color.RED; // 默认的填充颜色 Color pRightFillColor = new Color(124, 0, 0); // 默认的填充颜色 声明并初始化了 3 个 Color 类的对象。分别表示立方体正面的矩形、顶部平行四边形及 右侧部平行四边形默认的填充颜色。RGB 为(178,0,0)和(124,0,0)表示两种红色。 它们都比 Color.RED 所表示的红色要深一些,红色立方体的正面和右侧面如图 2.41①所示。 提示:本书提到的“填充”,表示用当前颜色绘制一个实心的图形对象,而“描绘”表 示用当前颜色绘制一个空心的图形对象(即绘制图形的轮廓)。 程序第 38 行: Color outlineColor = Color.LIGHT_GRAY; //默认的立方体轮廓颜色 设置默认描绘立方体的颜色为淡灰色。 程序首先对上述变量进行初始化,然后,直接调用程序第  190  行的  paint  方法,再回到  paint 的方法体中。 绘制完标题后,程序第 200 行: DrawCube(FILL, g); 调用 DrawCube 方法来绘制一个立方体。之后,程序立即转到第 140 行,执行 DrawCube  方法。DrawCube 方法需要两个参数,第 1 个是整型参数,表示要绘制的平行四边形的风格, 该风格在程序第 33 行已经定义了,FILL 在这里表示要绘制一个实心立方体;第 2 个参数是  Graphics 类的对象 g,它是由 Applet 容器创建的。 程序运行到 DrawCube 的方法,首先执行程序第 142 行: calculateOffset(); 调用程序第 97 行 calculateOffset 方法,利用下列公式: offsetX = (int)(Math.cos(alpha * Math.PI / 180) * thickness + 0.5);  offsetY = (int)(Math.sin(alpha * Math.PI / 180) * thickness + 0.5); 计算偏移量的值。注意,我们在 Math.cos(alpha * Math.PI / 180) * thickness 后面加上了一 个 0.5 的浮点数。原因是 Math.cos 的结果是一个 double 值的小数,而变量 offsetX 是一个整 型变量, 所以我们使用 (int)的方法,强制将 double 值转换成整数, 转换的结果是将 Math.cos  值的小数部分全部丢弃。如:double 类型的 10.999 和 10.0001,都被转换成同样的整数 10, 将其结果加上 0.5 后,凡是小数部分大于 0.5 的,其个位将会递增 1。如 10.999 +0.5=11.499, 转换成整数后,将得到 11 的结果;而 10.0001+0.5=10.5001,转换成整数后,该值仍然是 10。 上面的例子说明,一个浮点数取整的时候,加上 0.5 后,就实现了 4 舍 5 入的功能。  calculateOffset 方法结束后,程序返回到 DrawCube 方法中,继续执行第 143 行: buildRFront();
  • 75. 调用 buildRFront 方法。程序转到第 104 行 buildRFront 方法,在 buildRFront 方法体内, 用以下方法计算出图 2.40 所示的 P1、P2 和 P6 点: pRightX[0] = baseXPts + width;  pRightY[0] = baseYPts;  pRightX[1] = pRightX[0];  pRightY[1] = baseYPts ­ height;  pTopX[0] = baseXPts;  pTopY[0] = pRightY[1];  P0、P1、P2 和 P6 这 4 个点,正好组成正面的矩形。其中 P1 和 P2 两个点分别与右侧面平 行四边形 1P2P3P4) (P 左边两个端点重合, 6 和 P2 这两个点分别同顶部平行四边形 6P2P3  P5) P (P 下方的两个端点相重合。因此,在计算出 P1、P2 和 P6 点后,将点 P1 和 P2 的结果赋值给代表 右侧平行四边形坐标的数组 pRightX,以及 pRightY 第 1 个和第 2 个元素。将点 P6 的结果赋 值给代表顶部平行四边形坐标的数组 pTopX,以及 pTopY 第 1 个元素。 之后,程序回到 DrawCube 方法中,并执行程序第 144 行: buildPTop(); 程序转到第 117 行的 buildPTop 方法。 在 buildPTop 方法体内,用以下方法计算出图 2.40 所示的 P2、P3 和 P5 点: pTopX[1] = pRightX[1];  pTopY[1] = pRightY[1];  pTopX[2] = pTopX[1] + offsetX;  pTopY[2] = pTopY[1] ­ offsetY;  pTopX[3] = pTopX[0] + offsetX;  pTopY[3] = pTopY[2]; 经过计算以及赋值操作后,整型数组 pTopX 和 pTopY 就完成了初始化,保存了点 P6、  P2、P3、P5 坐标的相关信息。 程序返回到 DrawCube 方法中,继续执行第 145 行: buildPRight(); 程序转到并执行第 130 行 buildPRight 方法。 在 buildPRight 方法体内,用以下方法计算出图 2.40 所示的 P3 和 P4 点: pRightX[2] = pTopX[2];  pRightY[2] = pTopY[2];  pRightX[3] = pRightX[2];  pRightY[3] = pRightY[2] + height; 经过计算以及赋值操作后, 整型数组 pRightX 和 pRightY 就完成了初始化,保存了点 P1、  P2、P3、P4 坐标的相关信息。 之后, 程序返回到 DrawCube 方法中,继续执行程序第 147 行的 switch 多重选择语句块: switch (cubeStyle) 程序根据用户传递的整型参数进行选择。 现在用户传递的参数是“FILL” ,根据程序第 33 行的声明,变量 FILL 的值为 2。所以 程序执行标号为 2 的语句,即程序第 158 行~第 168 行。这段代码进行了 3 次填充操作,首 先是程序第 159 行~第 160 行:
  • 76. g.setColor(pTopFillColor);  g.fillPolygon(pTopX, pTopY, PARALLELOGRAM_POINT_NUMBER); 用  Color  类的实例  pTopFillColor  表示的颜色,即红色(Color.RED)对顶部的平行四边 形进行填充的绘制工作。 程序第 162 行~第 163 行: g.setColor(rFrontFillColor);  g.fillRect(pTopX[0], pTopY[0], width, height); 重新设置当前的绘图颜色, Color 类的实例 rFrontFillColor 表示的颜色, 用 即暗红色 (RBG  值:178,0,0)对正面的矩形进行填充的绘制工作。注意,程序第 163 行,我们在这里没有 采用 Graphics 类的 fillPolygon 方法,而是直接调用了 Graphics 类的 fillRect 方法,因为: Ø 可以减少定义两个用于保存正面矩形坐标的整型数组,节省内存; Ø 可以使程序更精简; Ø 得到了绘制矩形所需要的参数,我们可以直接调用 fillRect 方法。 因为矩形的左上角顶点(P6)和顶部平行四边形(P6P2P3P5)的第一个端点相重合,所以 直接使用 pTopX[0]和 pTopY[0]来表示该点。 此外,还可以使用点 P0 和 P6 之间的关系,即两者的横坐标相同,纵坐标相差 height,使 用下列的方法来绘制该矩形: g.fillRect(baseXPts, baseYPts ­ height, width, height); 填充完正面的矩形后,程序继续运行第 165 行~第 166 行: g.setColor(pRightFillColor);  g.fillPolygon(pRightX, pRightY, PARALLELOGRAM_POINT_NUMBER); 与程序第 159 行~第 160 行的代码基本相同。重新设置当前的绘图颜色,即用 Color 类 的实例 rFrontFillColor 表示的颜色,即深红色(RBG 值:124, 0, 0)来填充右侧面这个平行四 边形。 程序执行完第 166 行语句后,通过绘制两个平行四边形和一个矩形就得到了一个默认的 红色的实心立方体,如图 2.41①所示。这里总结一下该立方体的基本参数: Ø 立方体基准点的坐标是:10,200。 Ø 顶部平行四边形的夹角为:45°。 Ø 立方体的长度、宽度和厚度分别为:100 磅、120 磅和 30 磅。 Ø 立方体的风格为实心。 之后,程序执行第 168 行的 break 语句,中断 DrawCube 方法的运行再返回到 paint 方法 中。 程序第 203 行~第 211 行,用蓝色填充了一个如图 2.41②所示的立方体。下面我们来具 体分析。 程序第 203 行~第 207 行: setBasePoint(150, 300);  setWidth(80);  setHeight(220);  setThickness(40);  setAngle(60); 这段代码调用了 5 个 set 方法,重新设置了立方体的基准点坐标为(150,300) 、宽度为  80 磅、高度为 220 磅、厚度为 40 磅、顶部平行四边形的夹角为 60°。上述 set 方法与上一 节所讲述的 DrawTriangleApplet.java 程序中的相关 set 方法的结构和用法完全一致,我们就此 略过。
  • 77. 程序第 208 行~第 210 行: setPTopFillColor(new Color(0, 0, 255));  setFrontFillColor(new Color(0, 0, 178));  setPRightFillColor(new Color(0, 0, 124)); 这段代码调用了 3 个 set 方法,重新设置了立方体的 3 个面,即顶部平行四边形的填充 颜色、正面矩形的填充颜色及右侧面平行四边形的填充颜色。 程序第 211 行: DrawCube(FILL, g); 与程序第 200 行的代码完全相同,再次调用 DrawCube 方法,并提供与程序第 200 行代 码相同的参数。之后,程序再次进入第 140 行的 DrawCube 方法体中,重复前面讲述的计算、 赋值及填充绘制等功能。根据立方体的绘制参数,得到的绘制结果如图 2.41②所示,与默认 的立方体绘制结果(图 2.41①)相比,我们可以清楚地看到两者之间的差别。 程序第 214 行~第 215 行: setBasePoint(280, 300);  setOutlineColor(Color.ORANGE); 重新设置该立方体的基准点坐标为: (280,300) ,然后,重新设置立方体轮廓的描绘颜 色为橙色(默认为浅灰色,见程序第 38 行)。 程序第 216 行: DrawCube(OUTLINE, g); 再次调用 DrawCube 方法。 不过提供参数中的第 1 个整型参数的值由先前的“FILL”改变为“OUTLINE” 。同理, 根据程序第 33 行的定义,OUTLINE 的值为 1,所以这次调用 DrawCube 方法,进入 switch  语句块时,将执行第 149 行~第 156 行: case 1:  g.setColor(outlineColor);  g.drawPolygon(pTopX, pTopY, PARALLELOGRAM_POINT_NUMBER);  g.drawRect(pTopX[0], pTopY[0], width, height);  g.drawPolygon(pRightX, pRightY, PARALLELOGRAM_POINT_NUMBER);  break; 先设置立方体描绘的颜色为 Color 对象 outlineColor 所指定的颜色——橙色,然后,程序 分别用橙色描绘了立方体的 3 个组成部分,即顶部平行四边形、正面的矩形及右侧面的平行 四边形。绘制结果如图 2.41③所示。 该立方体绘制完成后,程序继续执行第 219 行~第 228 行。 我们先看程序第 219 行~第 227 行所进行的操作: setBasePoint(400, 280);  setWidth(120);  setHeight(180);  setThickness(20);  setAngle(30);  setPTopFillColor(Color.GREEN);  setFrontFillColor(Color.MAGENTA);  setPRightFillColor(Color.PINK);  setOutlineColor(Color.BLACK); 这段代码也是调用一些简单的 set 方法,重新设置立方体的基准点、宽度、高度、厚度、 夹角、立方体三个面的填充颜色及轮廓的描绘颜色。这次我们设置其顶部的平行四边形的填
  • 78. 充色为绿色、正面矩形的填充色为品红色、右侧面平行四边形为粉红色、立方体的轮廓描绘 颜色为黑色。 程序第 228 行: DrawCube(MIXED, g); 再次调用 DrawCube 方法,这次提供的参数中的第 1 个整型参数的值改变为“MIXED” 。 同理,根据程序第 33 行的定义,整型变量 MIXED 的值为 3,所以这次调用 DrawCube 方法, 进入 switch 语句块时,将执行第 170 行~第 186 行: case 3:  g.setColor(pTopFillColor);  g.fillPolygon(pTopX, pTopY, PARALLELOGRAM_POINT_NUMBER);  g.setColor(rFrontFillColor);  g.fillRect(pTopX[0], pTopY[0], width, height);  g.setColor(pRightFillColor);  g.fillPolygon(pRightX, pRightY, PARALLELOGRAM_POINT_NUMBER);  g.setColor(outlineColor);  g.drawPolygon(pTopX, pTopY, PARALLELOGRAM_POINT_NUMBER);  g.drawRect(pTopX[0], pTopY[0], width, height);  g.drawPolygon(pRightX, pRightY, PARALLELOGRAM_POINT_NUMBER);  break; 这段代码进行了 3 次图形的填充和 1 次的轮廓描绘操作。用我们设定的立方体 3 个面的 填充颜色,分别对立方体的顶部、正面和右侧面进行填充操作,然后,用设定的轮廓颜色对 立方体的 3 个面依次进行轮廓的描绘操作。实际运行结果如图 2.41④所示。 通过前面的学习,读者对 Java 如何控制颜色、操作字体、绘制基本的几何图形,以及扩 展到三维图形的绘制, 已经有了一个初步的认识。 下一节,我们将学习如何在 Java Applet 中, 加载并显示计算机上的图形文件。  2.11  图像的载入与显示 在现实工作中, 我们希望在 Web 图表最终输出结果中包含一些图标或者图像。这些图标 和图像可以是公司的徽标,也可以是软件的徽标(如微软的 Windows 操作系统的徽标、JSP  Web 服务器 Tomcat 的徽标等),也可能是任意一幅图片。如果这些徽标或者图像都需要我们 自己使用 Java 相关的绘制方法来完成,那么效率实在是太低了。对于一些已经绘制完成的图 像,我们可以直接使用 Java 从文件中读取并显示图像。  2.11.1  在 Applet 中加载和显示图像  Java 中有多种方法都可以加载并显示图像文件,如下所述: Ø 调用 Image 类的相关方法。 Ø 调用 ImageIcon 类的相关方法。  Java J2SE 5.0 版之前的旧版 Java 处理格式基本上分为以下三类: Ø 联合图像专家组(Joint Photographic Experts Group)格式,即 JPG/JPEG 格式。 Ø 图形交错格式(Graphics Interchange Format 格式) ,即 GIF 格式。 Ø 可移植的网络图像(Portable Network Graphics)格式,即 PNG 格式。
  • 79. J2SE 5.0 后的版本就支持了微软的 BMP 格式图像。BMP 图像作为 Windows 环境下主要 的图像格式之一,以其格式简单、适应性强而备受欢迎。因此在 J2SE  5.0 中,Sun 公司也将 其纳入 Java 的支持范围。但 BMP 文件格式,在处理单色图像和真彩色图像的时候,无论图 像数据多么庞大,都不对图像数据进行任何压缩处理,这就使得  BMP  在存储单色图像或者 真彩色图像时,文件体积过于庞大。所以在网络上,一般不采用  BMP  的文件格式来进行传 送。 表 2.19 列出了 java.swing 包中的 ImageIcon 类中的部分构造器。 表 2.19    ImageIcon 类中的部分构造器  ImageIcon()  生成尚未初始化的 ImageIcon 对象,在使用之前必须使用 Image 对象初始化  ImageIcon(String filename)  以 filename 指定的文件生成一个 ImageIcon 对象,filename 既可以表示文件的绝对路径,也可以是当前目录的相对路径  ImageIcon(String filename, String description)  同上面构造器一样,从  filename  指定的文件生成一个  ImageIcon  对象,第二个参数指定图像的说明。该图像将被  MediaTracker 类的对象预先装载用来监视图像的加载状况  ImageIcon(URL location)  以 URL 参数指定的源文件对象创建一个 ImageIcon 对象。URL 类表示统一资源定位器,它指出 World  Wide  Web 上的 资源。这里的 URL 可以是因特网上的地址,也可以是本地计算机  ImageIcon(URL location, String description)  URL 指定图像文件的位置,description 指定文件的名称。这里的 URL 可以是因特网上的地址,也可以是本地计算机  ImageIcon(Image image)  以 Image 的对象来创建一个 ImageIcon 对象  ImageIcon(Image image, String description)  以 Image 的对象来创建一个 ImageIcon 对象,第二个参数提供了图像的说明  ImageIcon 类不是 abstarct 类,所以,程序可以直接创建 ImageIcon 对象。ImageIcon 类提 供了多个构造器,允许程序使用本地计算机上的图像,或者使用存储在  Internet  上的图像来 初始化 ImageIcon 对象。 在这里我们已经涉及到了 URL,所以让我们先简要介绍 URL 和 URL 类。  1.URL  ,用 Internet 提供了许多协议,HTTP 是超文本传输协议(Hyper  Text  Transfer  Protocol) 在 World Wide Web 上,它构成了因特网的基础。它用 URI(Uniform Resource Identifier)统一 资源标识符来标识 Internet 上的数据,用于指定文档位置的 URI(Uniform Resource Locator) 统一资源定位符。一般的 URL 引用文件或目录,也可以引用执行复杂任务的对象,诸如执行 数据库查询和 Internet 搜索。URL 是相对于使用路径来标识数据资源文件标准方法的扩展。  URL 由 4 部分组成,它描述了要访问的资源文件的位置、名称,以及如何与保存该文件的主 机通信而获取文件。URL 中的某些部分可以完全忽略或者是使用其默认值。 表 2.20    URL 组成 协 议 域 名 端 口 路径和文件名  http://  java.sun.com  :80  /index.html  ftp://  ftp.freebsd.org/  :21
  • 80. 协议指定了用来获得文件的通信方法。FTP 是文件传输协议(File Transfer Protocol) 、远 程收取信件的 POP3 或者 IMAP 协议,以及用于发送信件的 SMTP 协议,远程登录的 Telnet  和 SSH 协议等。 端口在计算机上只是标识特定服务的数字,一个计算机可以有多个端口,每个端口提供 不同的服务。通常不需要指定 URL 中的端口,因为标准端口与特定的协议相关,系统将指定 默认的端口。例如,HTTP 协议使用 80 端口、FTP 协议使用 21 端口、POP3 使用 110 端口、  SMTP 协议使用 25 端口、TELNET 协议使用 23 端口、SSH 协议使用 22 端口,等等。 因为域名是数据资源引用的一部分,网络上每个数据源都有惟一的 URL。只要知道数据 资源在因特网上的 URL、储存该数据的计算机已经连接在因特网中并对外提供服务、具有相 应的读取权限,我们就可以获得该数据资源。  2.URL 类  ImageIcon 的构造器中接收了 URL 类的实例,  URL 类封装了 URL, 它隶属于 java.net 包。  Java 的网络功能集成在几个包中。 java.net 包中的类和接口声明了基本的网络功能, 使开发基 于 Internet 和 Web 的应用更为简单。本书讲述的内容(第 3 章以后的内容) ,也是一种基于  Internet/intranet 的 Web 服务器端编程技术。Java 通过该包提供了基于流的通信(stream­based  communication) ,使得应用程序能够把网络视为数据流。此外,java.net 包中的类和接口还提 供了基于数据包的通信(packet­based  communication) ,用于传输单独的信息包,通常是在  Internet 上用来传输音频和视频。 本书对网络的讨论,主要集中在浏览器/服务器(Browser/Server)的两端。浏览器请求 执行某些操作,服务器响应其请求并执行相应的操作,将执行结果返回给浏览器。 程序  LoadImageApplet.java(chap02Fig2.11Fig2.11_01)演示了如何加载并显示几种格式 图像的方法, 除了旧版 Java 支持的 PNG、  GIF、  JPG 外, 我们在程序中还提供了如何加载 BMP  图像文件的方法,运行结果如图 2.42 所示。讲解本程序源代码之前,读者可以在相应的目录 下先浏览本程序将要加载并显示的 4 个不同格式的图像文件。 图 2.42    Applet 加载并显示不同格式的外部图像文件 程序第 6 行: import java.net.*;  ImageIcon 类接收 URL 实例的构造器,URL 类是在 java.net 包中被定义的,所以,这里 必须引入 java.net 包。 程序第 7 行:
  • 81. import javax.imageio.*; 旧版 Java 并不支持 BMP 格式的图像文件,Java 在新版的 javax.imageio 包中的 ImageIO  类中提供对读取 BMP 格式图像文件的支持。 因为例程中涉及到了 BMP 格式图像文件的读取, 所以,这里也必须引入 javax.imageio 这个包。 程序第 11 行: private Image logoJPG, logoBMP, logoPNG; 声明了 3 个 Image 类的变量 logoJPG、logoBMP 和 logoPNG,分别表示加载 JPG、BMP  和 PNG 格式图像文件的 Image 对象。Image 类属于 java.awt 包中 Image 类,可以读取图像文 件。 程序第 12 行: private ImageIcon logoGIF; 定义了一个 ImageIcon 类的对象 logoGIF,表示该对象用于处理 GIF 格式的图像文件。 程序第  15 行~第  36  行,是  Applet  的  init  方法,加载图像、初始化和预处理工作就在  Applet 的 init 方法中完成。 程序第 18 行: logoJPG = getImage(getDocumentBase(), "logo.jpg");  Image 类是一个抽象类 (abstract class)所以 Applet 不能直接创建 Image 类的对象。  , Applet  必须调用一个方法,让 Applet 容器加载并返回程序要使用的 Image 类的对象。JApplet 的超 类 Applet 提供了一个名为 getImage 的方法,该方法将 Image 加载到 Applet 中,方法接收两 个参数——图像文件的位置和图像文件名。在第  1  个参数中,Applet  方法  getDocumentBase  返回一个 URL,表示图像资源在 Internet 中的位置(如果 Applet 是从本地计算机上加载的, 则返回该图像资源在本地计算机中的位置) 。本例中,Java Applet 编译后的.class 文件和调用 该.class  文件的  HTML  文件,以及  4  个不同格式的图像文件都保存在同一个目录下。方法  getDocumentBase 以 URL 类的对象的形式返回图像文件 logo.jpg 在本地计算机中的位置,第  2 个参数指定图像文件的文件名。 从第 18 行开始,程序就从本地计算机加载图像(或者从 Internet 下载图像,视 URL 对 象的内容而定) 。程序使用一个独立的线程去执行加载图像,这样,程序在图像开始加载后, 继续执行下面的语句。 提示:如果 URL 对象指定被请求的文件资源不可用,getDocumentBase 方法并不提示出 错或者抛出异常。 程序第 20 行: logoPNG = getImage(getDocumentBase(), "logo.png"); 该行与程序第 18 行语句,除了加载的图像文件的文件名不同外, 其余完全相同。程序在本 地计算机中加载一个名为 logo.png 的图像文件时,也使用一个独立的线程去执行它。 程序第 26 行: logoGIF = new ImageIcon(new URL(getCodeBase(), "logo.gif"));  ImageIcon 类不是 abstract 类,所以可以直接创建 ImageIcon 的实例。本条语句在执行时, 就创建了 ImageIcon 类的对象 logoGIF,用于加载本地计算机上的文件名为 logo.gif 的图像文 件。从表 2.19 中,我们可以看到 ImageIcon 类提供了多个构造器,允许程序使用本地计算机 上的图像,或者是存储在  Internet  中的图像来初始化  ImageIcon  对象。注意,如果以程序第
  • 82. 26 行的方式, 创建并初始化 ImageIcon 对象时, 系统可能会抛出异常。例如,输入错误的 URL  字符串等,所以我们把该语句放入程序第 23 行~第 35 行之间的 try…catch 语句块中。在这 里发生的异常,可能属于以下两类或者其派生的子类: Ø  IOException  Ø  java.net.UnknowHostException  程序第 29 行: logoBMP = ImageIO.read(new URL(getCodeBase(), "logo.bmp")); 同理,  URL 对象可能产生异常, 所以我们将其也放入程序第 23 行~第 35 行之间的 try…  catch 语句块中。不同的是,这里我们处理的是 BMP 格式的图像文件。传统的 getImage 方法 是不支持 BMP 格式的,我们在此引入了 javax.imageio 包中的 ImageIO 类来处理。logoBMP  是一个 Image 类的对象,Image 是抽象类,不能直接实例化,必须由 Applet 调用另外一个方 法,让 Applet 容器加载并返回程序要使用的 Image 类的对象。ImageIO 的 read 方法,返回一 个 BufferedImage 的对象引用,BufferedImage 是 java.awt.Image 类的子类,而子类的实例对象 是可以被其父类直接使用的,故此,实现了获得 Image 类的实例的目的。  init 的方法执行后,程序执行第 39 行的 paint 方法。paint 方法中的第 42 行~第 48 行, 在 Applet 上绘制了一条橙色的文本后, 继续执行其余语句, 将需要显示的图像一一绘制出来。 程序第 51 行: g.drawImage(logoJPG, 5, 50, this); 调用 Graphics 类的 drawImage 方法,显示 logoJPG 对象。drawImage 方法需要 4 个参数: 第 1 个参数是将要显示的 Image 类的对象引用(这里是 logoJPG) ;第 2 个和第 3 个参数是个 整形参数,表示该 Image 类的对象的引用 logoJPG 在 applet 中绘制所需的 x 和 y 坐标,该坐 标表示图像左上角顶点的坐标位置;最后一个参数是  ImageObserver  对象的引用。通常,  ImageObserver  是 程 序 用 来 显 示 图 像 的 对 象 , ImageObserver  可 以 是 任 何 一 个 实 现  ImageObserver 接口的对象。  Component 类实现了 ImageObserver 接口, 因此, 所有的 Component  都是 ImageObserver。因为 JApplet 也是 Component 类的子类(见第 2.2.1 节组件与容器,图  2.3 所示) ,所以 JApplet 也是 ImageObserver。最后一个参数使用了关键字“this” ,说明当前 这个 ImageObserver 对象就是 JApplet 本身。  ImageObserver 负责处理从 URL 制定的资源上加载图像数据的过程。当这个图像数据在 因特网上,而且图像文件比较大的时候,图像的加载过程就可能有比较长的延迟。为解决此 问题,ImageObserver 接口还声明了 imageUpdate 的方法,该方法会在前面请求有关图像的信 息可用时自动调用,并在组件上绘制该图像。 本语句执行后,将在制定位置显示 Image 类的对象 logoJPG,运行结果如图 2.42①所示。 程序第 52 行和第 54 行: g.drawImage(logoPNG, 160, 50, this);  g.drawImage(logoBMP, 5, 160, this); 同样是调用 Graphics 类的 drawImage 方法来显示图像,除了需要显示的 Image 对象不同 外,其余全部相同。这里是分别绘制一个 PNG 和 BMP 格式的图像文件。运行结果如图 2.42  ②和图 2.42③所示。 程序第 55 行: logoGIF.paintIcon(this, g, 160, 160); 直接调用 ImageIcon 类的 paintIcon 方法来显示图像,该方法需要 4 个参数:Component  对象引用、Graphics 对象引用及图像的绘制坐标(以图像的左上角为基准点) 。本语句的运行
  • 83. 结果如图 2.42④所示。 对比这两种加载及显示图像的方法,我们推荐使用 ImageIcon 方法。因为 ImageIcon 方 法更简单,更直接地创建  ImageIcon  类的实例,而且在显示图像的时候也不需要使用  ImageObserver 的引用。  2.11.2  使用 MediaTracker 加载并显示图像 上节我们阐述了两种加载并显示图像的方法,但是采用异步的方式,即程序在后台完成 图像的加载和显示,而主进程继续执行。这种情况可能在图像还未被完全加载完毕时造成不 完整的显示。在某些情况下,我们需要在图像完全加载完毕后再进行显示,要得到这种功能 就需要了解本节介绍的另外一种加载图像的方法,即调用媒体跟踪器(Media  Tracker)来加 载图像。 媒体跟踪器是 MediaTracker 类型的对象,专门用来跟踪图像的加载。这个类在 java.awt  包中定义,目前只管理图像的载入,以后可能会扩展其功能用来跟踪其他类型的媒体资源的 加载。MediaTracker  只有一个构造器,需要一个组件引用的参数,这个组件对象就是装入图 像的组件。表 2.21 列出了媒体跟踪器的构造器及其相关方法。 表 2.21  媒体跟踪器的构造器及其相关方法  public MediaTracker(Component comp)  构造器:接收一个用于加载图像的 Component 对象  public void addImage(Image image, int id)  续表 把 Image 类的对象 image 添加到当前媒体跟踪器要追踪的图像列表中,整型对象 id 表示该 image 的标识  public void waitForAll()  初始化加载过程并等待所有被跟踪的图像加载  public boolean waitForAll(long ms)  指定初始化加载过程并等待图像加载的时间,时间用毫秒表示  Public boolean checkID(int id)  检查指定的标识为 id 的 Image 对象 image 是否加载完毕,如果已经加载完毕,则返回真  Public boolean checkAll()  检查所有被跟踪的 Image 对象 image 是否加载完毕,如果已经加载完毕,则返回真  Public boolean isErrorAny()  检查所有被跟踪的 Image 对象 image 的错误状况,没有错误则返回假  Public boolean isErrorID(int id)  检查指定的标识为 id 的 Image 对象 image 在加载过程中是否发生错误,没有错误则返回假 媒体跟踪器的使用是在调用  getImage()方法,获得对 Image 的对象引用后,调用跟踪器 的 addImage()方法就将 Image 对象传递给媒体跟踪器。addImage 方法有两个参数:要跟踪的 图像的引用,以及用来跟踪图像的  int  类型的标识。媒体跟踪器可以同时跟踪多个图像,该 标识决定被加载图像的优先级。 程序  MediaTrackerApplet.java(chap02Fig2.11Fig2.11_02)演示了如何在  Applet  中调用  MediaTracker 类以及跟踪图像,运行结果如图 2.43 所示。
  • 84. 图 2.43  用媒体跟踪器(MediaTracker)加载外部图像文件 程序第 9 行~第 11 行: private Image[] images = new Image[3];  private Image bg; // 背景图像 private MediaTracker mt = null; 声明了 1 个 Image 类的数组对象 images,其中包含 3 个 image 对象,1 个 Image 类的对 象 bg,表示本 Applet 将要加载的背景图像,还声明了一个 MediaTracker 类的对象 mt。 程序第 14 行~第 27 行,在 init 方法内,对上述变量进行初始化。 程序第 16 行: bg = getImage(getCodeBase(), "images/background.gif"); 用 Applet 类 getImage 方法得到一个 Image 类的对象引用,并传递给 Applet。注意,在  getImage 的方法内,第 2 个参数的使用方法: "images/background.gif" 表示图像文件的位置在 Java  Applet 当前目录下的 images 子目录中,因此这里表示加载 当前目录中的 images 子目录下的 background.gif 图像文件。 程序第 19 行: mt = new MediaTracker(this); 初始化 MediaTracker 的实例 mt。MediaTracker 只有一个构造器,需要一个组件引用的参 数,这个组件对象就是装入图像的组件。这里的 this 表示本容器,也就是在本 Applet 中创建 图像。 程序第 20 行: mt.addImage(bg, 0); 使用 MediaTracker 类的 addImage 方法,指定要跟踪的图像对象。媒体跟踪器可以同时 跟踪多个图像,多个图像可以拥有相同的标识,标识的值决定加载图像的优先级,具有相对 较低标识值的图像优先加载。这里,图像 bg 的标识指定为 0。 程序第 22 行~第 25 行: for (int i = 0; i < images.length; i++)
  • 85. {  images[i] = getImage(getCodeBase(), "images/logo" + (i + 1) + ".jpg");  mt.addImage(images[i], i + 1);  } 使用了一个循环,在用 getImage 方法获得图像的引用后,将当前目录下的 images 子目 录中 logo1.jpg、logo2 以及 logo3.jpg 图像文件,依次添加到当前媒体跟踪器 mt 要跟踪的图像 列表中。标识在这里分别设定为 1、2、3。  init 方法结束,进入 paint 方法中。 程序第 32 行~第 40 行: try  {  mt.waitForAll();  }  catch (InterruptedException e)  {  System.out.println(e);  return ;  } 程序在第 34 行,调用 waitForAll 方法初始化加载过程,并等待所有被跟踪的图像加载完 毕后返回。如果要加载某个特定标识的图像对象,可以调用跟踪器的  waitForID  方法,并传 递一个标识给该方法,开始加载与某个特定标识相关的所有图像对象,并等待所有与该标识 相关的图像加载完成后才返回。例如,希望跟踪标识为 1 的图像,在上面的语句块中,只需 要更改程序第 34 行: mt.waitForID(1); 如果我们只希望跟踪某一个特定的图像对象,就赋予该媒体跟踪器中所跟踪的每个图像 对象都具有一个惟一的标识。这样,一旦出现异常,我们可以通过相应的方法获得出错图像 的标识,从而找到发生错误的地方。 这里介绍的 waitForID 和 waitForAll 方法将无限期进行等待。因为这些方法可能会抛出 异常,所以将其放入一个 try…catch 语句块中。还有另外一个版本的 waitForAll 方法,该方 法需要一个长整型变量,指定初始化加载过程以及等待图像加载的时间,时间用毫秒表示, 由这个长整型的参数决定。但是使用这种方法,我们并不知道在该方法返回时,图像是否真 的装载完毕了,还是等待的时间到了;也许图像早就加载完毕了,但因为设置的等待时间还 没有结束,因此媒体跟踪器仍旧在等待,这样会严重浪费系统资源。所以我们推荐使用前面 介绍的第 1 和第 2 种方法,而不要随意使用需要接收一个等待时间参数的 waitForAll 方法。 如果 waitForID 和 waitForAll 方法在无限期的等待中抛出异常,将被程序第 36 行~第 40  行的 catch 语句块所捕获,然后在命令控制台输出一条有关的异常信息,之后,主线程返回,  Applet 上没有任何显示。  waitForAll  方法执行完毕后,虽然所有被跟踪的图像都已经加载完毕,但并不说明这些 图像的加载过程中没有任何错误(例如,图像的路径有错、或者图像的文件名有误、或者文 件名和路径都正确,但却是 Java  不支持的图像格式) ,所以,我们必须明确地检查错误,因 此程序第 42 行~第 53 行的代码就负责此项检查工作: if (mt.isErrorAny())  {  for (int i = 0; i <= 4; i++)  {  if (mt.isErrorID(i))  {  g.drawString("图像产生错误状况!文件标志为:" + i, 10, 40 + 20 * i);  }
  • 86. }  return ;  } 程序第  42  行,调用了  isErrorAny 方法,用来检查并确定任何被追踪的图像是否产生了 错误。如果方法返回 false,则说明没有错误发生;如果图像产生了错误,isErrorAny 方法返 回真。我们在程序第 44 行~第 49 行的代码中,对其进行进一步的检查。 程序第 44 行~第 50 行, 编写了一段循环代码来检查并确定到底是哪一个被追踪的图像产 生了错误。程序第 46 行,我们将循环语句中的局部变量 i 作为参数并调用 isErrorID 方法。 如果 isErrorID 方法返回 true,则说明至少在加载一个图像标志与变量 i 相关的图像发生了错 误。因为每个图像都指定了惟一的标识,所以一旦发生错误,可以轻松地找到错误的图像。 找到发生错误的图像后,程序将在 Applet 上绘制出一条与该错误相关的信息。 当确定图像的加载没有错误时,就可以显示这些图像了。 程序第 55 行: g.drawImage(bg, 0, 0, this); 我们前面介绍过的 Graphics 类的 dwawImage 方法, Applet 上绘制图像 bg, 在 如图  2.43  ①所示。 图像 bg (background.gif)的宽度和高度分别是 565 磅和 470 磅。 为达到将该图作为 Applet  背 景 的 目 的 , 在 调 用  MediaTrackerApplet.class  的  HTML  文 档 中 , 我 们 指 定 该  Media  TrackerApplet.class 的宽度和高度正好与图像 bg(background.gif)的宽度和高度相同。例如,  MediaTrackerApplet.html 文档的第 4 行~第 5 行所示: <APPLET CODE = "MediaTrackerApplet.class" WIDTH = "565" HEIGHT = "470">  </APPLET> 程序第 57 行以及第 59 行: g.setFont(new Font("汉真广标", Font.PLAIN, 30));  g.drawString("MediaTracker 的使用", 15, 30); 用“汉真广标” (30 磅,普通风格)这种字体在 Applet 上的坐标(15,30)处绘制文本 “MediaTracker 的使用” 。Java 的绘图模式默认为覆盖模式,所以, “MediaTracker 的使用” 这段文本将显示在已经绘制完成的图像 bg 上,见图 2.43②所示。反之,如果程序第 57、59  行的位置互换,将无法看到文本“MediaTracker 的使用” ,因为图像对象 bg 将它覆盖了。 程序第 61 行~第 64 行: for (int i = 0; i < 3; i++)  {  g.drawImage(images[i], 410, 40+i * 100, this);  } 循环绘制出 Image 的数组对象 images 中的每一个图像对象。因为,每个图像对象的绘制 横坐标都为 410 像素(磅) ,而纵坐标依次递增 100 像素(磅) ,所以,显示效果呈现纵向排 列。如图 2.43③、2.43④、2.43⑤所示。 为了测试 MediaTrackerApplet.java 程序中的异常捕获功能是否正常工作,我们将 images  子目录下的 background.gif,以及 logo2.jpg 分别重新改名为 bf.gif 和 logo2bak.jpg。重新启动  IE,再调用 MediaTrackerApplet.html 文档,我们可以看到,程序中的异常捕获功能已经能够 正常工作了。因为,每个图像对象都定义了惟一的标识,所以在输出结果上,可以看到标志 为“0” “2” 和 的图像对象出现了错误, 而这两个图像文件正是我们刚刚更名前的 background.gif  以及 logo2.jpg(标志分别为 0 和 2,见源程序第 20、25 行) 。出现异常后的程序输出结果,
  • 87. 如图 2.44 所示。 图 2.44  异常的捕获  2.11.3  使用双缓冲技术绘制图像 读者可能已经发现, 在运行前面第 2.8.4 节所讲述的绘制圆柱体的源程序, 以及运行 2.10.5  节所介绍的绘制平行四边形及立方体源程序的时候, 当我们将 IE 从当前窗口转变成非当前窗 口状态,再从非当前窗口恢复到当前窗口状态,有时,某些绘制好的图像会消失,除非我们 重新刷新 IE 窗口,显示才会恢复正常。此外,当我们移动 IE 窗口或者其他的窗口在 IE 上移 动的时候,图像会有些闪烁。但运行 2.11.1 节的加载并绘制图像文件的源程序,却没有这种 现象。这是怎么一回事呢?这就要涉及到 Java  Applet 中的 paint 方法的绘图机制了。产生这 种现象的主要原因是: Ø 由于在显示所绘制的图像时,调用了  repaint  方法。repaint  方法被调用时,需要清除 整个背景,然后才调用 paint 方法显示画面。这样,在清除背景和绘制图像的短暂时 间间隔内被用户看见的就是闪烁。 Ø 由于 paint()方法需要进行复杂的计算,图像中包含着多个图形,不同图形的复杂程度 及其所需要的绘制时间不同,因此,图像中的各个像素值不能同时产生,使得图形的 生成频率低于显示器的刷新频率,从而造成闪烁。 提示:运行 Java 编写的动画程序时,发生不连贯或闪烁现象时,可参考下文所介绍的方 法加以改善。 下面两种方法可以明显地消除或减弱闪烁: Ø 重载 update 方法 当 AWT 接收到 Applet 重新绘制的请求时,调用 Applet 的 update 方法。 默认情况下,  update  方法清除 Applet 的背景,然后调用 paint 方法。重载 update 方法,就可以将以前在 paint 方法 中的绘图代码包含在 update 方法中,从而避免每次重新绘制时将整个区域清除。 Ø 双缓冲技术 双缓冲技术在很多动画 Applet 中被采用。主要原理是创建一幅后台图像,将每一帧画入 图像,然后调用 drawImage 方法,将整个后台图像一次画到屏幕上去。这种方法的优点在于 大部分绘制是在后台进行的。将后台绘制的图像一次绘制到屏幕上。在创建后台图像前,首 先通过调用  createImage  方法生成合适的后台缓冲区,然后获得在缓冲区的绘图环境(即  Graphics 类对象)。 综上所述,改善前面我们写的一些 Java  Applet 源程序的思路是:不直接在 paint 方法中 调用各种绘制方法,而是采用重载 update 方法及双缓冲技术,生成一个图像的缓冲区,获得
  • 88. 该缓冲区中的绘图环境后,将该绘图环境读入内存。paint 方法不再负责图像的绘制工作,即  paint 方法不再装入任何的图像绘制代码。我们在 paint 方法中,直接调用 update 方法,在内 存缓冲区的绘图环境下进行图像的绘制工作,当所有的图像绘制工作完成后,最后将缓冲区 的内容一次性地写入 Applet 并在 Applet 窗口中直接显示出来。 这种方法很巧妙地解决了图像 丢失和闪烁的问题。 现在我们就遵循上面的思路,重新改写第 2.8.4 和第 2.10.5 节所介绍的源程序。 首 先 看 改 写 的  2.8.4  节 介 绍 的 绘 制 圆 柱 体 的 源 程 序  DrawBufferedCylinder.java  (  chap02Fig2.11Fig2.11_03)。  DrawBufferedCylinder.java 与第 2.8.4 节所介绍的源程序 DrawCylinderApplet.java 在绘制 圆柱体时的不同之处在于,前者是先绘制在内存缓冲区中的,然后显示在 Applet 窗口中,而 后者是直接在 Applet 上进行绘制。 程序第 16 行~第 17 行: Image offImage;  Graphics offGraphics; 声明了一个名为 offImage 的 Image 对象,表示缓冲区的 Image 对象。然后声明了一个名 为 offGraphics 的 Graphics 对象,offGraphics 是标识缓冲区的图像绘制环境。这两个系统成 员变量在这里仅仅是声明,并没有初始化,我们将在 init 方法中对其进行初始化。 程序第 19 行: int appltWidth = 370, appletHeight = 420; 增加了两个类的数据成员变量 appletWidth 和 appletHeight, 分别表示本 Applet 的宽度和高 度。appletWidth  和  appletHeight  的值,与调用本  Applet  的  HTML  文档  DrawBuffered  Cylinder.html 中的 Applet 属性中的 Width 和 Height 相同。 程序首先运行第 22 行~第 26 行的 init 方法: public void init()  {  offImage = createImage(appltWidth, appletHeight);  offGraphics = offImage.getGraphics();  } 使用 Component 类的 createImage 方法,创建了一个 Image 类的实例,并将该实例对象 赋给 offImage。Component 类的 createImage 方法,返回的是一个后台的(off­screen),用于 双缓冲的图像对象。它接收两个整型参数,分别指定该图像对象的宽度和高度,如果该  Component 是不可显示的部件,则返回的结果为 null。 接着,用 Image 类的 getGraphics 方法,创建了一个名为 offGraphics 的 Graphics 对象, 并获得图形环境。后台的(off­screen)缓冲图像将由它来产生,因为这里画的是内存缓冲区 图像,所以 Applet 窗口上不会有显示。 然后, 程序执行第 68 行的 paint 方法, 我们强制其执行程序第 73 行~第 91 行的  update  方法。 程序第 75 行~第 77 行: offGraphics.setColor(Color.BLACK);  offGraphics.setFont(new Font("汉真广标", Font.PLAIN, 35));  offGraphics.drawString("圆柱体画法", 10, 40); 前面例程都是调用 paint 方法中的参数 Graphics 对象 g 的相关绘制方法,对文本和其他 图形进行绘制操作。上面的代码是在缓冲区图像的图形环境中绘制,所以,我们不再调用  g  的相关方法,而是调用  offGraphics  的相关方法进行绘图作业。因为  offGraphics  是缓冲区图
  • 89. 像的图形环境对象, 所以每当 Applet 调用 offGraphics 方法时,绘制工作都将在内存中的缓冲 区中进行。这几行代码设置了当前的绘图颜色、字体并绘制一条文本“圆柱体画法” 。 程序第 80 行: drawCylinder(offGraphics); 调用 drawCylinder 方法,并将 offGraphics 作为参数传递给该方法。因此,程序第 46 行~ 第 66 行: public void drawCylinder(Graphics g)  {  // 计算椭圆的坐标 calculateOvalCoordinate();  // 绘制下方椭圆 g.setColor(fillColor);  g.fillOval(ovalX2, ovalY2, ovalWidth, ovalHeight);  g.setColor(outlineColor);  g.drawOval(ovalX2, ovalY2, ovalWidth, ovalHeight);  // 绘制中间的矩形 g.setColor(fillColor);  g.fillRect(rectX, rectY, rectWidth, rectHeight);  // 绘制上方的椭圆 g.setColor(g.getColor().darker());  g.fillOval(ovalX1, ovalY1, ovalWidth, ovalHeight);  g.setColor(outlineColor);  g.drawOval(ovalX1, ovalY1, ovalWidth, ovalHeight);  } 因为,传递的参数是缓冲区的图形绘制环境对象 offGraphics,所以,drawCylinder 方法 同样是在 offGraphics 上进行绘制。这里需要注意的是程序第 90 行,update 方法中的最后一 条语句: g.drawImage(offImage, 0, 0, null); 在调用 drawImage 方法时把 null 作为第 4 个参数,这样可以防止 drawImage 调用 update  方法。因为图像的所有内容都已装入内存,所以,图像在 Applet 窗口的显示就一气呵成了。 现在我们检测程序的运行结果。 将 IE 从当前窗口转变成非当前窗口状态, 再从非当前窗 口恢复到当前窗口状态,我们会发现,绘制好的图像不再消失了,也不需要重新刷新 IE 窗口 了。 重新改写的第  2.10.5  节所介绍的绘制立方体的  DrawBufferedParallelogram.java  (chap02  Fig2.11Fig2.11_04)的源程序我们也不在赘述,请读者自行查阅并运行该程序。
  • 90. 2.12  本章小结 本章主要介绍了色彩的基本知识,Java Applet 运行机制及如何编写、运行 Java Applet 程 序,详细论述了各种基本几何图形在 Java Applet 下的编程工作。 详尽阐述了 Java.awt.Graphics 类的各种方法,包括绘制直线、文本(字符串) 、矩形、椭 圆、圆、圆弧、多边形和折线。在绘制基本的几何图形的基础上,我们以绘制圆柱体和立方 体为例,向读者展示了如何通过绘制多个多边形并将其组合成一个复杂的几何图形的方法。 用多个多边形实现  3D  效果,以及组合各种不同基本几何图形和多边形就能够生成复杂的图 形。 讲述了  Java  Applet  的图形加载及显示的几种方法,并分析了各自的优点。因为  Applet  的局限性,在图像绘制完成的情况下,可能会出现图像丢失或者闪烁的情况,为解决这个问 题,我们提出了如何利用双缓冲技术来改善绘制性能,并采用双缓冲技术重新改写了几个以 前的绘制程序。 在掌握了 Java Applet 绘制各种基本的几何图形及多边形的方法后, 接下来我们将学习使 用 Java Applet 生成 Web 动态图表的几个简单实例。 第 3 章  Java Applet 图表绘制实例 本章将阐述利用 Java  Applet 来绘制 Web 动态图表的一些简单实例。目前,企业中基于  B/S 体系结构的应用程序越来越多,对 B/S 应用程序的要求也越来越高。WWW 的发展使得 基于 B/S 的应用程序不再局限于静态或者简单的动态内容。在工作中,我们经常将所需要的 数据用图表的方法在 Web 页面上表示出来,例如:柱形(直方)图、饼形图、线段图、趋势 图、甘特图等,并且这些图表能够根据数据的改变而自动更新。  Java Applet 是可用于开发的一种轻量级、较简单、基于客户端的 Web 动态图表。  3.1  Java Applet 生成 Web 动态图表 本节介绍几个利用 Java  Applet 来绘制 Web 动态图表的简单实例。参与图表运算的数据 在实际工作中的来源是多种多样的。有通过 HTML 表单提交的,有通过 Applet 传递参数的, 但绝大多数是通过查询或访问数据库而得到的。因为本书不涉及  Java  和数据库编程的相关知 识,所以本书中凡是涉及到 Web 图表运算的实时数据都通过随机数产生。 为让读者能尽快地掌握 Web 动态图表的编程思路和方法,我们在讲述本章的内容时,将 每节的例程都用一个单独的 Java 源程序来编写(Fig.3.02_02:CheckMaker.java 除外) 。我们将 在学习过程中,逐步过渡到用面向对象的编程思路来设计 Web 动态图表。
  • 91. 首先让我们看看 Web 图表的外观结构,如图 3.1 所示。  Web 图表结构可以简单地分成两大区域: Ø 标题区:主要负责绘制图表的标题,表示本图表的名称,一般位于整个图表的上方。 除此之外,还可以将其设置在整个图表的下方、左方及右方。 Ø 图表区:负责绘制具体的图形。如图 3.1 中的折线图及三维饼图,图表区中有时还包 括图例。 图例就是图中所示的三维饼图的右边部分。 图例用来说明图表中各个部分所表示的意义。 在图表区中,某些图表需要坐标系作为参考。如图 3.1 中的折线图,而某些形状的图表 则不需要坐标系,如图  3.1  中所示的三维饼图。同样地,我们在图表区域中,可以设置图例 来说明图表中各部分所代表的含义,如三维饼图所示;也可以不用图表,直接在坐标系中说 明图表中各个部分的含义,如折线图中的坐标系所示。
  • 92. 标题区 图表绘制区 标题区 图表绘制区 图 3.1  图表的布局结构 提示:为使 Web 动态图表的标题更具美观和多样性, 读者可以在本地计算机中安装几套 字库。本书中所使用的字库除 Windows 系统自带的以外,还使用了额外安装的方正字库、经 典字库等。  3.1.1  垂直柱状图 垂直柱状图是使用最频繁的一种 Web 图表。我们就用它作为第一个 Web 动态图表的例 程。现在我们希望用垂直柱状图来表示某书店 5 种计算机编程类书籍在某天销售量的统计情 况。各书籍的销售量从一个  1~100  之间的随机数中产生。如图  3.2  所示,程序  VBar.java  (chap03Fig3.1Fig.3.1_01)的运行结果。 图 3.2 实际上是由三部分组成: (1)图表标题:也就是文本“Java Web 图表设计 Applet 版——柱状图”。 (2)图表区又由两部分组成: Ø 坐标系:横坐标表示编程书籍,纵坐标表示销售数量。 Ø 表示不同书籍销售量的柱状图。 每次我们重新刷新页面时,VBar.java 程序的运行都会重新生成新的、随机的书籍销售数 据。当销售数据发生变化后,图 3.2 中所示了表示各书籍销售量的柱状图也相应地发生变化。
  • 93. 图 3.2  VBar.java 的运行结果(垂直柱状图) 垂直柱状图的设计思路: (1)首先获得每种书籍的销售量。 (2)确定标题区域的范围。 (3)确定图表绘制区域的范围。 (4)绘制标题。 (5)在图表绘制区域内,绘制以下图形对象: Ø 表示书籍销售量的纵坐标。 Ø 表示书籍名称的横坐标。 Ø 用绘制实心矩形来代表垂直的条形,确定相邻矩形的间隔宽度。 Ø 根据每本书的销售量依照一定的比例绘制不同高度的实心矩形。 本章所有代码采用前一章所介绍过的双缓冲技术。程序 VBar.java 第 8 行~第 9 行: Image offImage;  Graphics offGraphics; 声明用于绘制缓冲区图形 Image 对象 offImage 及其在缓冲区的绘图环境 offGraphics。 程序第 12 行: int appltWidth = 600, appletHeight = 500; 声明了两个整型变量 appletWidth 及 appletHeight 并对其初始化,这两个变量分别表示本  Applet 的宽度和高度,它们的值和调用该 Applet 的 HTML 文档中 Applet 的参数相同。注: 如果没有特别说明, 本章中所有的 Java 源程序中与 Applet 相关的宽度和高度的取值与调用该  Applet 的 HTML 文档相同。 程序第 14 行: int bookSales[] = new int[5]; 声明了一个整型数组 bookSales,表示书籍的销售量,我们将在 Applet 中的 init 方法里对 其进行初始化。 程序第 16 行~第 19 行: String bookTitle[] =  {  "Python", "JAVA", "C#", "Perl", "PHP"
  • 94. }; 声明并初始化了一个字符串数组 bookTitle,表示被统计的计算机书籍的书名。这里我们 用了 Python、JAVA、C#、Perl,以及 PHP 这五个字符串来表示这些书名。 程序第 22 行~第 25 行: Color color[] =  {  new Color(99, 99, 0), Color.GREEN, Color.YELLOW, Color.RED, Color.BLUE  }; 声明了包含有五个 Color 对象的 Color 数组,数组中的每种颜色用于表示不同的书籍。 其中 new Color(99, 99, 0)的颜色效果为褐色。 到这里,VBar 类的成员变量就全部声明完毕。那么如何在宽为 600 像素,高为 500 像素 的绘图环境中分配标题区域和图表绘制区域?如何在图表绘制区域中再划分柱状图的绘制区 域?如图 3.3 所示。 标题区域 图表绘制区域 (80,40) (80,65) (480,65) 实心矩形图绘制区 (80,445) 坐标系原点 (480,445) (550,445) 图 3.3  垂直柱状图表的设计思路 我们的绘制思路如下: (1)标题区域占据整个 Applet 最上面高度为 30 像素,宽度为 600 像素的矩形区域。也 就是图 3.3 中最上面的绿色区域。 (2)剩下的区域都是图表绘制区域。除标题外,其余所有图表对象都绘制在此区域内。 除去标题区域,图表绘制区域实际上的宽度为 600 像素,高度为 470 像素。本例中,此区域 内的图形绘制对象又由两部分组成: Ø 坐标系。纵坐标表示书籍的销售量,因为每本书的销售量是从一个  1~100 之间的随 机数中产生的,也就是说,每种书的销售量最大值为 100,如果直接用像素来表示, 仅仅需要 100 像素就够了。现在我们还剩下 470 像素,如果仅用 100 像素,不仅不美 观,而且也浪费了大量的空间。所以我们在垂直方向上用  380  像素来表示销售量为  100  的这个数据,简而言之,也就是每一个单位的销售量就表示纵坐标上  38  像素。 横坐标表示书籍的名称,本例中共有五种不同的书籍。这里我们用 400 像素的宽度来 表示这些书籍,也就是说,实心矩形的绘制区域在宽度为 400 像素,高度为 380 像素 的矩形区域内,而我们最终确定的坐标系原点位于点(80,445) 。两条坐标轴,从美 观和实际的运行效果来看,应该分别比实心矩形绘制区域的宽度和高度的值更大一 些。从图  3.3 我们可以看到,纵轴的最高点比矩形绘制区域的高度大  20  像素,坐标 为 (80,  ; 40) 横轴的端点比实心矩形绘制区域的宽度更多出 70 像素, 坐标位于(550,  445) 。
  • 95. Ø 上述坐标系确定后,就可以确定实心矩形的绘制坐标了。我们先确定所有代表书籍销 售量的实心矩形的底端和横轴相重合,然后就可以推算出除实心矩形的高度后,就可 以推算出实心矩形的左上角顶点的坐标。另外,我们设定所有的实心矩形的宽度为  50 像素。 Ø 绘制的顺序是,先绘制标题区域,然后是绘制代表书籍销售量的  10  条直线,以及代 表各书籍实际销售量的实心矩形,最后是绘制坐标系及其说明文字。 在理清上述 Web 图表的绘制思路后,我们接着讨论源程序的 init 方法: 程序第 28 行~第 38 行: public void init()  {  offImage = createImage(appltWidth, appletHeight);  offGraphics = offImage.getGraphics();  for (int i = 0; i < bookSales.length; i++)  {  bookSales[i] = 1+(int)(Math.random() * 100);  }  } 首先还是创建缓冲区 Image 对象 offImage 及其绘图环境对象 offGraphics,然后初始化整 型数组 bookSales。注意语句: 1+(int)(Math.random() * 100); 正好生成一个 1~100 之间的整数。通过一个循环,将 bookSales 中的每个元素都进行赋 值。接着会运行 paint 方法,然后在 paint 方法中调用第 48 行~第 96 行的 update 方法。 update  在 方法里面的第 51 行~第 53 行: offGraphics.setColor(Color.black);  offGraphics.setFont(new Font("方正粗宋简体", Font.BOLD, 30));  offGraphics.drawstring("Java Web 图表设计 Applet 版——柱状图", 15, 30); 就实现了绘制标题区域的功能。用黑色,粗体,30  磅的“方正粗宋简体”这种字体在  Applet 坐标的点(15,30)处绘制了文本“Java Web 图表设计 Applet 版——柱状图”。 完成标题区域的绘制后,现在进入图表绘制区域。 程序第 56 行: offGraphics.setFont(new Font("SansSerif", Font.PLAIN, 12)); 重新设置当前的绘制字体。 前面说过,实心矩形绘制区域的高度为 380 像素表示 100 个单位的销售量,也就是说, 每 38 像素就相当于 10 个单位的销售量。为了更清晰地表达销售量的显示效果,我们在实心 矩形的绘制区域内再绘制 10 条直线,分别表示 10、20、30、…、90、100 个单位的销售量, 并且在每条直线旁标明该条直线所代表的销售量的数据。 程序第 57 行~第 67 行,实现了上述功能: int salesValue = 0;  for (int i = 418; i > 0; i ­= 38)  {  offGraphics.setColor(Color.black);  offGraphics.drawstring("" + salesValue, 36, (i + 27));  offGraphics.setColor(Color.lightGray);  offGraphics.drawLine(80, (i + 27), 520, (i + 27));  salesValue += 10;
  • 96. } 先声明了一个局部的整型变量 salesValue,并将其值初始化为 0。因为坐标系的原点位置 在点(80,445)处,也就是说,销售量为 0 的直线的纵坐标值都为 445,而销售量为 10 个 单位的直线纵坐标值都为 445-38=407。先从最下面一条直线开始绘制,第 1 次循环,循环变 量 i 的值为 418,注意第 61 行,我们的用法: offGraphics.drawstring("" + salesValue, 36, (i + 27));  drawString 方法接收的第 1 个参数是字符串型,所以我们用: "" + salesValue 这种方式,将一个内容为空的字符串 (注:不是空字符串 null)和其后面的整数 salesValues  连接起来后,整个结果就被 Java 的编译器自动转变成一个字符串。这种用法非常简单,推荐 大家使用。 也就是说,第 1 次循环,程序第 61 行和第 64 行,实际执行的内容是: offGraphics.drawstring("0", 36, 445);  offGraphics.drawLine(80, 445, 520, 445); 之后,salesValue 的值递增 10。 同理,第 2 次绘制,程序第 61 行和第 64 行,实际执行的内容是: offGraphics.drawstring("10", 36, 407);  offGraphics.drawLine(80, 407, 520, 407); 其余步骤,此处不再赘述。程序执行完第 67 行后,绘制完成的图像如图 3.4①所示。 图 3.4  垂直柱状图表的绘制过程 接着绘制表示各种计算机书籍销售量的实心矩形。 程序第 70 行~第 81 行: int drawHigh = 0;  for (int i = 0; i < bookTitle.length; i++)  {  offGraphics.setColor(color[i]);  drawHigh = (int)(Math.ceil(bookSales[i] * 3.8));
  • 97. offGraphics.fill3DRect(110+i * 80, 445­drawHigh, 50, drawHigh, true);  offGraphics.setColor(Color.BLACK);  offGraphics.drawstring(bookTitle[i], 110+i * 80, 465);  } 首先声明了一个局部变量 drawHigh,该变量表示销售量所对应的矩形的绘制高度。接着 我们用一个循环,计算出每种计算机书籍的销售量所对应的绘制高度。然后程序在第 77 行, 绘制实心矩形: offGraphics.fill3Drect(110+i * 80, 445­drawHigh, 50, drawHigh, true); 实心矩形的左上角顶点的绘制参数由下列方法计算: 横坐标:110+i * 80  纵坐标:445–drawHigh  也就是说,第一次循环绘制第 1 个实心矩形左上角的横坐标为 110。而每一个循环后, 实心矩形的横坐标依次增大 80 像素。矩形的纵坐标由其绘制高度通过“445–  drawHigh”得 到,矩形的宽度为  50  像素(即两个相邻之间的实心矩形的间隔距离是  30  像素) ,高度为  drawHigh 像素。这样就保证了所有的实心矩形的下端都和纵坐标为 445 的直线相重合,而矩 形的填充颜色由 Color 对象的数组 Color 决定。 然后重新设置当前绘图颜色为黑色,再通过调用 drawString 方法绘制出该实心矩形所代 表计算机书籍的名称,程序第 80 行: offGraphics.drawString(bookTitle[i], 110+i * 80, 465); 注意,这里的纵坐标都是  465,就保证了所有的说明文字都在同一水平线上(纵坐标为  ,而横坐标正好和矩形左上角顶点的横坐标相同,这样实心矩形和其说明文字也 465 的直线) 就排列整齐了。 程序循环的次数由表示计算机书籍名称的字符串数组 bookTitle 决定。 本段循环结束后,程序绘制的结果如图 3.4②所示。 接着进入坐标系的横轴和纵轴的绘制过程。程序第 84 行~第 86 行: offGraphics.setColor(Color.BLACK);  offGraphics.drawLine(80, 40, 80, 445);  offGraphics.drawLine(80, 445, 550, 445); 首先设置当前的绘制颜色为黑色。横轴和纵轴通过绘制两条直线得到。我们从横轴和纵 轴的绘制参数可以得知:两条直线交点的坐标为(80,445) 。 程序运行完第 86 行后的结果如 3.4③所示。 然后,绘制坐标系中横轴和纵轴的文字说明,程序第 89 行~第 91 行完成该功能: offGraphics.setFont(new Font("黑体", Font.BOLD, 16));  offGraphics.drawString("销售量", 20, 50);  offGraphics.drawString("编程书籍", 500, 465); 用黑体,大小为 16 磅的字体在点(20,50)处绘制文本“销售量” (纵轴的文字说明) , 然后在点(500, 465)处绘制文本“编程书籍” (横轴的文字说明)。 程序第 94 行,完成最后的工作: g.drawImage(offImage, 0, 0, null); 将缓冲区的图像内容全部输出到 Applet 中,并结束 updatefh 方法。至此,垂直柱形图的 所有绘制工作就完成了。程序的实际运行效果如图 3.2 所示。
  • 98. 3.1.2  饼图 饼图也是一种应用非常广泛的图表类型。常用于表示数据之间的对比关系。我们现在用 饼图来表示某书店五种计算机编程类书籍某天的销售量和当天所有书籍销售总量的对比关 系。各书籍的销售量仍旧从一个 1~100 之间的随机数中产生。 程序  Pie.java  演示了如何绘制饼图。同样地,我们先看图  3.5  所示的  Pie.java(chap03  Fig3.1Fig.3.1_02)的运行结果,然后分析该程序的运行流程。 图 3.5    Pie.java 的运行结果 图 3.5 实际上也是由三部分组成的: (1)图表标题:也就是文本“Java Web 图表设计 Applet 版——饼图”。 (2)图表区又由两部分组成: Ø 图 3.5 右边的图例。 Ø 图 3.5 左边部分表示各种计算机书籍销售量及其与销售总量之间比例关系的饼图。饼 图的效果实际上是通过绘制一组颜色各异的实心圆弧而得到的。 每次我们重新刷新页面的时候,  Pie.java 程序的运行都会重新生成新的、 随机的书籍销售 数据。当销售数据发生变化后,图 3.5 中的饼图也会相应地发生变化。 饼图的设计思路: (1)首先获得所有书籍的销售量。 (2)计算所有书籍的销售总量及每本书的销售量占所有书籍销售总量的比例。 (3)确定标题区域的范围。 (4)确定图表绘制区域的范围。 (5)绘制标题。 (6)在图表绘制区域内,再绘制以下图形对象: Ø 图例:图例中又包含 4 个元素,即表示某种图书的实心矩形小方块,书籍名称、书籍 销售量、该销售量占销售总量的比例。 Ø 绘制表示销售量的实心圆弧。实心圆弧的大小(弧度)由其销售量占销售总量的比例 决定。 程序第 6 行,引进了一个新的包: import java.text.*; // 引入 java.text 包中所有的类  java.text  包允许通过与特定语言无关的方式格式化文本消息、日期和数值。本例程将引
  • 99. 用该包中的 DecimalFormat 类,用于格式化浮点数的格式化输出。 与前例相同,程序第 10 行~第 11 行: Image offImage;  Graphics offGraphics; 声明了用于绘制缓冲区图形对象的  Image  对象  offImage  及其在缓冲区的绘图环境  offGraphics。 程序第 13 行: int appltWidth = 600, appletHeight = 500; 声明了两个整型变量 appletWidth 和 appletHeight 并对其初始化,这两个变量分别表示本  applet 的宽度和高度。 程序第 15 行: int bookSales[] = new int[5]; 声明了一个整型数组 bookSales,表示书籍的销售量,我们将在 Applet 中的 init 方法中对 其进行初始化。 程序第 17 行: int totalSales = 0; 声明了一个整型变量 totalSales,表示书籍的销售总量,即各种书籍销售量之合计。默认  totalSales 的值为 0。我们将在 Applet 中的 init 方法中对其进行再次赋值的操作。 程序第 19 行~第 22 行,声明并初始化了一个字符串数组 bookTitle,表示被统计的计算 机书籍的书名。这里我们同样用了 Python、JAVA、C#、Perl,以及 PHP 这 5 个字符串来表示 这些书名。 程序第 25 行~第 28 行,声明了包含有 5 个 Color 对象的 Color 数组,数组中的每种颜 色用于表示不同的书籍。其中 new Color(99, 99, 0)的颜色效果为黄褐色。 到这里,Pie 类的成员变量就全部声明完毕。那么如何在宽为 600 像素,高为 500 像素 的绘图环境中分配标题区域和图表绘制区域?如何在图表绘制区域中划分饼图的绘制区域以 及图例的绘制区域?如图 3.6 所示。 标题区域 图表绘制区域  350  190  (395, 95) (15, 95)  编程类图标 销售数量 所占比例  Python  50      22.94%  JAVA  38      17.43%  300  C#  52      23.85%  Perl  33      15.14%  PHP  45      20.64% 实心圆弧图绘制区 图例绘制区 图 3.6  饼图的设计思路 本例中饼图的绘制思路如下: (1)标题区域占据整个 Applet 最上面高度为 30 像素,宽度为 600 像素的矩形区域。也 就是图 3.3 中最上面的绿色区域。 
  • 100. (2)剩下的区域都是图表绘制区域。除标题外,其余的所有图表对象都绘制在此区域 内。除去标题区域,图表绘制区域的实际宽度为 600 像素,高度为 470 像素。本例中,此区 域内的图形绘制对象又由两部分组成: Ø 左侧的实心圆弧绘制区。一组绘制在一起的实心圆弧就组成了一个饼图的样式。该区 域中,除去蓝色的实心圆弧外,所有圆弧的外切矩形的左上角的顶点坐标为(15, 95) 。 为了美观地显示效果,我们设定其中一个实心圆弧(即蓝色圆弧)的外切矩形的左上 角的顶点坐标为(30,  93),也就是向右水平移动了 15 像素,向上垂直移动了两个像 素,这样的显示效果比较美观。另外,所有实心圆弧的外切矩形的宽度都是 350 像素, 高度为 300 像素。 Ø 确定了圆弧的外切矩形的宽度、高度,以及左上角的顶点坐标后,就可以绘制实心圆 弧了。我们将绘制的第 1 个圆弧的起点角度在 30 度,而圆弧的圆心角的大小由其销 售量决定。 Ø 右侧的图例绘制区。图例被确定在一个蓝色的空心矩形内,该矩形左上角顶点 坐标为(395,95) 左侧的实心圆弧的外切矩形的左上角的顶点坐标相同,处于 ,和 同一高度。其宽度为 190 像素,高度为 300 像素。在图例绘制区内,再依次采用相对 应的颜色绘制 1 个实心的小矩形,以及与其对应的书籍名称、销售数量及其所占销售 总量的比例。 Ø 绘制的顺序是,先绘制标题区域,然后绘制仅有一个空心矩形的不含任何文字说明的 图例,之后是绘制实心圆弧及其相应的图例。 明白饼图的绘制思路后,我们接着讨论 Pie.java 源程序中的 init 方法: 程序第 31 行~第 42 行: public void init()  {  offImage = createImage(appletWidth, appletHeight);  offGraphics = offImage.getGraphics();  for (int i = 0; i < bookSales.length; i++)  {  bookSales[i] = 1+(int)(Math.random() * 100);  totalSales += bookSales[i];  }  } 首先还是创建缓冲区 Image 对象 offImage 及其绘图环境对象 offGraphics。然后初始化整 型数组 bookSales。与前例不同的是,这里我们在对数组 bookSales 赋值的时候,同时也进行 销售总量的计算: totalSales += bookSales[i]; 每循环一次,就将新的、随机生成的数据累加到变量 totalSales 中。循环完毕,则得到了 销售总量的数值。 接着运行 paint 方法,然后在 paint 方法中调用程序第 52 行~第 106 行的 update 方法。 在 update 方法里面,我们首先绘制 Applet 的背景,程序第 55 行~第 56 行: offGraphics.setColor(new Color(144, 255, 255));  offGraphics.fillRect(0, 0, appletWidth, appletHeight); 用蓝色(new  Color(144,  255,  255))填充了整个 Applet 背景。填充背景的功能实际上是 通过填充一个同我们设定的 Applet 宽度和高度都相同的实心矩形而实现的。 程序第 59 行~第 61 行:
  • 101. offGraphics.setColor(Color.BLACK);  offGraphics.setFont(new Font("方正隶变简体", Font.BOLD, 30));  offGraphics.drawString("Java Web 图表设计 Applet 版——饼图", 15, 30); 这里就实现了绘制标题区域的功能。用黑色、粗体、30 磅的“方正隶变简体”这种字体 在 Applet 坐标点(15,30)处绘制了文本“Java Web 图表设计 Applet 版——饼图” 。 完成标题区域的绘制后,现在进入图表绘制区域。 程序第 64 行: offGraphics.drawString("计算机编程类图书销售统计表", 70, 465); 在  Applet  的底部绘制一条文本“计算机编程类图书销售统计表” 。采用的字体和前面的 相同。 程序第 65 行~第 71 行: offGraphics.setFont(new Font("SansSerif", Font.PLAIN, 12));  offGraphics.drawString("编程类图书", 400, 125);  offGraphics.drawString("销售数量", 475, 125);  offGraphics.drawString("所占比例", 535, 125);  offGraphics.setColor(Color.blue);  offGraphics.drawRect(395, 95, 190, 300); 重新设置字体后,绘制了 3 条文本:“编程类图书”“销售数量”及“所占比例” 、 。它们 的纵坐标都为 125,同在一条水平线上。然后绘制一个空心的蓝色矩形,其绘制参数如图 3.6  所示,左上角顶点(395,95) ,宽度和高度分别为 190 像素和 300 像素。 程序运行完第 71 行后,绘制结果如图 3.7 所示。 图 3.7    Pie.java 的运行流程 程序第 74 行: int arcStartAngle = 30, arcAngle = 0; 声明了两个整型变量 arcStartAngle 和 arcAngle。  arcStartAngle 表示绘制圆弧的起始角度, 默认的起始角度是 30 度。arcAngle 表示圆弧两条边线之间的夹角(圆心角) ,默认值为 0。 程序第 75 行:
  • 102. float proportion; 声明了  1  个浮点变量:proportion,该变量表示某种图书销售量与所有图书销售总量之 间的比例关系。 程序第 76 行: DecimalFormat twoDigits = new DecimalFormat ("0.00"); 声明了 1 个 DecimalFormat 类的对象 twoDigits。DecimalFormat 类隶属于 java.text 包。我 们前面讲过,引用该包中的 DecimalFormat 类,用于格式化浮点数的格式化输出。在本例中, 我们决定输出的变量 proportion 值的小数点右边带两位小数(即精确到 0.01)。该语句创建 一个格式为“0.00”进行初始化的 DecimalFormat 对象,其中每个“0”分别指定格式化浮点 数中必不可少的数位。这种特定的格式表明每个用 twoDigits 格式化后的数字,其小数点坐 标至少有 1 位,而小数点右边至少保留两位。 如果被格式化的数字与上面的格式要求不匹配, 则“0”将被自动插入到该数相应的数位上。 然后程序就进入了实心圆弧及其图例说明文字的绘制过程,程序第  78  行~第  101 行实 现了这个过程: for (int i = 0; i < bookTitle.length; i++)  {  arcAngle = (int)(bookSales[i] * 360 / (float)totalSales + 0.5);  proportion = ((float)bookSales[i]) / totalSales * 100;  offGraphics.setColor(color[i]);  if (i < bookTitle.length ­ 1)  {  offGraphics.fillArc(15, 95, 350, 300, arcStartAngle, arcAngle);  }  else  {  offGraphics.fillArc(30, 93, 350, 300, arcStartAngle, arcAngle);  }  arcStartAngle += arcAngle;  offGraphics.fillRect(400, 155+i * 50, 12, 12);  offGraphics.setColor(Color.black);  offGraphics.drawString(bookTitle[i], 420, 167+i * 50);  offGraphics.drawString("" + bookSales[i], 490, 167+i * 50);  offGraphics.drawString("" + twoDigits.format(proportion) + "%", 540,  167+i * 50);  } 在循环体中,程序第  80  行首先计算圆弧的圆心角的度数。注意,我们又用了在浮点数 后面再加上 0.5,然后再取整的操作,达到了小数点后一位数字四舍五入的功能。圆心角的值 等于该种图书的销售量除以销售总量再乘以 360。因为 Java 的除法操作符“/”默认是整除。 因为我们声明的整型数组变量 bookSales 中的元素,以及变量 totalSales 都是整数。所以,在 整型变量 totalSales 前面用了一个关键字“float”就将整型变量 totalSales 的值转变为浮点类 型的数值了。当执行除法操作时,结果就不再是整数,而自动转变为 folat 类型的浮点数了。 然后把得到的浮点类型的结果加上 0.5 再取整后, 实现了小数点后一位数字四舍五入的功能, 最后再把取整后的结果赋值给变量 arcAngle,这样我们就得到了当前圆弧的圆心角的度数。 程序第 81 行, 计算当前销售量占销售总量的百分比。 同样的, 我们强制转换 bookSales  [i]为浮点型。所以当所得的商再乘以 100 后,结果仍是浮点型,然后结果被赋值给 float 变量  proportion。 程序第 83 行,设定当前的绘制颜色。接下来,绘制实心圆弧:
  • 103. if (i < bookTitle.length ­ 1)  {  offGraphics.fillArc(15, 95, 350, 300, arcStartAngle, arcAngle);  }  else  {  offGraphics.fillArc(30, 93, 350, 300, arcStartAngle, arcAngle);  } 上述代码说明,如果绘制的不是最后一个实心圆弧(图  3.5  和图 3.6  中所示的蓝色实心 圆弧) ,则一直执行程序第 87 行。该圆弧的外切矩形的参数都是一样的,即图 3.6 中所示的 左上角顶点坐标为(15,95) ,宽度为 350 像素,高度为 300 像素。第 1 次循环, arcStartAngle  的值为 30,也就是说,第 1 个圆弧的起始位置在 30 度,圆弧的圆心角的大小已经在程序第  80 行计算出了。这时,根据这些参数,就可以绘制出第 1 个实心圆弧。 如果这时局部变量 i 的值等于 bookTitle.length-1,则说明绘制的是最后一个实心圆弧, 执行程序第 91 行。就是圆弧的外切矩形的左上角顶点坐标变更为(30,93) ,宽度和高度保 持不变,分别为 350 像素和 300 像素。 程序第 94 行: arcStartAngle += arcAngle; 重新计算绘制下一个圆弧的起始角度。本条语句实现的功能是,绘制下一个圆弧的时候, 其起始角度正好和当前圆弧的结束位置相同。 绘制完实心圆弧后,接着绘制图例部分。程序第  96  行,先用当前颜色绘制一个宽度和 高度都为 12 像素的实心矩形,然后程序第 97 行,改变当前的绘制颜色为黑色。 程序第 98 行,绘制当前书籍的名称。程序第 99 行,绘制当前书籍的销售量。 程序第 100 行: offGraphics.drawString(twoDigits.format(proportion) + "%", 540, 167+i *  50); 先格式化浮点变量 proportion 的输出格式。注意,这里格式化字符串所使用的方法: twoDigits.format(proportion) 调用 DecimalFormat 类的 format 方法,来格式化所传递参数的输入格式,然后在返回的 结果后再加上一个“%”符号,然后再绘制其百分比。 在绘制书籍名称、 销售数量及销售比例这 3 个字符串的时候, 它们的纵坐标都是相同的, 都是 167+i  *  50。这样就保证了这三个字符串的绘制高度相同。而每循环一次,绘制的新的 这三条字符串的高度会比当前字符串的绘制位置低 50 像素。 程序运行到这里,就结束了一次循环,然后会重复上述过程。 循环语句执行结束后,程序运行第 104 行: g.drawImage(offImage, 0, 0, null); 将缓冲区的图像内容发送到 Applet 上显示,之后便结束了 update 方法,饼图的所有绘制 工作就完成了。程序的实际运行效果如图 3.5 所示。  3.2  Java Applet 生成单据 从前面的例程我们可以看到,用 Applet 来生成 Web 动态图表是非常方便的。有时候,我 们需要生成一些复杂的图表: (1)带有某个公司徽标的图表。 图 3.8    Java 的徽标
  • 104. (2)各种单据: Ø 账本及相关凭证; Ø 支票; Ø 销售发票; Ø 其他单据:如发货单、提货单、入库单、检验单、海关报关单,等等。 下面简单谈谈解决上述问题的思路,先说说第一个问题:比如说要绘制一个附带有如图  3.8 所示的 Java 徽标。 解决方法很简单,上一章我们讨论过在 Java Applet 如何加载并显示图像。那么,我们就 利用这一功能来在 Applet 中载入某个公司的徽标即可。对于第二个问题,以支票的生成为例。 我们认为最高效及简单的解决办法就是:通过扫描一张完整的、清晰的空白支票而得到一张 图像,然后以该图像的宽度和高度生成一个大小相同的 Java Applet,仔细调整相关的绘制参 数,在需要填写的位置上绘制出相关的内容即可。这种方法同样也适用于生成其他各种形式 的单据。 这里,我们提供两个简单的实例,分别展示如何用上述思路来解决这些问题。  3.2.1  带徽标的 Web 动态图表 以本章 3.1 节的 VBar.java 为例, 在该程序的基础上再显示一个如图 3.8 所示的 Java 徽标, 那么该如何修改 VBar.java 源程序呢? 源程序 LogoVBar.java(chap03Fig3.2Fig3.2_01)是 VBar.java 的升级版本,实现了上述 功能。本程序和第 3.1 节的 VBar.java 相比,仅仅改动了几个地方。 在程序第 9 行,新增加了一个 Image 类的对象: Image logoJPG; 在程序第 38 行,init 方法中用 Applet 类的 getImage 方法对其进行初始化: logoJPG = getImage(getDocumentBase(), "javaLogo.jpg"); 在 update 方法中,程序第 52 行: offGraphics.drawImage(logoJPG, 100, 0, this); 在点(100,0)处绘制 Image 对象 logoJPG。 程序第 55 行: offGraphics.drawString("带 Java 徽标的 Web 图表", 135, 40); 重新绘制了一条文本,我们改变了文本的内容和绘制位置。程序的其他部分没有任何改 变,读者可以参考第 3.1 节的解释。 程序的运行结果如图 3.9 所示。
  • 105. 图 3.9  带徽标的 LogoVBar.java 的运行结果
  • 106. 3.2.2  支票的生成 前面提到,生成类似支票这样的单据的最高效及简单的办法就是:通过扫描一张完整、 清晰的空白支票而得到一张图像,然后以该图像的宽度和高度生成一个大小相同的  Java  Applet,在该 Applet 中加载并显示该图像,然后仔细调整相关的绘制参数,在需要填写的位 置上绘制出相关的内容即可。 上述方法的前提是仅仅需要实现扫描一张完整、清晰的空白支票。下面以一张我们自 己制作的转账支票为例,讲述如何生成单据,如图 3.10 所示。 图 3.10  转账支票的外观 简单地说,支票分为左右两部分。左边的部分是支票存根,右边部分是交付给收款人或 银行。需要填写的内容主要有“出票日期”“付款金额”“收款人”“出票人账号”“用途” 、 、 、 、 、 “付款行名称”“科目” 、 ,以及“对方科目”等栏目。似乎只需要调整好上述各栏目绘制内容 的坐标就可以很轻松地完成支票的生成工作了。其实不然,支票的绘制看似简单,实则有几 个不大不小的麻烦需要处理。  1.出票日期的处理 Ø 需要将年、 月、 日提取出来, 并将其分别输入到相应栏目。 另外,在支票右边部分 “出 票日期”一栏,需要输入大写的日期。也就是说,这里涉及到如何将“2004­12­13” 这种日期格式中的年、月、日转换成 “贰零零肆”年“拾贰”月及“拾叁”中文格 式。 Ø 对于“年”的转换,我们可以将阿拉伯数字和中文大写数字进行简单的一对一的转换 操作,如“1999”“2005”可以直接转换成“壹玖玖玖”“贰零零伍” 、 、 ; Ø 对于“月”的转换,却不能直接将阿拉伯数字和中文大写数字进行简单的一一对应的 转换操作,如“10”“11”“12”这三个月份,按简单的一一对应的方法转换,我们 、 、 得到的是 “壹零” 、 “壹壹” “壹贰” 和 的结果, 这不符合我们的习惯称呼。 “10”、“11”、 “12”这三个月份的转换结果应该是“拾”“拾壹”和“拾贰” 、 。但对于 1~9 月份, 则可以直接采用按简单的一一对应的方法进行转换。 Ø 对于“日”的转换,同“月”的转换类似。1~9 之间的“日” ,可以直接转换。 “10”、 “20” “30” 和 这几个日子的转换结果, 应该分别是 “拾” 、“贰拾” “叁拾” 而 和 。 “11”~ “19”之间的日子转换结果应该是“拾壹”~“拾玖”“21”~“29”之间的日子转 ; 换结果应该是“贰拾壹”~“贰拾玖”“31”转换成“叁拾壹” ; 。  2.转账金额的处理 这里涉及到以下几个方面:
  • 107. Ø 如何将小写的转账金额转换成中文的大写金额,即如何将“6 437 192.08”这类的小写 金额转换为“陆佰肆拾叁万柒仟壹佰玖拾贰圆(元)零角捌分”这种中文货币的大写 金额。 Ø 转账金额在支票左边部分的绘制还需要将其格式化成货币的表现形式, 即用小数点将 元和角、分隔开,并在千位,百万,十亿(以千位单位)的数位上用逗号隔开。如“6  437 192.08”应该表示为: “¥6 437 192.08”的格式。 Ø 转账金额在支票右边的绘制内容有两部分,一部分需要在“人民币大写”一栏内,以 中文大写格式填入转账金额, 另一部分是将阿拉伯数字格式的转账金额中的每一个数 字都相应地填写到“人民币大写”一栏右边的小写栏内。注意,在小写栏内转账金额 的最高位数字前还需要绘制一个人民币(¥)的符号。 源程序  CheckMaker.java(chap03Fig3.2Fig3.2_02)演示了如何实现支票的绘制功能。 上面谈到了程序应该对日期及金额进行中文转换的操作,在本例程中,除了没有提供“将小 写的转账金额转换成中文的大写金额”这一功能外,其余所有功能都可以在源程序中通过一 些简单而巧妙的方法实现。我们在 CheckMaker.java 引用了一个自定义的 DateToChinese 类来 实现阿拉伯数字的年、月、日到中文大写日期的转换功能。 我们先看看源程序 CheckMaker.java 的运行结果,如图 3.11 所示。 图 3.11  CheckMaker.java 的运行结果 我们先讨论实现阿拉伯数字的年、 日到中文大写日期转换功能的 DateToChinese. java  月、 (chap03Fig3.2Fig3.2_02)的源程序的运行机制。 程序第 7 行~第 10 行: String[] chineseNumber =  {  "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "拾"  }; 定义了一个字符串数组  chineseNumber,该数组中每一个元素分别对应着用中文表示的 阿拉伯数字中的 0~9。 程序第 12 行~第 15 行: public DateToChinese()  {  Super();  } 声明默认的构造器,在其方法体内,用  super  方法来调用其父类默认的构造器方法。程 序第 4 行在声明类的名字为 DateToChinese 的时候,并没有指明本类所继承的父类。在这种 情况下,Java 默认所有未指明父类的类都将继承 Object 类,Object 类位于 java.lang 包中。实
  • 108. 际上,Object 类是所有 Java 类的超类,也就是说,每个 Java 类都是 Object 类的子类或间接 子类,都继承 Object 类所声明的 11 个方法。根据 Java 机制,每个 Java 类的默认构造器中, 不论程序是否显示 super 方法来调用其父类默认的构造器方法, 编译器都将用 super 方法来调 用其父类的默认构造器方法。 类的继承、封装及多态的内容不属于本书讨论的范畴,但因本书中的源程序涉及到这三 个面向对象设计的最重要的概念,所以在这里我们仅做一个很简单的介绍。 除默认的构造器外,DateToChinese 类提供了 5 个方法以实现不同的功能: Ø 程序第 17 行,getDayNumber 方法返回日期中“日”的中文大写转换结果。 Ø 程序第 48 行,getMonthNumber 方法返回日期中“月”的中文大写转换结果。 Ø 程序第 61 行,getCapitalNumber 方法返回日期中“年”的中文大写转换结果。 Ø 程序第 85 行,quotient 方法返回两个整数之间的整除运算结果。 Ø 程序第 90 行,remainder 方法返回两个整数之间的求模(余数)运算结果。 我们先介绍程序第 61 行~第 83 行的 getCapitalNumber 方法。该方法代码如下: public String getCapitalNumber(int number)  {  int divisor = 1, digit;  String output = "";  // 找到“最大的基数” for (int i = 1; i < number; i *= 10)  {  divisor = i;  }  while (divisor >= 1)  {  digit = quotient(number, divisor);  output += chineseNumber[digit];  number = remainder(number, divisor);  divisor = quotient(divisor, 10);  }  return output;  } //分离数字结束  getCapitalNumber  方法接收一个整型参数,以该方法接收到的整型参数“2004”为例, 讨论该方法的实现过程。 程序第 63 行,声明了两个整型变量:divisor 和 digit,其中 divisor 的初始值为 1。 程序第 64 行,声明了 1 个字符串变量:output,初始化为一个内容为空的字符串,表示 要返回的字符串结果。 程序第 67 行~第 70 行,用一个循环找到整数 2004 的“最大基数”“最大基数”在这里的 。 意思是指某个整数最高位数(也就是,从左往右数第 1 个非零数字)的基数,比如说: 2004 = 2 * 1000 + 0 * 100 + 0 * 10 + 4 * 1 所以 2004 的最大基数是 1000。又如: 642589 = 6 * 100000 + 4 * 10000 + 2 * 1000 + 5 * 100 + 8 * 10 + 9 * 1 所以 642 589 的最大基数是 100 000。 计算最大基数后,并将最大基数赋值给变量 divisor,此时 divisor 的值为 1000。 接下来,程序第 72 行~第 80 行的另一个 while 循环: while (divisor >= 1)
  • 109. {  digit = quotient(number, divisor);  output += chineseNumber[digit];  number = remainder(number, divisor);  divisor = quotient(divisor, 10);  }  divisor 的值为 1000,大于 1,所以进入 while 循环,在其循环体内: 程序第 74 行,调用 quotient 方法,并传递两个参数:2004 和 1000。然后程序会跳转至 第 85 行的 quotient 方法: public int quotient(int a, int b)  {  return a / b;  }  quotient  方法接收两个整型参数,并返回这两个整数执行整除运算的结果。这里接收的 是 2004 和 1000,因为 2004/1000=2,所以 digit 的值为 2。然后程序返回 getCapital  Number  方法中的 while 循环中第 76 行,继续执行。 此时,相当于执行: output += chineseNumber[2]; 而 chineseNumber[2]的结果为“贰” ,因此,字符串变量 output 的值现在就变为“贰”。 接下来,程序执行第 78 行,调用 remainder 方法,并提供两个整型参数:2004 和 1000, 然后程序会跳转至 90 行的 remainder 方法: public int remainder(int a, int b)  {  return a % b;  }  remainder 方法也接收两个整型参数,并返回这两个整数执行求模运算的结果。这里接收的 是 2004 和 1000,因为 2004%1000=4,所以返回的结果是 4。然后程序返回 getCapital Number  方法中的 while 循环中第 78 行,将结果 4 赋值给变量 number 后,继续执行程序第 79 行。 此时,相当于执行: divisor = quotient(1000, 10); 再次调用 quotient 方法,并将结果 1000/10=100 赋值给变量 divisor。此时 divisor 的值等 于 100,仍然不小于 1,所以程序继续进行第 2 次循环,同样重复上面的步骤,这次接收的参 数是 4 和 100, 因为 4/100=0, 所以 digit 的值为 0,之后,因为 chineseNumber[0]的值为“零”, 所以 output 的值就变成了“贰零” ,同理,变量 nubmer=4 % 100,所以 number 的值仍然为 4, 而 divisor=100/10,所以 divisor 的值就改变为 10。 现在进入第 3 次循环,同样重复上面的步骤,这次接收的参数是 4 和 10,因为 4/10=0, 所以 digit 的值还是为 0,之后,因为 chineseNumber[0]的值为“零” ,所以 output 的值就变成 了“贰零零” ,同理,变量 nubmer=4%10,所以 number 的值仍然为 4,而 divisor=10/10,所 以现在 divisor 的值就改变为 1 了。 现在进入第 4 次循环,同样重复上面的步骤,这次接收的参数是 4 和 1,因为 4/1=4,所 以 digit 的值改变为 4,之后,因为 chineseNumber[4]的值为“肆” ,所以 output 的值就变成了 “贰零零肆” ,同理,变量 nubmer=4%1,所以 number 的值仍然为 4,而 divisor=1/10,所以 现在 divisor 的值就改变为 0 了,因为 0 小于 1,所以程序结束循环。 最后将字符串变量  output  内容作为  getCapitalNumber  方法的返回值。至此,getCapital  Number 方法结束。
  • 110. 综上所述,getCapitalNumber  方法实现的功能实际上是将给定的一个任意长的整数(当 然该整数被限定在 Java 对整数的处理范围之内)按一一对应的方式把数字转换成中文,并返 回转换后的结果。 现在介绍程序第 48 行~第 58 行的 getMonthNumber 方法。该方法代码如下: public String getMonthNumber(int month)  {  String output = "";  if (month > 10)  {  output += "拾";  month ­= 10;  }  output += getCapitalNumber(month);  return output;  } 顾名思义,该方法返回日期“月”的中文大写转换结果。getMonthNumber 方法接收一个 整型参数 month,程序第 50 行,声明了一个字符串对象:output,并初始化其内容为空。如 果传递的参数在 1~10 之间,则程序跳过第 51 行~第  55 行之间的 if 语句块,执行第 56 行 的语句: output += getCapitalNumber(month); 将变量 month 作为参数再次传递并调用 getCpitalNumber 方法,把 getCpitalNumber 方法 返回的字符串在串接在变量 output 后,最后返回 output。 如果传递的参数大于 10,这里以传入的 month 参数: “12”为例,则程序首先进入第 51  行~第 55 行之间的 if 语句块,执行完第 53 行的语句后,output 值更新为“拾” 。第 54 行, 将 month 参数减去 10 后,再将相减后的结果重新赋值给 month。所以这时,month 的值更改 为 2(12­10)。然后再执行程序第 56 行的语句,这时实际上执行的是: output += getCapitalNumber(2);  getCpitalNumber(2)方法返回的结果是“贰” ,现在字符串变量 output 的值就等于“拾贰” 了,最后再返回 output 的内容。 综上所述,对于“月”的转换,getMonthNumber 方法对“10”“11”“12”这三个月份 、 、 返回的结果是: “拾”“拾壹”和“拾贰” 、 ;对于 1~9 月份,则可以直接采用按简单的一一对 应的方法返回相应的结果。注意,这里我们并没有对大于 12 以上的数进行额外的处理,因为 在程序 CheckMaker.java 中, 我们调用 getMonthNumber 方法时所传递的参数范围就限定在 1~  12 之间了。 现在介绍程序第 17 行~第 46 行的 getDayNumber 方法。该方法代码如下: public String getDayNumber(int day)  {  String output = "";  if (day < 20)  {  output += getMonthNumber(day);  }  else if (day >= 20 && day < 30)  {  output += "贰拾";  day ­= 20;  if (day == 0)  {  return "贰拾";  }
  • 111. output += getCapitalNumber(day);  }  else  {  output += "叁拾";  day ­= 30;  if (day == 0)  {  return "叁拾";  }  output += getCapitalNumber(day);  }  return output;  }  getDayNumber 方法首先对传入的参数 day 进行判断: (1)day 小于 20,则执行程序第 22 行,直接将 day 传递给 getMonthNumber 方法,会 重复执行刚刚我们所讲述的 getMonthNumber 方法的执行过程,然后执行程序第 45 行,并返 回所得到的结果。 (2)day 如果大于或等于 20 且小于 30,则执行程序第 24 行~第 33 行之间的语句块。 这里以 day 等于“27”为例,首先 output 的内容就改变为“贰拾” ,然后 day 减去 20,如果 差为“0” ,说明 day 正好等于 20,则立刻返回字符串“贰拾” ;否则将新的 day 的值传递给  getCpitalNumber 方法,即执行 getCpitalNumber(7)方法,返回的结果“柒”附加在字符串 对象 output 的内容后,这时字符串 output 的内容就改变为“贰拾柒”了。最后执行程序第 45  行,返回 output 的内容。 (3)在其他情况下,则执行程序第  35  行~第  43  行之间的语句块。与前面介绍的第  2  点运行结构过程完全相同,此处不再赘述。同理,注意这里我们并没有对大于 31 以上的数进 行额外的处理,因为在程序 CheckMaker.java 中,调用 getDayNumber 方法时所传递的参数范 围就限定在 1~31 之间了。 现在讨论 CheckMaker.java 的运行过程。 程序第 6 行~第 7 行: import java.util.*; //引入 java.util 包中所有的类 import java.text.*; //引入 java.text 包中的所有的类 分别引进 java.util 包和 java.text 包。java.text 包我们已经介绍过了。java.util 包是一些工 具类的集合,里面包含了一些常用的工具类,如用于日期及时间处理的 Date 类等。 程序第 11 行: Image checkPNG; 声明了一个 Image 类的对象 checkPNG,用于加载和显示背景图像,如图 3.10 所示的空 白支票。 程序第 12 行~第 13 行: Image offImage;  Graphics offGraphics; 声明用于绘制缓冲区图形 对象的  Image  对象  offImage  及其在缓 冲区的绘图环境  offGraphics。 程序第 15 行: int appltWidth = 740, appletHeight = 270; 定义本 Java Applet 的宽度和高度。这里的宽度和高度与背景图像,即空白支票的宽度和
  • 112. 高度相同。 程序第 16 行~第 22 行,创建了一些字符串对象用于空白支票某些栏目的绘制。上述字 符串对象分别表示: “科目”“对方科目”“收款人”“付出行名称”“用途”“会计”以及 、 、 、 、 、 “单位主管”等内容。 程序第 24 行: Date issuedDate = new Date(); 创建了一个 Date 类的实例 issuedDate,表示支票的出票日期。 Date 类隶属于 java.util 包, 主要用于 Java 对日期和时间的处理。 用 new Date()方法创建的 Date 对象表示的是当前的系统 时间。默认的 Date 对象的输出格式如下所示(以 2004 年 12 月 13 日为例) : Mon Dec 13 20:12:50 CST 2004­12­13 程序第 25 行~第 27 行,声明了 3 个整型变量:year、month,以及 day,分别表示支票 出票日期的年、月、日。 程序第 28 行: double amount = 6437192.08d; 创建了 1 个值为 6 437 192.08d 双精度型的变量:amount,表示转账金额。最后一位数字 后面的 8 表示该浮点数为双精度型(这里的 d 可以省略不写,我们的编程习惯是写上“d”表 示 double 型,“f”表示 float 型,以示区别) 。 程序第 30 行: String accountNumber = "渝工行 2004010178930"; 创建字符串对象 accountNumber,表示出票人账号。 程序第 31 行: String chineseAmount = "陆佰肆拾叁万柒仟壹佰玖拾贰圆零角捌分"; 创建了一个字符串变量  chineseAmount,并用“陆佰肆拾叁万柒仟壹佰玖拾贰圆零角捌 分”对其进行初始化。chineseAmount 实际上是程序第 28 行,转账金额 6 437 192.08 的人民 币大写的货币表现格式。 因为本程序并没有涉及到如何将类似于程序第 28 行的数字值自动转 换成相应的人民币大写的货币表现格式的方法, 所以这里我们直接提供了一个与 6 437 192.08  相对应的人民币大写的货币表现格式。对此有兴趣的读者,可以借鉴我们提供的年、月、日 的转换方法来完成这一功能。 程序第 33 行: NumberFormat moneyFormat = NumberFormat.getCurrencyInstance(Locale.  PRC); 创建了 1 个 NumberFormat 的实例对象 accountNumber,并调用 NumberFormat 的静态方 法  getCurrencyInstance 对其进行初始化。该方法返回一个能将数字值格式化为指定的货币值 形式(如表示美元的数字值前,通常加一个“$”符号;表示人民币的数字值前,通常加一个 “¥”符号)的 NumberFormat 对象。方法中的参数 Locale.PRC 表示货币值应该按中国的人 民币格式显示,即以“¥”符号开头,并用小数点将元和角、分隔开,并在千位、百万、十 亿(以千位单位)的数位上用逗号隔开。Locale 类提供了一些静态的常量,用于表示特定国 家的货币值,以便能正确地显示某个国家的货币格式。其他的一些表示国家和地区的静态常 量还有: Ø  Locale. CHINA:中国 Ø  Locale. US:美国
  • 113. Ø  Locale. CANADA:加拿大 Ø  Locale. FRANCE:法国 Ø  Locale. GERMAN:德国 Ø  Locale. UK:英国 Ø  Locale. KOREA:韩国 Ø  Locale. JAPAN:日本 Ø  Locale. ITALY:意大利 Ø  Locale. TAIWAN:中国台湾  NumberFormat 隶属于 java.text 包,而 Locale 类隶属于 java.util 包。两者分别在程序第 6、  7 行引进。 程序第 35 行: DateToChinese dtc = new DateToChinese(); 用 DateToChinese 类的默认构造器创建了 1 个 DateToChinese 类的实例——dtc。注意,我 们并没有引入  DateToChinese  类。如果一个类与使用它的类位于同一个包(目录)中,则不 需要 import。如果程序员没有为类指定一个包,则该类将置于一个没有名称的默认包中,它包含 当前目录中所有已编译、但未显示放置在某个包中的类。这就是我们为什么必须在 Java 程序中, 为了使用 Java API 中的类,就一定要用 import 语句将其引入后才可以使用。 提示:如果程序通过在类名前面添加完全包名来限制类的名称,则可以不需要 import 语 句。例如,程序第 24 行,可改写成以下形式: java.util.Date issuedDate = new java.util.Date(); 至此,本例的变量声明完毕。接下来程序运行第 38 行~第 46 行的 init 方法: public void init()  {  checkPNG = getImage(getDocumentBase(), "check.jpg");  offImage = createImage(appletWidth, appletHeight);  offGraphics = offImage.getGraphics();  year = issuedDate.getYear() + 1900;  month = issuedDate.getMonth() + 1;  day = issuedDate.getDate();  } 关于 Init 方法现在读者都已经很熟悉了。首先还是加载背景图像——空白的支票,然后 创建缓冲图像对象及其绘图环境。之后用 Date 类的相关方法获得程序第 24 行,用 new Date  ()方法创建的 Date 对象:issuedDate 所表示的当前系统时间中的年、月、日等信息。  Date 类的 getYear 方法,返回 Date 对象的年值,该年值是以 1900 为基准点的,也就是 说,公元 2000 年则 getYear 返回 100(2000–1900) 。所以,程序第 43 行,在 getYear 方法后 加上了 1900,就得到了 Date 对象 issuedDate 的实际年值。  Date 类的 getMonth 方法返回 Date 对象的月值,该月值是以 0~11 分别代表 1~12 月。 所以,程序第 44 行,在 getMonth 方法后加上了 1,就得到了 Date 对象 issuedDate 的实际月 值。 程序第 45 行,issuedDate.getDate 返回了 issuedDate 的日值。 之后,我们又会看见熟悉的运行流程,程序先执行 paint 方法,再执行 update 方法,最 后再将缓冲区内的内容显示在 Applet 上。 在 update 方法中,我们首先显示被加载的背景图像,空白的支票。然后,程序在第 142  行: drawLeft();
  • 114. 调用 drawLeft 方法,绘制支票左边部分的内容。程序转到第 56 行~第 85 行的 drawLeft  方法中继续运行: public void drawLeft()  {  offGraphics.setFont(new Font("宋体", Font.PLAIN, 12));  // 绘制科目 offGraphics.drawString(subjectName, 85, 80);  // 绘制对方科目 offGraphics.drawString(partnerSubjectName, 85, 108);  // 绘制出票日期 offGraphics.drawString("" + year, 77, 133);  offGraphics.drawString("" + month, 115, 133);  offGraphics.drawString("" + day, 140, 133);  // 绘制收款人 offGraphics.drawString(receiver, 70, 165);  // 绘制付款金额 offGraphics.drawString(moneyFormat.format(amount), 70, 188);  // 绘制付款用途 offGraphics.drawString(payFor, 70, 208);  // 绘制会计人员 offGraphics.drawString(accountant, 60, 255);  // 绘制主管 offGraphics.drawString(supervisor, 125, 255);  } 这段代码仅仅重复使用了 drawString 的方法,将相关内容绘制到各自对应的栏目中。这 里惟一需要介绍的地方就是程序第 75 行,用 moneyFormat 类的 format 方法,并接收 1 个参 数:即程序第 28 行所声明的双精度型变量 amount,就获得了 amount 的中国货币表现格式的 输出。如果所提供的参数是整数或者只有 1 位小数,则 moneyFormat 类的 format 方法会在相 应的数位上自动添加“0” 。  drawLeft 方法运行完毕后,程序的绘制结果如图 3.12 所示。 我们可以看到“金额”一栏中 amount 的中国货币表现格式的输出效果。 然后,程序返回 update 方法,调用 drawRight 方法,绘制支票右边部分的相关内容。 这时,程序进入第 87 行~第 134 行的 drawRight 方法。实际上,drawRight 方法也是由 一系列的 drawString 方法组成。drawRight 方法运行完成后的绘制结果如图 3.11 右边部分所 示。 在该方法中,要注意图 3.13 圆圈部分是如何绘制的。
  • 115. 图 3.12    drawLeft 方法的运行结果 图 3.13    drawRight 方法的运行结果  drawRight 方法中的第 117 行~第 133 行的代码就进行了圆圈部分的绘制工作: offGraphics.setFont(new Font("宋体", Font.PLAIN, 14));  String amountOutput = moneyFormat.format(amount);  int j = 0;  for (int i = amountOutput.length() ­ 1; i >= 0; i­­)  {  String s = "" + amountOutput.charAt(i);  // 跳过小数点 if (s.equals(".")||s.equals(","))  {  continue;  }  offGraphics.drawString(s, 710­j * 15, 127);  j++;  } 我们首先重新设定当前的绘图字体。 程序第 119 行,创建一个 String 对象 amountOutput,并调用 NumberFormat 类的 format  方法对其进行初始化,amountOutput 的内容如下: ¥6,437,192.08 然后需要将字符串“¥6,437,192.08”中的每一个数字,以及中国货币符号“¥” ,绘制 到圆圈区域内所示的相应位置上。注意,amountOutput  中的逗号“, ”及小数点“.”是不需 要绘制的。 我们的绘制思路是从字符串 amountOutput 的最后 1 个(即倒数第 1 个)字符开始读取, 将其绘制到图 3.13 圆圈区域中“分”的位置;然后读取倒数第 2 个字符,将其绘制在“角” 的位置上;如果读取字符串中当前字符的内容为逗号或小数点,则程序忽略该字符。依此类 推,直到绘制完中国货币符号“¥”为止。  drawRight  方 法 中 的 第  121  行 ~ 第  133  行 , 就 利 用 上 述 思 路 实 现 了 绘 制 字 符 串  amountOutput 的目的。 因为 String 类的 charAt 方法返回的是一个字符,所以在程序第 124 行,用了字符串的串 接方法,创建了一个 String 对象 s,而 s 的内容就是 charAt 方法返回的一个字符。 然后,程序在第 127 行,判断 s 的内容是否是逗号或小数点,如果是,则程序不对 s 做 任何处理,调用 continue 关键字,跳过剩余的部分而直接进入下一次循环。如果 s 的内容不 是逗号或小数点,则运行程序第 131 行:
  • 116. offGraphics.drawString(s, 710­j * 15, 127); 这里的 710 正好是圆圈区域中“分”的横坐标,每循环一次,j 的值会递增 1,也就是说, 下一个对象的绘制位置向左移动 15 像素。 当循环结束的时候,所有的数字以及中国货币符号“¥”就被绘制到相应的位置了。 当 drawRight 方法结束,程序返回到 update 方法中,缓冲区的图像绘制工作也就随之结 束,最后程序在第 147 行,将缓冲区的图像全部显示在 Applet 上,程序就此结束。最终的绘 制结果如图 3.11 所示。 关于如何在 Java  Applet 中加载并显示图像,并利用该功能来生成支票或者其他单据类的 图表,我们就介绍到这里,利用这种思路可以实现其他复杂的票据及单据的绘制工作。  3.3  从 HTML 文档获取参数生成动态图表 前面我们在介绍 Java Applet 中生成动态图表的时候,所需要的数据都是在该 Applet 源程 序中直接指定或者是在该程序中由实时生成的随机数而得来。此前,我们所用到的 HTML 文 档仅仅是加载 Java Applet 的.class 文件,以及设定该 Applet 在浏览器中所占的宽度和高度这 两个最基本的参数。其实除了在 Java 源程序自身可以生成所有需要的数据外,Applet 还可以 从 HTML 文档中读取数据。 当我们希望改变 Web 图表的一些相关信息的时候,以第 3.12 节的 Pie.java 为例,假如我 们更改 Web 图表的标题:“Java Web 图表设计 Applet 版——饼图”为“Java Web 图表设计  Applet & HTML 版” ,就必须修改 Pie.java 源程序第 61 行,用新的内容替换掉旧的内容,然 后再次编译,重新启动浏览器,才可以看到修改后的结果。如果要求在不修改 Java 源程序的 基础上,也同样达到修改 Java Applet 中某些内容的目的,则最简单而有效的办法是从 HTML  文档中读入所需的数据。 下面我们来学习如何通过 HTML 文档向 Java Applet 传递数据。  3.3.1  传递参数的 HTML 文档 利用 Applet 来接收从 HTML 中传递过来的参数,需要在 HTML 文档中的<APPLET>和  </APPLET>标记中,再插入用于向 Java  Applet 传递参数的标记。下面就 HTML 文档中用于  Java Applet 的标记做一个简单说明,如表 3.1 所示。 表 3.1  用于 HTML 文档的 Applet 的标记 标记名称 说 明  CODE  指定 Applet 的类名  WIDTH  指定 Applet 窗口宽度的像素尺寸  HEIGHT  指定 Applet 窗口高度的像素尺寸  指定 Applet 的 URL 地址。它可以是绝对地址 ,如 java.sun.com;也可以是相对于当前 HTML 所在 CODEBASE  目录的相对地址,如/AppletPath/Name。如果 HTML 文件不指定 CODEBASE  标志,浏览器将使用和  HTML 文件相同的 URL  虽然  Java  在  WWW  上很受欢迎,但并非所有浏览器都支持  Java。如果某种浏览器无法运行  Java  ALT  Applet,那么它在遇到 APPLET 语句时将显示 ALT 标志指定的文本信息  ALIGN  用来控制把 Applet 窗口显示在 HTML 文档窗口位置。与 HTML 语法中的<IMG>语句相同,ALIGN
  • 117. 标志指定的值可以是 TOP、MIDDLE 或 BOTTOM  把指定的名字赋予 Applet 的当前实例。当浏览器同时运行两个或多个 Applet 时,各 Applet 可通过 NAME  名字相互引用或交换信息。如果忽略 NAME 标志,Applet 的名字将对应于其类名  PARAM  指定由 Applet 读取的参数值,每个标记指定一个参数 我们重新编写了一个 PieNew.java(chap03Fig3.3)的源程序,这次绘制饼图所需要的数 据全部从 PieNew.html 文档中获得。 我们从 PieNew.html(chap03Fig3.3)文档中可以观察到,同以前的 HTML 文档相比, 增加了一些如下所示的 PARAM 标记: <PARAM NAME = "bookTitle1" VALUE = "Python">  PARAM 指定了由 Java Applet 读取的参数。每个 PARAM 标记(即参数)又包含两个属 性:NAME 和 VALUE。NAME 表示参数的名称,VALUE 表示参数的值。 一般来说,HTML 向 Java  Applet 传递的参数,可以在 Java  Applet 中通过编程的方式获 得这些参数值。通常会在 Java  Applet 的 init 方法中,调用 getParameter 方法来获得某个参数 的值。如果 getParameter 方法中指定某个参数可用,则以字符串的形式返回某个参数的值。 下面阐述如何在 Java Applet 中,获取这些参数的详细用法。  3.3.2  获取参数并生成图表 源程序 PieNew.java 和本章第 3.1.2 节介绍的 Pie.java 的结构完全相同。不同之处在于,  PieNew.java 中生成饼图的数据,没有采用 Pie.java 中的由随机数的方式产生,所&#x