/zy3

Page 1


内容提要 本书是普通高等教育“十五”国家级规划教材(高职高专教育) ,特点是:内容基础性强、图文并茂:在编写 上尽量做到通俗易懂、方便学习,注重内容的编排和例题的选择。 本书重点强调 Java 的基础知识和应用实践以及用 Java 进行多媒体、图形界面、数据库和 Web 应用的程序设 计,并注意对新、旧知识的融会与贯通,使读者能够尽快掌握 Java 的基础知识和应用。全书分为 14 章,比较全 面系统地介绍了 Java 的运行环境及开发工具、基本编程方法、Java 面向对象程序设计的基本概念、继承与多态、 异常处理、Java API、Web 服务器和 Applet 程序、用户图形界面设计、Java 多媒体程序设计、Java 数据库程序 设计,Java Servlet 程序设计以及 Java Bean 组件等内容。 本书适用于高等职业学校、高等专科学校、成人高校、示范性软件职业技术学院、本科院校及举办的二级职 业技术学院、继续教育学院以及民办高校使用,不仅可作为高等院校相关专业的教材,也可供从事 Java 开发、应 用的人员学习参考。

图书在版编目(CIP)数据 Java 编程及应用/杨武,刘贞编. —北京:高等教育 出版社,2004.1 ISBN 7-04-013903-0 Ⅰ. J…

Ⅱ. ①杨… ②刘…

计-高等学校-教材

Ⅲ. JAVA 语言-程序设

Ⅳ. TP312

中国版本图书馆 CIP 数据核字(2003)第 123561 号

出版发行

高等教育出版社

购书热线

010 - 64054588

北京市西城区德外大街 4 号

免费咨询

800-810-0598

邮政编码

100011

http://www.hep.edu.cn

010 - 82028899

新华书店北京发行所

印 字

787×1092

1/16

http://www.hep.com.cn

月第 1 版

张 14.5

月第

350 000

18.50 元

本书如有缺页、倒页、脱页等质量问题,请到所购图书销售部门联系调换。

版权所有

侵权必究

次印刷


出 版 说 明 为加强高职高专教育的教材建设工作,2000 年教育部高等教育司颁发了《关于加强高职高专 教育教材建设的若干意见》 (教高司[2000]19 号) ,提出了“力争经过 5 年的努力,编写、出版 500 本左右高职高专教育规划教材”的目标,并将高职高专教育规划教材的建设工作分为两步实 施:先用 2 至 3 年时间,在继承原有教材建设成果的基础上,充分汲取近年来高职高专院校在探 索培养高等技术应用性专门人才和教材建设方面取得的成功经验,解决好高职高专教育教材的有 无问题;然后,再用 2 至 3 年的时间,在实施《新世纪高职高专教育人才培养模式和教学内容体 系改革与建设项目计划》立项研究的基础上,推出一批特色鲜明的高质量的高职高专教育教材。 根据这一精神,有关院校和出版社从 2000 年秋季开始,积极组织编写和出版了一批“教育部高 职高专规划教材” 。这些高职高专规划教材是依据 1999 年教育部组织制定的《高职高专教育基础 课程教学基本要求》 (草案)和《高职高专教育专业人才培养目标及规格》 (草案)编写的,随着 这些教材的陆续出版,基本上解决了高职高专教材的有无问题,完成了教育部高职高专规划教材 建设工作的第一步。 2002 年教育部确定了普通高等教育“十五”国家级教材规划选题,将高职高专教育规划教材 纳入其中。“十五”国家级规划教材的建设将以“实施精品战略,抓好重点规划”为指导方针, 重点抓好公共基础课、专业基础课和专业主干课教材的建设,特别要注意选择一部分原来基础较 好的优秀教材进行修订使其逐步形成精品教材;同时还要扩大教材品种,实现教材系列配套,并 处理好教材的统一性与多样化、基本教材与辅助教材、文字教材与软件教材的关系,在此基础上 形成特色鲜明、一纲多本、优化配套的高职高专教育教材体系。 普通高等教育“十五”国家级规划教材(高职高专教育)适用于高等职业学校、高等专科学 校、成人高校及本科院校举办的二级职业技术学院、继续教育学院和民办高校使用。 教育部高等教育司 2002 年 11 月 30 日


Java 是目前 Internet 时代发展速度最快的软件开发工具之一,它采用面向对象思想和虚拟机 技术,功能强大且简单易学,特别适用于 Internet 应用开发。由于 Java 内容丰富、庞杂,因此要 完成一本涵盖 Java 基本内容和重点领域,且通俗易懂的 Java 应用程序设计教材绝不是一件容易的 事。同时,如果不有所选择和限制的话,这些内容可能需要好几本书的篇幅来介绍。在教学实践中, 我们发现国内一些较好的 Java 书籍,往往篇幅庞大,针对 Java 某一具体方面进行详细介绍,用于 学习 Java 入门和常规应用程序设计存在一定困难;另有一些著名的外文翻译书籍,但在编排上又 不太适应国内具体的教学情况。 那么,以怎样的方式编写一本能够让读者快速入门的 Java 应用程序设计教材呢?在充分考虑 IT 专业学生知识体系结构的基础上,确定了本书的基本框架和内容。本书重点强调 Java 的基础知 识和应用实践,对一些其他课程介绍的内容不做过多的重复,但注意到了对新、旧知识的融会与贯 通,使读者能够尽快掌握 Java 的基础知识和应用方法。 本书除了介绍 Java 一般性知识外,还重点介绍了用 Java 进行网络+数据库的 Web 程序设计, 这是 Web 应用的基础,也是 Java 的生命力所在。本书以简单易用的微软 SQL Server 为后台的网络 数据库服务器,以 JDBC 为数据库连接接口,用 Java Servlet 进行 Web 服务器程序设计,使读者理 解 Web 浏览器/Web 服务器/数据库服务器应用的一般模式,了解和掌握用 Java 开发 Web 应用的基本 思路和方法。 本书在编写上尽量做到通俗易懂、方便学习,注意了内容的编排和例题的选择。书中对应需 要掌握的知识都举有实例程序,实例程序紧紧围绕学习过程中可能遇到的问题,并对执行结果和 源程序进行了详细说明。每章都附有一定数量的练习题,帮助读者复习和巩固所学知识。为了便 于读者阅读本书,在编写过程中,遵循了以下几项原则: 1.每段理论知识后都有相应的详细解释,分为【语法说明】 、 【例题】 、 【运行结果】和【程序 说明】等四部分。 2.所有的例题都尽量与实际应用相结合,并且做到前后呼应,以便形成一个完整的知识理 论体系。 3.本书的所有例题均通过上机调试,运行正确无误。 4.每个章节后面都有相关习题,这些习题是作者在 Java 题库的 1000 多道习题中精选出来 的。建议读者在学习完相关章节后,选做相应习题并对上机题进行编程调试。 本书内容分为三部分,第一部分为 Java 入门,包括 1、2 章,主要介绍了 Java 的产生、历 史、特点、应用背景、分类、应用范围、运行环境和开发工具。第二部分为 Java 语言基础,包 括 3~7 章。3、4 章主要介绍了 Java 语言的基本特性及基本语法,包括 Java 语言概述、数据类型、 运算符与表达式以及流程控制语句。5~7 章介绍了面向对象的程序设计技术,包括 Java 类与对象、 继承与多态、接口与包、异常处理。通过该部分的学习,读者可以对面向对象思想和 Java 的面 向对象程序设计特点有较为扎实的了解和掌握。第三部分即 Java 应用程序开发,包括 8~14 章,


II

它是掌握好 Java 语言并进一步利用它解决实际问题的关键部分。其中:第 8 章介绍了 Java API, 它是 Java 程序设计的基础;第 9 章介绍了 Web 服务器、HTML 表单和 Java Applet 小程序的基本 知识,为进行 Web 应用提供准备;第 10、11 章介绍了 Java 用户图形界面和多媒体程序设计;第 12 章介绍了 Java 进行数据库应用的相关知识;第 13 章介绍了用 Java Servlet 进行一般 Web 服 务器程序设计和 Web 数据库应用的方法;第 14 章简单介绍了 Java 组件程序设计 JavaBean。 采用本书教学,建议总学时数为 64,其中课堂教学为 40 学时,上机实验为 24 学时。具体建 议学时数分配为: 章

建议课堂学时

建议实验学时

第1章

2

第2章

2

2

4

第3章

2

2

4

第4章

2

2

4

第5章

4

2

6

第6章

2

2

4

第7章

2

2

4

第8章

2

2

4

第9章

4

2

6

第 10 章

4

2

6

第 11 章

4

2

6

第 12 章

4

2

6

第 13 章

4

2

6

第 14 章

2

40

2

2 24

64

由于水平与时间的原因,书中不足之处在所难免,欢迎读者批评指正。 编

2003 年 9 月


第 1 章 Java 简介 ············ 1

3.7.1 一维数组 ··········· 36

1.1 Java 的发展历程 ··········· 1

3.7.2 多维数组 ··········· 38

1.2 Java 语言的特点 ··········· 2

本章小结 ··············· 40

1.3 Java 与 Internet ··········· 5

练习与思考 ·············· 40

1.4 Java 程序的分类 ··········· 6

第 4 章 流程控制 ··········· 42

1.5 Java 应用举例 ············ 8

4.1 选择(条件)控制 ········· 42

本章小结 ··············· 10

4.1.1 条件语句(if-else) ····· 42

练习与思考 ·············· 10

4.1.2 多分支语句(switch…case) ·· 43

第2章

Java 运行环境及开发工具 简介 ············· 11

4.2 循环控制 ············· 46

2.1 Java 运行系统与开发流程 ······ 11

4.2.2 while 语句 ·········· 46

2.2 Java 的安装和配置 ········· 14

4.2.3 do-while 语句 ········ 48

2.3 Java 开发工具包 ·········· 15

4.3 跳转控制 ············· 49

2.4 Java 源文件编辑环境的选择与设置 ·· 17

4.3.1 break 语句 ·········· 49

2.5 编写并运行 Application 程序 ···· 18

4.3.2 continue 语句 ········ 50

2.6 编写并运行一个 Applet 小程序 ···· 21

4.3.3 return 语句 ········· 51

本章小结 ··············· 22

4.4 其他语句 ············· 52

练习与思考 ·············· 22

本章小结 ··············· 52

第 3 章 数据类型、变量与表达式 ··· 24

练习与思考 ·············· 52

3.1 数据类型 ············· 24

4.2.1 for 语句 ··········· 46

第5章

Java 面向对象程序设计的

3.2 标识符 ·············· 26

基本概念 ··········· 53

3.3 关键字 ·············· 27

5.1 Java 面向对象基础 ········· 53

3.4 常量 ··············· 27

5.1.1 类和对象 ··········· 53

3.5 变量 ··············· 28

5.1.2 类的定义 ··········· 54

3.6 运算符与表达式 ·········· 29

5.1.3 类修饰符 ··········· 55

3.6.1 赋值运算符与类型转换 ····· 29

5.2 对象创建和引用 ·········· 57

3.6.2 算术运算符 ·········· 30

5.2.1 对象的定义 ·········· 57

3.6.3 关系运算符 ·········· 32

5.2.2 对象成员变量的引用 ······ 58

3.6.4 逻辑运算符 ·········· 33

5.2.3 对象方法的调用 ········ 58

3.6.5 位运算符 ··········· 34

5.3 成员变量 ············· 60

3.6.6

三目运算符和复杂运算符···· 34

5.3.1 成员变量的定义 ········ 60

3.6.7 运算符优先级 ········· 35

5.3.2 成员变量修饰符 ········ 61

3.7 数组 ··············· 36

5.4 方法 ··············· 62


II

5.4.1 方法声明 ··········· 62

8.3 Java.lang.Math 类 ········· 96

5.4.2 方法调用 ··········· 62

8.4 java.util 类 ··········· 98

5.4.3 方法参数的传递 ········ 63

8.4.1 日期类 Date ········· 98

5.4.4 方法修饰符 ·········· 64

8.4.2 随机数类 Random ·······100

本章小结 ··············· 65

8.5 Java 输入输出类··········101

练习与思考 ·············· 65

8.5.1 输入流与输出流 ········102

第 6 章 继承与多态 ·········· 66

8.5.2 文件输入输出 ·········102

6.1 继承 ··············· 66

8.5.3 缓冲区输入输出 ········106

6.1.1 子类创建(extends 关键字) ·· 66

本章小结 ···············108

6.1.2 this 与 super 关键字 ······ 67

练习与思考 ··············108

6.1.3 多重继承与接口 ········ 69

第 9 章 Web 服务器和 Applet 程序 ···109

6.2 接口 ··············· 70

9.1 客户机/服务器结构 ········109

6.2.1 接口定义 ··········· 70

9.1.1 Web 服务器 ··········110

6.2.2 接口实现 ··········· 70

9.1.2 统一资源定位符 ········110

6.3 多态 ··············· 72

9.1.3 超文本传输协议 HTTP······110

6.3.1 方法覆盖 ··········· 72

9.1.4 Tomcat 服务器 ········111

6.3.2 方法重载 ··········· 74

9.2 HTML 表单设计基础 ·········114

6.3.3 构造函数 ··········· 74

9.2.1 Form 语法结构 ········114

6.4 包 ················ 75

9.2.2 Form 属性 ··········114

6.4.1 包的定义 ··········· 76

9.2.3 Form 输入元素 ········117

6.4.2 包的引用 ··········· 76

9.3 Applet 的执行过程 ·········120

本章小结 ··············· 77

9.4 在页面中加入 Applet ········121

练习与思考 ·············· 77

9.5 传递参数给 Applet ······· 123

第 7 章 异常处理 ··········· 78

本章小结

············ 125

7.1 异常的概念 ············ 78

练习与思考 ··············125

7.2 异常处理机制 ··········· 80

第 10 章 图形用户界面程序设计 ····126

7.3 异常处理方式 ··········· 81

10.1 Java 图形用户界面编程基础 ····126

7.4 捕获异常 ············· 82

10.1.1 Java.awt ··········126

7.5 抛出异常 ············· 84

10.1.2 AWT 常见控制组件 ······127

7.6 自定义异常 ············ 86

10.1.3 布局管理器 ·········130

本章小结 ··············· 87

10.2 Swing 简介············133

练习与思考 ·············· 88

10.3 事件处理机制 ··········141

第 8 章 Java API 简介 ········· 89

10.3.1 KeyEvent ··········141

8.1 Java API 中的包 ·········· 89

10.3.2 TextEvent ··········143

8.2 java.lang 包 ··········· 90

10.3.3 ItemEvent ··········145

8.2.1 java.lang.System 类 ····· 90

10.3.4 MouseEvent ·········147

8.2.2 java.lang.String 类 ····· 91

本章小结 ···············150

8.2.3 java.lang.StringBuffer 类 ··· 95

练习与思考 ··············150


III

第 11 章 Java 多媒体程序设计 ···· 152

12.5.3 Connection 接口 ·······175

11.1 多媒体技术概述········· 152

12.5.4 Statement 接口 ·······176

11.2 Java 图形处理 ········· 152

12.5.5 PreparedStatement 接口····178

11.2.1 图形坐标系 ········ 153

12.5.6 ResultSet 接口 ·······181

11.2.2 Graphics 类 ········ 153

本章小结 ···············182

11.2.3 Color 类·········· 154

练习与思考 ··············183

11.2.4 文本与字体 ········ 156

第 13 章 Java Servlet 程序设计 ···184

11.3 图像处理 ··········· 157

13.1 Java Servlet 工作原理 ······184

11.3.1 加载图像 ········· 157

13.1.1 Servlet 主要功能 ······184

11.3.2 显示图像 ········· 158

13.1.2 Servlet 的执行过程······184

11.3.3 图像生成 ········· 159

13.1.3 Java Servlet 与 CGI 的比较 ··185

11.3.4 图像的简单处理 ······ 161

13.1.4 Servlet 的运行环境······185

11.4 声音播放 ··········· 162

13.1.5

11.5 动画技术 ··········· 163 本章小结 ·············· 166

Servlet 程序的两种基本 结构·············185

13.1.6

Servlet 的生命周期与基本

练习与思考 ············· 166

方法·············187

第 12 章 Java 数据库程序设计 ···· 167

13.2 Servlet API ···········189

12.1 JDBC 原理 ··········· 167

13.2.1

与 Servlet 有关类、接口的

12.1.1 JDBC 的功能 ········ 167

关系·············189

12.1.2 JDBC 的驱动程序管理器 ··· 168

13.2.2 Servlet 接口 ········190

12.1.3 ODBC 和 JDBC 的比较 ····· 168

13.2.3 GenericServlet 类 ······190

12.2 JDBC 两层结构和三层结构 ···· 168

13.2.4 HttpServlet 类 ·······191

12.3 JDBC 驱动程序类型 ······· 169

13.2.5 ServletRequest 接口 ·····193

12.3.1 JDBC-ODBC 桥 ········ 169

13.2.6 ServletResponse 接口·····193

12.3.2 Java to Native API ···· 170

13.2.7 HttpServletRequest 接口 ···194

12.3.3 Net Protocol All-Java ··· 170

13.2.8 HttpServletResponse 接口···195

12.3.4 Pure JDBC Driver ····· 170

13.3 Servlet 程序设计实例 ·······195

12.3.5

13.4 Servlet 会话···········197

建立 Book_Shop 数据库和 ODBC 数据源 ········ 171

13.5 Servlet 数据库程序设计举例 ····200

12.4 JDBC 数据库访问流程 ······ 172

本章小结 ···············205

12.4.1 创建数据库连接 ······ 173

练习与思考 ··············205

12.4.2 执行 SQL 语句 ······· 173

第 14 章 Java 组件程序设计 ······207

12.4.3

接收并处理 SQL 的返回

14.1 JavaBeans 概述··········207

结果 ············ 174

14.1.1 JavaBeans 的基本概念·····207

12.4.4 关闭创建的各个对象 ···· 174

14.1.2 JavaBeans 的特点 ······208

12.5 JDBC 应用程序接口 ······· 174

14.1.3 JavaBeans 和 EJB ·······208

12.5.1 JDBC API ········· 174

14.2 JavaBeans 的属性 ·········209

12.5.2 DriverManager 类 ······ 175

14.3 JavaBean 的事件 ·········211


IV 14.4 BDK 安装与配置 ········· 212 14.4.1 BeanBox 的启动 ······· 212 14.4.2

在 BeanBox 中应用已建好

录 14.6

在 Applet 中使用 JavaBeans 组件···············217

本章小结 ···············217

的 Bean 组件 ········ 212

练习与思考 ··············218

14.5 Bean 组件的创建 ········ 213

参考文献 ················219


1.1

第1章

Java 的发展历程

1

Java 简介

学习目标  了解 Java 的发展历程及应用  理解 Java 语言的特点  掌握 Java 程序的分类 Java 是目前 Internet 时代发展速度最快的软件开发工具之一,由于它具有面向对象、跨平 台、分布式应用、健壮性、安全性、多线程、高度可移植性等诸多优秀特点,因此得到了广泛的 使用。同时,由于 Java 与 Internet 的紧密结合,它也是当今主要的网络应用开发工具。 本章主要介绍 Java 语言的发展过程、Java 的主要特点、与 Internet 的关系以及 Java 的应 用,使读者对 Java 语言的基本特征有所了解。

1.1

Java 的发展历程 Java 的产生可以追溯到 20 世纪 90 年代,Sun 公司的 James Gosling 负责开发一个称为 Green

的项目。这个项目的主要用途是为家用电子消费产品开发一个分布式系统,以便对电冰箱、电视 机、烤面包箱等家用电器进行控制,从而在它们之间可以进行信息交流。由于该系统涉及日用家 电产品,这些产品对电子产品的安全性、可靠性、高效性等要求较高,并且涉及多处理器进行处 理的功能,因此,要求这种语言必须是简单的和面向对象的。在项目实施之初,采用 C++来写嵌 入式软件,但在实现过程中发现用 C++来实现该项目有诸多弊端,如 C++常有系统失效性程序错 误,尤其内存管理要求设计人员必须记录并管理内存资源,这都会成为安全性问题的隐患。在这 种情况下,Gosling 最后决定在 C++语言的基础上开发一种新型语言 Oak。Oak 是一种用于网络的 精巧而安全的语言,它在保留了大部分与 C++相似语法的同时,对 C++语言中存在的弊端进行了 改进,比如我们上面所提到的内存管理。 Sun 公司曾经用 Oak 对一个交互式电视项目进行投标, 但被 SGI 打败。 这时 Web 浏览器 Mosaic 启发了 Oak 项目组成员,由于 Oak 独立于平台、可靠性高、安全性好等特性,非常适合 WWW,因 此他们把 Oak 作为一种软件程序语言,编制了 HotJava 浏览器。后来,因为 Oak 这个商标已被注 册,项目组成员把 Oak 重新命名 Java。在 SunWord95 中发布出来,并得到了 Sun 公司首席执行官 Scott McNealy 的支持,从此以后,Java 进军 Internet,并随着软件的快速发展而成为程序设计 语言家族中的一颗璀璨明珠。 Java 的出现给软件业带来了巨大的冲击。1994 年下半年,Internet 的迅猛发展, WWW 的快 速增长,促进了 Java 语言研制的进展,使得它逐渐成为 Internet 上最受欢迎的开发与编程语言 之一。一些著名的计算机公司纷纷购买了 Java 语言的使用权,如 Microsoft、IBM、Netscape、 Novell、Apple、DEC、SGI 等,因此,Java 语言被美国的著名杂志 PC Magazine 评为 1995 年十


第1章

2

Java 简介

大优秀科技产品之一,随之大量出现了用 Java 编写的软件产品,受到工业界的重视与好评,认 为“Java 是 20 世纪 80 年代以来计算机界的一件大事” 。微软总裁比尔·盖茨在悄悄地观察了一 段时间后,不无感慨地说: “Java 是长时间以来最卓越的程序设计语言” ,并确定微软整个软件开 发的战略从 PC 单机时代转向以网络为中心的计算机时代。Java 的诞生对整个计算机产业产生了 深远的影响,对传统的计算模型提出了新的挑战。Sun 公司总裁 Scott McNealy 认为 Java 为 Internet 和 WWW 开辟了一个崭新的时代。Microsoft 和 IBM 两大公司都计划在 Internet 上销售 用 Java 编写的软件。Apple、HP、IBM、Microsoft、Novell、SGI、SCO、Tandem 等公司均计划将 Java 并入各自开发的操作系统,而负责开发并推广 Java 技术的 SunSoft 公司(这是 Sun 下属的 一个子公司),将通过颁发许可证的办法来允许各家公司把 Java 虚拟机和 Java 的 Applets 类库 嵌入他们开发的操作系统,这样各类开发人员就能更容易地选择多种平台来使用 Java 语言编程, 不同的用户也就可以脱离 Web 浏览器来运行 Java 应用程序,这无疑是很受广大用户欢迎的,也 为 Java 语言的应用开拓了极为广阔的前景。 Java 语法是从 C++继承过来的,它的对象模型源自 C++,因此它与 C 和 C++有着直接的联系。 现在,Java 与 C 和 C++的这种联系对编程人员变得更加便利,因为许多熟悉 C/C++语法的编程人 员,可以很容易对 Java 进行学习;从另一方面来看,熟悉 Java 的编程人员也可以轻松地学习 C/C++。这里特别说明一点,尽管 Java 受 C++的影响,但是它并不是 C++的增强版,它与 C++既不 是向上兼容,也不向下兼容。Java 并不是用来取代 C++,它是为了解决一系列特定问题而设计, 而 C++是为解决另外一系列问题而设计的,两者将并存很长一段时间,读者在今后的学习中将会 逐渐体会到这一点。

1.2

Java 语言的特点

由于 Java 是一种定位于网络应用的软件开发工具,因此,Java 的特点也是紧紧围绕这一中 心展开的。另外,Java 语言也充分利用了当代软件技术的最新成果。Java 性能优异,其主要特 点集中在简单易学、面向对象、分布式、解释性、健壮性、安全性、平台无关性、可移植性、高 性能、多线程、动态等多方面,下面简单介绍 Java 的主要特点。 1.简单易学 Java 最初是为对家用电器进行集中控制而设计的,因此它必须简单明了。Java 的简单易学 主要体现在以下三个方面:① Java 的风格类似于 C++,因而 C++程序员是非常熟悉的。从某种意 义上讲,Java 语言是 C 及 C++语言的一个变种,因此,C/C++程序员可以很快掌握 Java 编程技术。 ② Java 摒弃了 C/C++中容易引发程序错误的、不易理解和掌握的部分,如指针操作、结构类型、 运算符和内存管理。③ Java 语言对计算机的硬件环境要求很低。用 Java 编制的程序,可以在内 存很小的计算机独立运行。 2.面向对象 面向对象可以说是 Java 最重要的特性。面向对象技术的核心是,以更接近于人类思维的方 式建立解决问题的逻辑模型,它利用类和对象机制将数据及其操作封装在一起,并通过统一的接 口与外界交互,使反映现实世界实体的各个类在程序中能够独立及继承。通常我们所说的面向对 象的特性一般要包括封装性、多态性、继承性、动态性。我们所使用的面向对象的语言,


1.2

Java 语言的特点

3

很多都不是完全面向对象的,只是部分地用到了面向对象的技术。Java 是完全面向对象的,它不 支持类似 C 语言那样的面向过程的程序设计技术,并且做到了 C++所不具备的动态链接功能。Java 支持静态和动态风格的代码继承及重用。单从面向对象的特性来看,Java 类似于 SmallTalk,但 其他特性、尤其是适用于分布式计算环境的特性远远超越了 SmallTalk。编写 Java 程序的过程就 是设计、实现类,定义类的属性、行为的过程。 3.分布式 分布式计算指的是几部计算机通过网络同时协同工作。Java 在网络程序设计上极为优异,用 Java 来写网络程序就好像只是从一个文件调用或存入数据。Java 包含一个支持 HTTP、FTP 等基 于 TCP/IP 协议的子库。因此,Java 应用程序可凭借 URL 打开并访问网络上的对象,其访问方式 与访问本地文件系统几乎完全相同。在分布式环境中尤其在是 Internet 上实现动态内容无疑是 一项非常宏伟的任务,但 Java 的语言特性却使我们很容易地实现这项目标。 4.健壮性 Java 虚拟机检查程序在编译和运行时的错误,类型检查会帮助查出许多编程时产生的错误。 Java 自己操纵内存减少了内存出错的可能性。Java 还实现了真数组,避免了覆盖数据的可能, 这种功能特征大大缩短了开发 Java 应用程序的周期。 5.平台无关性 为实现平台无关性,Java 采用了半编译、半解释的策略,使用 Java 虚拟机(Java Virtual Machine)技术。Java 将它的程序编译成一种结构中立的中间文件格式,只要安装有 Java 虚拟机 的机器都能执行这种中间代码。Java 语言的平台无关性的体现如图 1.1 所示。目前,Java 运行 系统可运行在 Windows98/NT/2000、Linux、Solaris2.4(SPARC) 、Apple Macintosh 等平台上。 Java 源程序被编译成一种高层次的与具体机器无关的字节码(Byte Code) ,这种字节码在具体机 器的虚拟机上解释、运行。

图 1.1

Java 语言的平台无关性

6.安全性 Java 的安全性在 Java 设计时就得到了考虑。首先,在 Java 中,像 C/C++中的指针和内存释 放等功能被取消,从根本上避免了程序产生的非法内存操作。另外,当 Java 程序嵌入在网页内 时,Java 程序的功能受到严格限制,以确保整个系统的安全。Java 程序在执行前,要经过三次 检查,分别是 Java 本身的代码检查、对字节码(Byte Code)的检查以及程序执行系统也即 Java 解释器的检查,如图 1.2 所示。


第1章

4

图 1.2

Java 简介

Java 对安全维护的三次检查

(1)代码检查包括:检查代码段的格式,检查指针操作,检查是否试图改变一个对象的类型 等。 (2)字节码检查包括:检查代码有无引起堆栈溢出,检查所有操作代码参数类型是否都是正 确的,检查是否发生非法数据转换(如将整数转换成指针),检查访问对象操作是否合法。 (3)程序执行系统的检查:类装载(ClassLoader)通过将本机类与网络资源类的名称分开, 来保持安全性。因为调入类时总要经过检查,这样避免了特洛伊木马现象的出现。从网络上下载 的类被调进一个与源相关的私有的名字域。当一个私有类访问另一个类时,本机类(build-in) 首先被检查,然后检查相关的类,这样就避免了本机类被破坏的情况出现。 7.可移植性 同具体平台无关的特性使得 Java 应用程序可以在安装了 Java 解释器和运行环境的任何计算 机系统上运行,这成为 Java 应用软件移植的良好基础。但仅仅如此还不够。如果基本数据类型 设计依赖于具体实现,也将为程序的移植带来很大不便。例如在 Windows98 中整数为 32 bit,而 在 DEC Alpha 中整数为 64 bit。通过定义独立于平台的基本数据类型及其运算,Java 数据可以 在任何硬件平台上保持一致。 Java 的数据类型都是依据以上思想具体实现的。因为几乎目前所有 CPU 都能支持常用的数据 类型和运算, 如 8~64 位整数格式的补码运算和单/双精度浮点运算。 Java 编译器本身就是用 Java 语言编写的,Java 语言规范中也没有任何“同具体实现相关”的内容。 8.解释性 Java 解释器(虚拟机系统)能直接解释、运行目标代码指令,解释程序通常比编译程序所需 资源少。 9.高性能 运行效率低是 Java 长期以来一直被批评的缺点,因为 Java 的字节码需经 Java 虚拟机(JVM) 来转换为本地代码,所以速度较慢。但是随着 JVM 技术上的进步,加入 JIT(Just-in-time)实时 编译技术, Java 可以在运行时直接将目标代码翻译成机器指令。 目前翻译目标代码的速度与 C/C++ 几乎已经没什么区别。 10.多线程 线程有时也称小进程,是一个大进程里分出来的小的独立的进程。拥有多线程的程序可以充 分利用系统资源,改善 CPU 的闲置状态,缩短 CPU 闲置时间,对同一时间运行多个应用程序提供 了最佳处理方式。多线程带来的更大的好处是更好的交互性能和实时控制性能。Java 的多


1.3

Java 与 Internet

5

线程功能使得在一个程序里可同时执行多个小任务。在 Java 里,你可用一个单线程来调一幅图 片,同时你可以访问 HTML 里的其他信息而不必等它。 11.动态性 Java 的动态特性是其面向对象特性的扩展。它允许程序在运行过程中动态地装入所需要的 类,这是 C++无法实现的。在 C++程序设计过程中,每当在类中增加一个实例变量或一种成员函 数后,引用该类的所有子类都必须重新编译,否则将导致程序崩溃。Java 从几方面采取措施来实 现动态性。Java 编译器不是将对实例变量和成员函数的引用编译为数值引用,而是将符号引用信 息在字节码中保存后传递给解释器,再由解释器完成动态连接类,最后将符号引用信息转换为数 值偏移量。这样,一个在存储器中生成的对象不是在编译过程中决定,而是延迟到运行时由解释 器确定,因而在对类中的变量和方法进行更新时就不至于影响现存的代码。当解释、执行字节码 时,这种符号信息的查找和转换过程仅在一个新的名字出现时才进行一次,随后代码便可以全速 执行。在运行时确定引用的好处是可以使用已被更新的类,而不必担心会影响原有的代码。如果 程序连接了网络中另一系统中的某一个类,该类的所有者也可以自由地对该类进行更新,而不会 使任何引用该类的程序崩溃。

1.3

Java 与 Internet 很少有软件开发工具能够像 Java 那样对软件技术产生了如此深远的影响,现在的 Java 已不

仅是一种程序设计工具,它还提出了一种全新的“网络计算”计算模式。 传统的 Web 服务器端程序设计通常采用 CGI 的方式,也就是通用网关接口(Common Gateway Interface)的方式。CGI 程序是用 C 或 Perl 编写的服务器端可执行程序,专门用来处理客户端 浏览器传送过来的 HTTP(Hyper Text Transfer Protocol,超文本传输协议)请求中的参数,CGI 程序执行完毕后,将处理的结果传送给 Web 服务器,最后由 Web 服务器传给客户浏览器。其执行 过程如图 1.3 所示。

图 1.3

CGI 程序执行流程

CGI 虽然应用比较广,但存在效率问题。因为 CGI 程序在服务器端以进程的形式运行,每个 客户请求都要产生一个 CGI 程序响应,当客户请求数量增加时,服务器的负载将会大大加重,造 成整个 Web 服务器系统运行效率低下,这对重要系统特别是电子商务系统的影响尤为严重。 Java 的 Servlet 技术可以从一定程度上避免上述问题出现,Servlet 采用单进程多线程的方 式,即每个请求由一个 Java 线程(Thread)响应,而不是一个操作系统进程,整个 Web 服务器 的开销大为减小,系统效率得到提高。


第1章

6

Java 简介

另外由于 Java 跨平台的特点,使 Java 程序可以不加修改就在多种操作系统平台上运行,这 是因为 Java 编译器产生的字节码(Byte Code)是独立于具体平台的。从网络上下载的 Java 程 序都可在支持 Java 的环境中直接运行。例如可以下载包含了 Java 程序的 Web 网页,只要客户端 的浏览器如 Microsoft Internet Explorer 支持 Java,它们就可在浏览器中解释、执行。 浏览器下载和执行包含了 Java 程序的 Web 网页的过程,如图 1.4 所示。

图 1.4

Java 程序在浏览器上的执行过程

(1)客户机浏览器与服务器进行连接,要求下载整个 Web 网页(包括 Java 字节码文件); (2)客户机浏览器内的 Java 解释器解释、执行下载的字节码; (3)在浏览器上显示 Java 程序的执行结果。

1.4

Java 程序的分类 Java 能开发可独立解释执行的本地应用程序(Application)、包含在 Web 网页 HTML 文件中

依靠浏览器解释并执行的小程序(Applet)和后端 Web 服务器程序(Servlets)。虽然三者的结 构不同,但基本语法都一样,所以能彼此沟通。 在 Java 的开发工具包 JDK(Java Developer’s Kit)环境中能方便地编译、运行和调试前二 种程序,第三种程序需要配合对 Servlets 支持的开发工具一起使用。 1.Java 应用程序 Application Java 应用程序 Application 是在命令行环境下执行的 Java 程序,它可以独立运行在 Java 虚 拟机上。所谓独立,是相对于 Applet 对浏览器的依赖而言,实际上它的执行离不开 JDK 中的编 译器 javac 和解释器 java。 Application 先由 Java 编译器编译成为独立于平台的字节码(Byte Code) ,然后由 Java 解 释器 java 来运行。例如一个源程序叫 app1.java,用 Javac 编译器编译后将会生成 app1.class, 而在命令行状态下输入 java app1 就可以运行此程序。 2.Java 小程序 Applet Java 小程序 Applet,它是一种嵌入在网页文件中的 Java 字节码程序。由于在网络上传输, Applet 程序往往很短小,其源文件后缀仍为.java,编译后后缀也是.class。


1.4

Java 程序的分类

7

由于 Applet 没有自己的程序入口,不能直接在 java 虚拟机上运行,故执行过程与 Application 稍有不同。其具体过程是,首先由 Java 编译器将 Java 程序编译为字节码文件,并 把这个字节码文件嵌入到 Web 页面中;客户端将字节码文件下载后,由支持 Java 的 Web 浏览器 来解释、执行。 Applet 是提高网页生动性和交互性的有用的手段,因为 Java 提供了大量控制页面外观和处理 交互事件的便利方式。图 1.5 是一个简单的图表显示,它是 JDK 工具中自带的一个 Applet 实例。

图 1.5

一个简单的图表显示实例

3.Java 服务器端程序 Servlet Java 服务器端程序 Servlet 在服务器端执行,提供各种处理功能。Servlet 是一种采用 Java 技术来实现 CGI 功能的一种技术。虽然 Servlets 和 CGI 都是在 Web 服务器上运行,并生成 Web 页 面,但与传统的 CGI 或其他 CGI 类似替代技术相比,Java Servlets 具有效率更高,使用更方便, 功能更强大,更小巧也更便宜等特点,例如数据查询及交互操作响应等。图 1.6 是对请求信息的响 应。


第1章

8

图 1.6

1.5

Java 简介

对请求的响应

Java 应用举例 Java 是一种与平台无关的语言,因此用 Java 开发的网络应用系统可以在各种平台上运行,

大大提高了开发效率,减少了重复劳动。而且,Java 集成的网络功能有利于开发网络应用系统。 Web 浏览是现在 Internet 上的主要使用方式。HTML 文档能很容易地显示文本和各种图片, 还能提供超文本链接。WWW 浏览器技术只限于文本和图像。如果你想播放一种声音或运行一个演 示程序,你不得不下载那个文件并用本机上的能运行或解释那个文件格式的程序来播放它。而 Java 程序和它的浏览器 HotJava,提供了可让你的浏览器运行程序的方法。你能从你的浏览器里 直接播放声音,甚至你还能播放页面里的动画。Java 还能告诉你的浏览器应该怎样处理新的类型 文件。当我们在 2 400b/s 的线缆上传输视频图像时,HotJava 将能显示这些视频。 1.商业网站 Java 在商业网站的应用也相当广泛。就以网络调查机构 Forrester Research 在 2000 年全球 的 eBusiness Tech Ranking(电子商务科技评选)中选出的在 eCommerce platform(电子商务 平台)中的第一名 Intershop 为例,该网站以其 Java 程序的灵活、弹性而受到肯定。据报道, 在欧洲有 70%以上的电信公司,在美国也有 40%以上的电信公司是它的客户。全球有超过 200 家电信公司与大型 ISP 选择与这家公司合作,图 1.7 是该网站的首页。


1.4

Java 程序的分类

图 1.7

9

商业网站应用

2.网页丰富多彩的视觉效果 当 Java 在 1996 年首次被放在互联网上时,最吸引人的就是其 Applet 所展现出的卓 越丰姿。经过这几年的快速发展,Java 在网页上所呈现的更是多彩多姿的另一番风貌, 像 水 中 倒 影 , 燃 烧 特 效 等 。 我 们 可 看 其 SDK 所 附 的 Demo 程 序 , 它 也 可 以 被 单 独 下 载 (http://www.java.sun.com\Demo\jfc\Java2d\Java2Demo.html),如图 1.8 所示。

图 1.8

3.网络教育培训

视觉效果应用


第1章

10

Java 简介

在教育训练上,Java 也把我们的视野带到另一个境界。不只是在国外,在中国也有丰硕的果 实呈现,如图 1.9 所示即为 Java 中文站(http://www.java-cn.com)的首页。

图 1.9

教育网站的应用

4.网络联机游戏 Java 在网络游戏上的表现更是可圈可点。小巧而精悍的 applet 游戏程序通过网络下载下来, 速度极快,立即可以享用。以网站 http://www.playjavagames.com/为例,就是一个纯粹以 Java 写的游戏程序网站。 5.家用消费电子产品 Java 平台针对小型消费电子产品推出 Micro Edition。像在 Palm PDA 及无线通信上,Java 已开始展现它的能量。市面上已有书籍介绍如何在 Palm OS 上写 Java 程序。前面已经介绍过, Java 最先是为针对小型消费电子设备而设计的,如今通过 ME 的释放,终于能够开始展现其长处 了。

本 章 小 结 本章介绍了 Java 的产生背景、特性、影响等方面的基础知识,并对 Java 程序的分类及应用 作了介绍,阐述了 Java 的发展与流行与 Internet 的密不可分的关系。通过本章的学习,读者应 能对 Java 的特点及用途有一个总体的、较为明确的认识和了解。

练习与思考 1.1

解释下列名词: (1)WWW;(2)HTTP;(3)HTML

1.2

Java 语言与 Internet 有什么关系?


1.5

Java 应用举例

1.3

Java 语言如何达到跨平台的特性?

1.4

列出 5 个 Java 语言的特性,并举例说明。

1.5

试说明 Java 是如何进行安全维护检查的?

1.6

试说明 Java 虚拟机的概念及作用。

1.7

试举出几个 Java 的应用实例,并说明它们是 Java 哪类程序的应用。

11


1.1

第2章

Java 的发展历程

11

Java 运行环境及开发工具简介

学习目标  理解 Java 的运行系统、Java 的平台、程序开发流程  掌握 Java 开发工具包中的工具字节码编译器、字节码解释器、调试器、分解器和文档生 成器  掌握 Java 的安装和配置、Java 源文件编辑环境的选择与设置 Sun MicroSystem 公司在发布 Java 语言时提供了简单的开发环境及开发工具,它主要包含了 一些常用的 JDK 工具、执行时需要用到的函数库、与 C 连接时所需的文件及一些 Java 应用程序 范例等。在 Java 不断发展壮大的同时,也出现了一些比较流行的 Java 集成开发环境,如 Visual J++、JBulider 等。 本章首先介绍 Java 程序的运行环境及平台和 Java 程序开发流程,然后介绍 JDK 工具包的安 装及环境配置、JDK 工具包中的主要工具,最后对 Application 应用程序和 Applet 小程序进行简 单的举例说明,给出编写 Java 程序的完整过程。

2.1

Java 运行系统与开发流程 Java 字节代码在执行时需要有 Java 运行系统的支持,虽然 Java 运行系统可以建立在不同的

平台上,但是为了做到 Java 程序的可移植性,对 Java 运行系统的功能要求应该统一,即可以看 作是一个与具体的软、硬件环境无关的系统。 1.Java 运行系统 Java 运行系统一般都包括类装配器、字节码校验器、解释器、代码生成器和运行支持库等几 个组成部分。其基本结构如图 2.1 所示。

图 2.1

Java 运行系统的基本结构

Java 字节码的运行过程可以分为装入字节码、校验字节码和执行字节码三步。


第2章

12

Java 运行环境及开发工具简介

(1)装入字节码 装入字节码的工作由类装配器来完成。类装配器负责装入程序运行所需要的代码,包括程序 代码中调用到的所有的类。这些类都被安放在自己的名字空间中,不会对其他类所在的空间造成 影响。在装入类之后,运行系统就可以确定程序执行的内存布局,即建立符号引用和具体内存地 址之间的查找表。 (2)校验字节码 由字节码校验器对字节码进行校验,以确保所装入的字节码不违背 Java 语言的安全性规则。 (3)执行字节码 在经过了装入和校验两个步骤之后,字节码就可以提交运行了。在 Java 语言中,字节码的 执行有编译执行和解释执行两种方式。通常采用解释执行方式,即由 Java 解释器通过每次翻译 并执行一小段代码来完成字节码程序的所有操作。但是,如果对程序的运行速度有较高的要求, 就应该采用编译方式,即由代码生成器先将整个程序转化为机器指令,而后再执行。 2.Java 平台 由于 Java 字节代码在执行时需要有 Java 运行系统的支持,因此,虽然 Java 运行系统是建 立在不同的平台上,但是为了做到 Java 程序的可移植性,对 Java 运行系统的功能要求应该统一, 即该系统可以看作是一个与具体的软、硬件环境无关的“Java 平台”。 Java 平台是 Java 语言编程和操作环境的基础,它由两个部分组成:Java 虚拟机(JVM)和 Java 应用程序接口(API)。 Java 虚拟机是 Java 平台的核心部件,它可以看作是一个虚拟的、能运行 Java 字节码的操作 平台。 Java 应用程序接口是供 Java 应用程序使用的 JVM 的标准接口,它又可以分为 Java 基本 API 和 Java 标准扩展 API 两大部分。Java 标准扩展 API 包含了面向不同领域和范畴的 API 接口规范, 它们随着 Java 语言的发展而不断完善,并逐渐成为 Java 基本 API 的组成部分。 3.Java 程序开发流程 通过第一章可知 Java 程序分为本地应用程序(Application)、包含在 Web 网页 HTML 文件 中依靠浏览器解释、执行的小程序(Applet)和后端 Web 服务器程序(Servlets)三类。这一节 主要介绍 Java 应用程序(Application)和小程序(Applet)的开发流程。Java 应用程序 (Application)的开发流程,如图 2.2 所示。 首先,通过文本编辑器对 Java 源代码进行编辑,这里可以采用 Windows 中自带的写字板, 也可以采用 Word 来进行编辑,当然也可以使用 DOS 下的文本编辑器,这些可依据使用者的爱好。 本书中采用的是常用的程序编辑器 TextPad。 其次,对 Java 源代码进行编译生成 Java 类文件。若生成过程中有错,则返回第一步,对源 文件进行编辑修改,而后编译,直到生成正确的类文件。 最后,解释执行 Java 类文件。若运行结果错误,需要重新返回第一步,进行编辑、生成类 文件,直至运行结果正确。若运行结果正确,则可以在此基础上利用类文件生成 C/C++头文件, 利用源文件生成 HTML 说明文档(此步骤为可选步骤)。 对于 Java 小程序 Applet 的开发可参见流程图 2.3。与 Application 一样,需要先编辑 Java 源代码文件,并编译成 Java 类文件,在执行时需要一个 HTML 主文件,也就是需要用编辑器编写


2.1

Java 运行系统与开发流程

13

一个调用 Java 类文件的 HTML 文件。在执行时,我们可以采用浏览器或 AppletViewer 打开 HTML 文件,从而调用 Java 程序在浏览器中执行。

图 2.2

Java 应用程序(Application)的开发流程

图 2.3

Java Applet 开发流程

表 2.1 给出了开发 Java Application 和 Applet 程序的综合比较。从表格中可以看出,二者 主要在程序的编写要求和运行方式有区别,其编译过程、文件格式等类似。


第2章

14

表 2.1 比 较 内 容

Java 运行环境及开发工具简介

Application 和 Applet 程序编写比较 Application

Applet 有 init(),start(),stop(),dest roy()

1. 程序格式特征

有 main()

2. 存储文件格式

.java 文件

.java 文件

3. 编译(compile)程序代码

使用 javac.exe

使用 javac.exe

4 产生文件格式

.class 文件

5. 运行程序

2.2

.class 文件

使用 java.exe 即可直接运行

(1) 先制作一个嵌入此.class的html 文件,方法如下: <Applet Code=“xxx.class”…> </Applet> (2) 再使用appletveiwer.exe或浏览 器运行此html文件

Java 的安装和配置 Sun Microsystems 公司在推出 Java 语言的同时,推出了一套 Java 的开发工具包(JDK) 。JDK

提供了 Java 的编译器和解释器等开发程序时所必需的工具,还有可供 Java 程序设计人员使用的 一些标准包及程序范例。 要编写 Java 程序必须先获得并安装 JDK,可以从 http://java.sun.com/j2se 站点免费下载 JDK(图 2.4)。JDK 开发工具包有 UNIX、Window98/Windows NT、Mac 等多种版本。

图 2.4

Java 资源网站

JDK 在不同系统下的安装过程不尽相同,下面以 Windows 2000 为例,介绍 JDK 的安装和配置


2.1

Java 运行系统与开发流程

15

过程。 1.JDK 的安装 首先,从 Sun 公司的 Web 站点上下载 Windows 版本的 JDK,得到一个自解压的程序。 然后,执行该程序,选择安装的目标盘和安装目录,即可把 JDK 解压并安装到硬盘上。 安装后,在 JDK 的主目录中包含以下文件和子目录(默认安装在 C 盘上): C:\j2sdk1.4.0\COPYRIGHT:JDK 版本说明。 C:\j2sdk1.4.0\index.html:JDK 的 HTML 说明文档。 C:\j2sdk1.4.0\README:JDK 基本内容及功能说明。 C:\j2sdk1.4.0\src.zip:JDK 程序源代码压缩文件。 C:\j2sdk1.4.0\bin\:包含了一些常用的 JDK 工具。如 javac、java、jdb、javah、javap 等。 C:\j2sdk1.4.0\lib\:包含了一些在执行 JDK 可扫行文件时所要用到的函数库。注意其中 的 tools.jar 和 dt.jar 不要解压,Java 编译器和 Java 解释器会直接使用这个压缩过的函数库文 件。 C:\j2sdk1.4.0\include\:包含了一些与 C 连接时所需的文件。 C:\j2sdk1.4.0\demo\:包含了许多 Sun 公司提供的 Java 小应用程序(applet)范例。 2.配置 安装完成以后,还必须修改环境变量 PATH 和 CLASSPATH,以将其提供的开发工具所在的路径 包含进去。在 Windows 2000 操作系统下,可在控制面板→系统→系统属性→高级→环境变量中, 对系统变量 PATH 进行如下的修改: 在 PATH 行之后添加:C:\j2sdk1.4.0\bin 在 CLASSPATH 行之后添加:.;C:\j2sdk1.4.0\lib\tools.jar; C:\j2sdk1.4.0\lib\dt.jar 添加完毕后,选择“应用”并退出控制面板。 在 Windows 95/98 操作系统环境下,可以按下列方式对 autoexec.bat 中的环境变量进行修 改: PATH=%PATH%; C:\j2sdk1.4.0\bin SET CLASSPATH=.;C:\j2sdk1.4.0\lib\tools.jar;C:\j2sdk1.4.0\lib\dt.jar 然后重新启动计算机。 最后,在 MS-DOS 命令提示符下输入下列命令: C:\>java –version 如果出现下列提示信息,就说明已经成功地安装并配置好了 Java 2 SDK 1.4.0,也就是说我 们可以编译和运行 Java 程序了。 java version "1.4.0" Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-b92) Java HotSpot(TM) Client VM (build 1.4.0-b92, mixed mode)


第2章

16

2.3

Java 运行环境及开发工具简介

Java 开发工具包 Java 不仅提供了一个丰富的语言和运行环境,而且还提供了一个免费的 Java 开发工具包

(Java Developers Kits,简称 JDK) ,其目的是为程序开发者提供编写、测试、执行程序的一套 完备的工具体系,Java 的编译器 javac、解释器 java 以及 applet 浏览器 appletviewer 实际上 都包含在 JDK 中。总体说来,JDK 由 javac(字节码编译器) 、java(字节码解释器) 、appletviewer (applet 浏览器) 、jdb(Java 调试器) 、javap(Java 分解器) 、javadoc(Java 文档生成器) 、 javah(C 语言头文件生成器)七个部分组成。下面将对这些工具进行简要的介绍。 1.字节码编译器(javac) 这里所说的 javac 就是 java 程序的编译器,是 JDK 包中的一个文件,文件名是 javac.exe, 在 JDK 包的 bin 目录下。编译器 javac 负责对后缀为.java 的源文件进行翻译转换,生成后缀 为.class 的可执行的类文件(即字节码 Byte Code)。 使用编译器的命令格式为:javac [选项] 源文件名 Java 的源代码文件必须以.java 结尾。在源代码文件中定义的每个类在 javac 中是独立的编 译单元,在编译时它们相互之间不会 产生影响。javac 将每个类编译后的字节码存放在 classname.class 的文件中,如果 java 程序中包含了多个类,则 java 将生成多个 class 文件, 每个 class 文件中只存入一个类的字节码,其文件名与类名相同。 2.字节码解释器(java) 同样,解释器也是 JDK 包中 bin 目录下的一个文件,文件名是 java.exe。Java 解释器是面 向 Java 程序的一个独立运行系统,它以一种稳定、高性能的方式运行那些独立于平台的 Java 字 节码。在字节码下载和执行过程中,解释器负责维护它的完整性、正确性和安全性,解释器 java 执行编译后产生的后缀为.class 的类文件。 使用解释器的命令行格式为:java [选项] 类名 [参数] 被解释执行的类中必须有且仅有一个有效的 main 方法,相当于 C 和 C++中的 main 函数,上 述命令行中所带的参数将在执行时传递给相应类的 main 方法。 3.Applet 浏览器(appletviewer) appletviewer 展示 Web 页面中包含的 Applet,通常用于 Applet 开发过程中的测试。 使用 appletviewer 的格式为:appletviewer [选项] URL 其中 URL 是包含被显示 Applet 的 HTML 文件的统一资源定位符(Universal Resource Locator)。当 HTML 文件位于本地机上时,只需写出文件名。 4.调试器(jdb) 调试器 jdb 也可执行字节码,同时提供置断点中断执行和显示变量值等功能,是查找程序错 误的有效工具。Java 调试器 jdb 用于监督检测 Java 程序的执行。启动 jdb 的方法有两种:常用 的方法是用 jdb 解释执行被调试的类,格式与由 Java 解释器执行相类似;第二种方法是把 jdb 附加到一个已运行的 Java 解释器上,该解释器必须是带-debug 项启动的。 5.分解器(javap) 分解器 javap 将字节码分解还原成源文件。对于从 Web 上获取的无法得到源文件的类,分解


2.3

Java 开发工具包

17

器是十分有用的。Java 的分解器 javap 将经编译生成的字节码分解,给出指定类中成员变量和方 法的有关信息。使用格式为: javap [选项] 类名 其中类名无.class 后缀,它的用途在于:当用户从网上下载了某可执行的类文件而又无法取 得源码时,对类文件加以分解,能迅速了解该类的组成结构,以便更好的理解和使用。 6.文档生成器(javadoc) 文档生成器 javadoc 接受源文件(后缀为.java)输入,然后自动生成一个 HTML 文件,内容 包括 Java 源文件中的类、变量、方法、接口、异常(exception)等。javadoc 的使用格式有下 面三种: javadoc [选项] 包名 javadoc [选项] 类名 javadoc [选项] 源文件名 7.C 语言头文件生成器(javah) C 语言头文件生成器 javah 用以从 Java 的字节码上生成 C 语言的头文件和源文件,这些文件 用来在 Java 的类中融入 C 语言的原生方法。使用格式为: javah [选项] 类名

2.4

Java 源文件编辑环境的选择与设置

编写程序的源代码可使用一般的文字日处理软件,如记事本、wordPad,但它们往往缺乏编写程 序的辅助功能,因此在 Java 源文件的编辑中,我们可以选用一些整合性开发软件。这里介绍一种简 单 易 用 、 适 合

java

源 程 序 开 发 的 编 辑 程 序

TextPad , 可 从

http://www.textpad.com/download/index.html 下载。下面简单介绍 TextPad 与 Java 编程环境的整 合。 安装后的主界面如图 2.5 所示,其中左上方为文件名显示区,右边区域是文本编辑区,左下 角是对一些特殊字符进行录入。


18

第2章

Java 运行环境及开发工具简介

图 2.5

TextPad 的主界面

我们选择菜单<配置>→<参数选择>,就会出现如图 2.6 的参数选择界面,选择<工具>选项, 然后选择<添加>就弹出一个菜单,选择<JDK>(注:只有安装配置 JDK 后,才有此选项) ,就会出 现图 2.7。在工具子项中就会显示相关的开发 JDK 程序所需的命令集。我们在编辑完一具体程序 之后,就可以通过 TextPad 运行相关的 Application 或 Applet 程序。当然也可以用来编辑其他 的程序如:c,asp,php,prel,lisp,html,pascal 等,TextPad 支持对其语法的检测和编译。

图 2.6

TextPad 的参数选择(1)


2.4

Java 源文件编辑环境的选择与设置

图 2.7

2.5

19

TextPad 的参数选择(2)

编写并运行 Application 程序 现以最简单的 Java 应用程序例子, 来了解它的结构。 例 2.1 给出了一个简单 Java Application

输出程序。我们先在 TextPad 的编辑区域中输入例 2.1,录入完毕后,选择“工具”菜单的“编 译 Java”项,即可编译生成 myfirst.class。选择“工具”菜单的“运行 Java 应用程序”项, 则运行编译后的字节代码。 注意:Java 程序严格区分大小写! 【例 2.1】myfirst.java 1: 2: 3: 4: 5:

class myfirst{ public static void main (String args[ ]) { System.out.println ("This is my first Java Application"); } }

【运行结果】 如图 2.8 所示。

图 2.8

【例 2.1】的运行结果

如果在 TextPad 中对运行结果设置为<捕获输出>(图 2.9),则将会出现如图 2.10 的运行


第2章

20

Java 运行环境及开发工具简介

结果。

图 2.9

图 2.10

设置捕获输出参数

【例 2.1】中在设置捕获输出后的运行显示结果

【程序说明】 (1)第 1 行用关键字 class 来定义名为 myfirst 的新类,myfirst 是新类的名称,必须是一 个有效的标识符,有效标识符的定义请参见程序设计基础章节。类的说明包括数据说明和成员函 数说明,都放在类后边的大括号里面。一般类定义如下: class 类名称{数据定义;函数定义;}。 (2)第 2 行中的 public 是一个表示访问权限的关键字,表示此成员函数是公有的,可以被 其他类直接调用,包括 java 解释器。相对应的关键字有 private 和 protected、friend。private 表示只能被本类访问,protected 表示只能被子类访问,friend 是缺省的访问权限,表示能被本 包(package)中任意类访问,而对于其他包中的类是不可被访问的。static 表示 main()成员函 数在 myfirst 类的所有对象实例中是惟一的,因此如果本程序生成另一个 myfirst 类对象,调用 的 main()函数将是同一个函数。void 表示 main()函数没有返回值,如果有返回类型值,则可加 上 Integer 或 Boolean 等。对于有返回值的函数,其函数实体的最后应加上 return 语句。main() 函数是应用程序运行的入口点,因此编写应用程序必须有且仅有一个 main()函数。


2.5

编写并运行 Application 程序

21

(3)第 3 行是 main()函数里的功能语句,System.out.println 是 java.lang.System 包里 out 类的 println()成员函数,是标准输出语句。 对于应用程序参数的传递,Java 与 C/C++很类似。在 C 语言里,通过在命令行输入参数,C 程序可由 main()函数读入这些参数,Java 程序也一样。例 2.2 给出了一个带参数的 Java 输入与 输出程序。其功能是输入参数,并显示出来。 【例 2.2】Myclass.java 1: class Myclass{ 2: public static void main (String args[ ]){ 3: int arc = args.length; 4: if (arc>0){ 5: for (int i =0;i<arc;i++) 6: System.out.println(args[i]); 7: } 8: else{ 9: System.out.println ("Application have no args!"); 10: } 11: } 12: }

【运行结果】 如图 2.11 所示。

图 2.11

【例 2.2】未输入参数时的输出结果

【程序说明】 (1)当运行 java Myclass 时,由于无参数输入,输出结果如图 2.11 所示。 (2)当运行 java Myclass abc cde efg 时,由于有 3 个参数输入,输出结果如图 2.12 所示。

图 2.12

【例 2.2】输入参数为 abc cde efg 时的输出结果

注意:Java 的输入参数传递方式与 C/C++类似,即类名后第一个参数放在 args[0]里,第二


第2章

22

Java 运行环境及开发工具简介

个参数放在 args[1]里,以此类推。

2.6

编写并运行一个 Applet 小程序

编写、编译 Applet 小程序和 Application 程序是类似的,选择“工具”菜单的“运行 Java Applet”项就可启动 appletviewer 来运行 Applet 程序。 例 2.3 给出了一个 Applet 的实例:HelloWorld.java,其源程序代码如下: 【例 2.3】HelloWorld.java 1: 2: 3: 4: 5: 6: 7:

import java.applet .*; import java.awt.*; public class HelloWorld extends Applet{ public void paint(Graphics g){ g.drawString ("你好,Java 世界!",2,20); } }

实 际 使 用 时 应 将 这 个 生 成 的 .class 文 件 放 入 到 网 页 中 去 。 建 立 一 个 网 页 文 件 Helloworld .html,如下: 1: 2: 3: 4: 5: 6: 7: 8:

<html> <head><title>我的第一个 JavaApplet 程序</title></head> </body> <p> <applet code=HelloWorld.class width=300 height=200> </applet> </body> </html>

【运行结果】 如图 2.13 所示。

图 2.13

用 Appletviewer 运行 Applet 小程序

双击 Helloworld .html 文件,在浏览器打开,就可以看到这个刚刚编译好的文件的结果了, 如图 2.14 所示。


2.6

图 2.14

编写并运行一个 Applet 小程序

23

以浏览器方式输出 Applet 小程序

本 章 小 结 本章主要介绍了 Java 的运行环境与开发工具,并对 Java 的两种主要类型的应用程序 Application 和 Applet 的开发过程进行了简单的说明,同时介绍了一种常用的程序编辑器 TextPad,用它可以提高编写 Java 程序的效率。最后分别对 Application 应用程序和 Applet 小 程序进行了举例说明。 通过本章的学习,读者会对 Java 程序从整体上有一个概括的了解和认识,为下面的学习奠定基础。

练习与思考 2.1

Java 源程序的扩展名是什么?经过编译后产生的文件的扩展名又是什么?

2.2

用来运行 Java 应用程序的命令是什么?用来运行 Java Applet 程序的命令是什么?

2.3

在 Windows 2000 下如何配置 JDK 开发环境和如何检测 JDK 开发环境配置是否正确?

2.4

试说明 Java Application 与 Java Applet 的开发流程,并指出二者不同之处。

2.5 编写一个 Java Application 程序,编译并运行这个程序,在 MS-DOS 窗口里输出“欢迎进入 Java 奇妙 世界!”的信息。 2.6 编写一个 Java Applet 程序,编译并运行这个程序,要求能在浏览器上显示出“欢迎进入 Java 奇妙世 界”的信息。


第3章

24

第3章

数据类型、变量与表达式

数据类型、变量与表达式

学习目标  熟练掌握数据类型、关键字、运算符与表达式  熟练掌握一维数组、多维数组 Java 要求程序中的每一个变量和表达式都应有确定的类型,这样有助于编译时期错误的检 查。Java 语言的数据类型可以分为基本类型和构造类型,基本类型主要包括整型、浮点型、字符 型和布尔型,构造类型主要包括数组。 本章介绍了 Java 语言的基本数据类型,对标识符、关键字、变量、常量,运算符和表达式 的基本概念和用法进行了讨论,最后对构造类型——数组的定义及用法进行了说明和举例。

3.1

数据类型

Java 的数据类型如图 3.1 所示。Java 不支持 C/C++中的指针类型、结构体类型和共用体类型。 本节我们主要介绍几种简单数据类型。

图 3.1

Java 的数据类型

1.整型数据 整型数据有 int、long、byte、short 四种。 int 类型,最常使用的一种整数类型,它以 4 个字节表示整型数。 long 类型,它以 8 个字节表示整型数。当遇到超出 int 类表示范围的整数时需要使用 long 类型的整数。声明为 long 型的整数值最后需要加上“L”或“l”。 byte 类型,以一个字节来表示整型数,它有八进制、十进制和十六进制 3 种表示方法。由于 byte 类型表示的数据范围很小,使用时应避免造成溢出。 short 类型,以 2 个字节表示整型数。 以上四种数据类型的声明例子如下:


3.1

byte b=0x18; short s=O16; int i=7; long k=326L;

数 据 类 型

25

//指定变量 b 为 byte 型十六进制数 //指定变量 s 为 short 型八进制数 //指定变量 i 为 int 型十进制数 //指定变量 k 为 long 型十进制数

2.浮点型(实型)数据 实型有单精度类型 float 和双精度类型 double 两种。 双精度类型 double 比单精度类型 float 具有更高的精度和更大的表示范围。声明为 float 型的数值最后需要加上“F”或“f” ,否则默 认会被视为 double 型。 float 类型是 32 位的单精度浮点数,表示的数的范围为 3.4e-038~3.4e+038; double 类型是 64 位的双精度浮点数,表示的数的范围为 1.7e-308~1.7e+308。 以上两种数据类型的声明例子如下: float p=3.141592653F; //指定变量 p 为 float 型 double d=2.71828; //指定变量 d 为 double 型 与 C/C++不同,Java 中没有无符号型整数,并且 Java 明确规定了整型和浮点型数据所占的 内存字节数,这样就保证了程序的安全性、健壮性和平台无关性。 3.字符型 字符型 char 用 2 个字节表示一个字符,其整数值范围为 0~65535。Java 的字符型数据采用 国际标准 Unicode 编码,它所包含的信息量远远多于 8 位的 ASCII 码。 使用 char 类型定义字符时,必须使用一对单引号将字符括起来。 字符型变量的声明例子如下: char c='a'; //指定变量 c 为 char 型,且赋初值为'a' 与 C/C++不同,Java 中的字符型数据不能用作整数,因为 Java 不提供无符号整数类型。但 是同样可以把它当作整型数据来操作。例如: int three=3; char one='1'; char four=(char)(three+one); //four='4' 字符型变量 one 被转化为整数,进行相加,最后把结果又转化为字符型。 4.布尔型 布尔型数据只有两个值,true 和 false。布尔型数据常用于程序的比较和流程控制。 布尔型变量的声明例子如下: boolean b=true; //定义 b 为布尔型变量,且初值为 true 例 3.1 中用到了前面提到的几种数据类型,并通过屏幕输出它们的结果。 【例 3.1】SimpleTypes.java 1: 2: 3: 4: 5: 6:

public class SimpleTypes{ public static void main(String args[]){ byte b=0x55; short s=0x55ff; int i=1000000; long l=0xfffL; char c='c';


第3章

26 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: }

数据类型、变量与表达式

float f=0.23F; double d=0.7E-3; boolean bool=true; System.out.println("b="+b); System.out.println("s="+s); System.out.println("i="+i); System.out.println("l="+l); System.out.println("c="+c); System.out.println("f="+f); System.out.println("d="+d); System.out.println("bool="+bool); }

【运行结果】 如图 3.2 所示。

图 3.2

3.2

【例 3.1】的运行结果

标识符

标识符用于对变量、类和方法的命名。对变量、类等作适当的命名,可以大大提高程序的可 读性。标识符是除关键字以外的任意一串以合法字母、下划线(_)或美元符号($)开头的由合 法字母、数字、下划线(_)和美元符号($)组成的字符串。 其具体规定如下: (1)Java 中标识符必须使用字母、下划线(_)或美元符号($)开头。 (2)Java 中合法字母除了大小写的英文字母外,还包括所有位置在 00C0 以前的 Unicode 字 符集中的字符。 (3)同 C/C++中规定一样,关键字不能用作标识符。关键字是 Java 语言本身使用的标识符, 每个关键字均有其特殊意义,设计者只能按指定的意义使用,并不能重新定义,因此用户的标识 符不能使用关键字(具体的关键字请参见下节)。 (4)保留字是为以后 Java 语言扩展使用的,保留字不是关键字,也不能用作标识符。如 true,


3.4

27

false,null 等。 (5)Java 是区别大小写的语言,MyClass 和 myclass 分别代表不同的标识符,在声明类时要 特别注意。 (6)一般标识符用小写字母开头。同一个标识符中,中间的单词以大写字母开头,或用下划 线进行分隔。 (7)使用的标识符在一定的程度上反映它所表示的变量、常量、对象或类的意义。 这些是合法的标识符:openOn,day_24_hours,x,value。 这些是不合法的标识符:24_hours,day-24-hours,Boolean,value#。

3.3

关键字 关键字都由小写字母组成,每一个关键字都有特定的含义和严格的使用规定,关键字不能当

作程序中的标识符,否则出错。 Java 中预定义的关键字如表 3.1 所示。 表 3.1 abstract

boolean

break

byte

case

catch

char

class

const

continue

default

do

double

else

extends

final

finally

float

for

goto

if

implements

import

int

interface

long

native

new

package

public

return

short

static

this

throw

throws

void

volatile

while

private super transient

3.4

Java 的关键字表

protected switch try

synchronized virtual

instanceof

常量

所谓常量就是在程序整个运行过程中值都不会改变的量。Java 中常用的常量有整型常量、实 型常量、字符常量、字符串常量。 1.整型常量 整型常量用 4 个字节表示,为 int 型。长整型常量用 8 个字节表示,为 long 型。长整型常 量要在数字后加 L 或 l,如 123L 表示一个长整数。与 C/C++相同,Java 的整常数有三种形式: 十进制整数,如 123,-456,0。 八进制整数,以 0 开头,如 0123 表示十进制数 83,- 011 表示十进制数-9。 十六进制整数,以 0x 或 0X 开头,如 0x123 表示十进制数 291,- 0X12 表示十进制数-18。 2.实型常量 实型常量用 8 个字节表示,为 double 型。一般浮点常量用 4 个字节表示,双精度浮点常量用


第3章

28

数据类型、变量与表达式

8 个字节表示,均为 float 型,浮点常量需要在数字后加 f 或 F,如 12.3F。 Java 的实型常量有两种表示形式: 十进制数形式,由数字和小数点组成,且必须有小数点,如 0.123,.123,123.,123.0 科学计数法形式。如:123e3 或 123E3,其中 e 或 E 之前必须有数,且 e 或 E 后面的指数必 须为整数。 3.字符常量 字符常量是用单引号引起来的一个字符,如'a','A'。另外,Java 也提供转义字符,以反斜 杠(\)开头,将其后的字符转变为另外的含义,表 3.2 列出了 Java 中的转义字符。 表 3.2

Java 中的转义字符

转 义 字 符

含 义 描 述

\ddd

1 到 3 位 8 进制数据所表示的字符(ddd)

\uxxxx

1 到 4 位 16 进制数所表示的字符(xxxx)

\'

单引号字符

\\

反斜杠字符

\r

回车

\n

换行

\f

走纸换页

\t

横向跳格

4.字符串常量 Java 的字符串常量是用双引号括起来的一串字符,如"This is a string."。需要注意的是, Java 中的字符串常量是作为 String 类的一个对象来进行处理它,而不仅仅是一个数据。有关类 String,将在后面的章节中讲述。 与 C/C++不同,Java 中不能通过#define 命令把一个标识符定义为常量,而是用关键字 final 来实现,如 final double PI=3.14159。

3.5

变量

与常量正好相反,变量的值在程序整个运行过程可以发生变化。变量在使用前必须要声明, 同时在变量声明时可根据需要决定是否赋初值。 下面是变量声明的例子: int a,b,c; double d1 =0.32; char mychar,Mychar=’Y’; 几点说明:

//多个变量间用逗号隔开 //给 d1 赋初值 // mychar 和 Mychar 是两个不同的变量。

(1)变量名必须是一个合法的标识符。合法的变量名如 myName、value_1、dollar$等,非法


3.6

运算符与表达式

29

的变量名如 2mail、room#、class(关键字)等。变量名应具有一定的含义,以增加程序的可读 性。 (2)Java 的变量名区分大小写。 (3)变量有一定的生命周期和作用域。变量的作用域是指可访问该变量的那一段代码,声明 一个变量的同时也就指明了变量的作用域。按作用域来分,变量具体可分为局部变量、类变量、 方法参数、异常处理参数等几大类。局部变量在方法或方法的一块代码中声明,它的作用域为它 所在的代码块(整个方法或方法中的某块代码)。类变量在类中声明,而不是在类的某个方法中 声明,它的作用域是整个类。方法参数传递给方法,它的作用域就是这个方法。异常处理参数传 递给异常处理代码,它的作用域就是异常处理部分。在一个确定的域中,变量名应该是惟一的。 通常,一个域用大括号{}来划定。

3.6

运算符与表达式

运算符就是用来进行运算的符号。运算符通常必须与操作数一起使用组成 Java 的表达式 才有意义。Java 的运算符有多种,分为赋值运算符、算术运算符、关系运算符和逻辑运算符 等。运算符按操作数的个数又可为单目运算符(如++、-)、双目运算符(如+、>)和三目运 算符。

3.6.1

赋值运算符与类型转换

1.赋值运算符 赋值运算符为“=” ,用于将运算符右边表达式的值赋给左边的变量。赋值运算符的使用格式 如下: 变量=表达式; 下面是一些简单的赋值运算例子: j=1; k=j; l=i+j*4; 2.类型转换 赋值运算要求赋值号左右两边的数据类型一致,如果遇到赋值号两边的数据类型不一致的情 况,就需要把赋值号右边的数据类型转换为赋值号左边的数据类型。通常情况下,如果类型不一 致,需要在程序中进行强制的类型转换。Java 在某些特定情况下,也能自动地进行类型的转换。 强制类型转换的格式是: (数据类型) 变量名或者表达式 但由于 Java 中不同类型所占字节数不同,转换后可能丢失信息,所以当较长的数据类型转 换为较短的数据类型时必须使用强制类型转换,用户需自己确保赋值号右边的数在类型转换后不 会丢失信息。下面是一些具体的例子: int a; long b;


第3章

30

float c; a=(int)b; b=(long)a; a=(int)c;

数据类型、变量与表达式

//将 b 转换成 int 类型并赋值给 a。有可能丢失信息。 //将 a 转换成 long 类型并赋值给 b。不会丢失信息。 //将 c 转换成 int 类型并赋值给 a。有可能丢失信息。

例 3.2 演示了几种数据类型间如何由 Java 进行自动转换(即隐式转换)。 【例 3.2】Promotion.java 1: 2: 3: 4: 5: 6: 7: 8: 9:

public class Promotion{ public static void main(String args[]){ byte b=10; char c='a'; int i=90; long l=555L; float f=3.5f; double d=1.234; float f1=f*b; //float*byte→float

10:

int i1=c+i;

//char+int→int

11:

long l1=l+i1;

//long+int→ling

12: 13: 14: 15: 16: 17: 18: }

double d1=f1/i1-d; //float/int→float,float-double→double System.out.println("f1="+f1); System.out.println("i1="+i1); System.out.println("l1="+l1); System.out.println("d1="+d1); }

【运行结果】 如图 3.3 所示。

图 3.3

3.6.2

【例 3.2】的结果

算术运算符

算术运算符用于完成算术运算,有双目运算符、单目运算符两种。 1.双目算术运算符 双目算术运算符如表 3.3 所示。


3.6 表 3.3 运

运算符与表达式

31

双目算术运算符 例

+

a+b

求 a 与 b 相加的和

-

a-b

求 a 与 b 相减的差

*

a*b

求 a 与 b 相乘的积

/

a/b

求 a 除以 b 的商

取余数

a%b

求 a 除以 b 所得的余数

Java 对加运算符进行了扩展,使它能够进行字符串的连接,如“abc”+“de” ,得到串"abcde"。 与 C、C++不同,对取模运算符%来说,其操作数可以为浮点数,如 37.2%10=7.2。 2.单目算术运算符 单目算术运算符如表 3.4 所示。 表 3.4 运

单目算术运算符 例

功能等价于

++

自增

a++或++a

a=a+1

--

自减

a- -或- -a

a=a-1

-

求相反数

-a

a=-a

需要注意的是“++”和“- -”在变量左右使用的区别。例如 i++与++i 是不一样的,i++发生 在 i 使用之后,使 i 的值加 1。++i 发生在使用 i 之前,使 i 的值加 1。i--与--i 类似。 比如: int x=2; int y=(++x)*3; 运行结果是 x=3;y=9。 又比如: int x=2; int y=(x++)*3; 运行结果是 x=3,y=6。 这是为什么呢?因为第一个例子中,是 x 已经等于 3 后再算 y,而后一个例子中,则是先用 x=2 算出 y 后,再算 x,因为++符号在后面,这就是++x 和 x++的区别了。下面的例子说明了算术 运算符的使用。 【例 3.3】ArithmaticOp.java 1: 2: 3: 4:

public class ArithmaticOp{ public static void main( String args[] ){ int a=5+4; //a=9 int b=a*2; //b=18


第3章

32 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: }

数据类型、变量与表达式

int c=b/4; //c=4 int d=b-c; //d=14 int e=-d; //e=-14 int f=e%4; //f=-2 double g=18.4; double h=g%4; //h=2.4 int i=3; int j=i++; //i=4,j=3 int k=++i; //i=5,k=5 System.out.print("a = "+a); System.out.println(" b = "+b); System.out.print("c = "+c); System.out.println(" d = "+d); System.out.print("e = "+e); System.out.println(" f = "+f); System.out.print("g = "+g); System.out.println(" h = "+h); System.out.print("i = "+i); System.out.println(" j = "+j); System.out.println("k = "+k); }

【运行结果】 如图 3.4 所示。

图 3.4

3.6.3

【例 3.3】的结果

关系运算符

关系运算符用来比较两个值,比较结果是布尔类型的值 true 或 false。关系运算符都是二元 运算符,如表 3.5 所示。 表 3.5 运

关系运算符号表 例

==

a==b

a 等于 b

!=

a!=b

a 不等于 b


3.6

运算符与表达式

33 续表

>

a>b

a 大于 b

<

a<b

a 小于 b

>=

a>=b

a 大于等于 b

<=

a<=b

a 小于等于 b

关系运算的结果是布尔值,只有“真”和“假”两种取值,例如: int x=5,y=7; boolean b=(x==y); 则 b 的值是 false。 Java 中,任何数据类型的数据(包括基本类型和组合类型)都可以通过= =或!=来比较是否 相等(这与 C/C++不同) 。关系运算的结果返回 true 或 false,而不是 C/C++中的 1 或 0。关系运 算符常与逻辑运算符一起使用,作为流控制语句的判断条件,如 if(a>b&&b==c)。

3.6.4

逻辑运算符

布尔逻辑运算符用来进行布尔逻辑运算,逻辑运算是针对布尔型数据进行的运算,运算的 结果仍然是布尔型量。常用的运算符如表 3.6。 表 3.6 运

常用的逻辑运算符 例

&

非简洁与

x&y

x,y 都真时结果才为真

|

非简洁或

x|y

x,y 都假时结果才为假

!

取反

!x

x 真时为假,x 假时为真

^

异或

x^y

x,y 同真假时结果为假

&&

简洁与

x&&y

x,y 都真时结果才为真

||

简洁或

x||y

x,y 都假时结果才为假

对于布尔逻辑运算,先求出运算符左边的表达式的值,对“或”运算如果为 true,则整个表 达式的结果为 true,不必对运算符右边的表达式再进行运算。同样,对“与”运算,如果左边表 达式的值为 false,则不必对右边的表达式求值,整个表达式的结果为 false。 例 3.4 演示了关系运算符和布尔逻辑运算符的使用。 【例 3.4】RelationAndConditionOp.java 1: 2: 3: 4:

public class RelationAndConditionOp{ public static void main(String args[]){ int a=25,b=3; boolean d=a<b; //d=false


第3章

34 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:

数据类型、变量与表达式

System.out.println("a<b = "+d); int e=3; if(e!=0 && a/e>5) System.out.println("a/e = "+a/e); int f=0; if(f!=0 && a/f>5) System.out.println("a/f = "+a/f); else System.out.println("f = "+f); } }

【运行结果】 如图 3.5 所示。

图 3.5

【例 3.4】的运行结果

注意:上例中,第二个 if 语句在运行时不会发生除 0 溢出的错,因为 e!=0 为 false,所以 就不需要对 a/e 进行运算。

3.6.5

位运算符

位运算是对操作数以二进制位为单位进行的操作和运算,位运算的操作数和结果都是整形变 量。Java 中提供了如表 3.7 所示的位运算符。 表 3.7 运

常见的位运算符 例

~

位反

~x

将 x 按比特位取反

>>

右移

x>>a

x 各比特位右移 a 位

<<

左移

x<<a

x 各比特位左移 a 位

>>>

不带符号的右移

x>>>a

x 各比特位右移 a 位,左边的空位填零

位运算符中,除“~”以外,其余均为二元运算符。操作数只能为整型和字符型数据。

3.6.6

三目运算符和复杂运算符

Java 除了一般的运算符外,还有三目运算符和复杂运算符。 Java 中的三元运算符与 C 语言中三元运算符完全相同,使用格式为:


3.6

运算符与表达式

35

x?y:z; 运算时先计算 x 的值,若 x 为真,整个表达式的结果为 y 的值;若 x 为假,则整个表达式的 值为表达式 z 的值。 例如: int x=5,y=8,z=2; int k=x<3?y:z; //因为 x<3,所以 k=2; int j=x>0?x:-x; //y 的值始终为 x 的绝对值。 复杂运算符是在先进行某种运算后,再把运算结果赋给变量。表 3.8 列举出了复杂赋值运算 符及相关运算例子。 表 3.8 运

3.6.7

复杂运算符

+=

x+=a

x=x+a

- =

x-=a

x=x-a

*=

x*=a

x=x*a

/=

x/=a

x=x/a

%=

x%=a

x=x%a

&=

x&=a

x=x&a

|=

x|=a

x=x|a

^=

x^=a

x=x^a

<<=

x<<=a

x=x<<a

>>=

x>>=a

x=x>>a

<<<=

x<<<=a

x=x<<<a

运算符优先级

运算符的优先级决定了表达式中不同运算执行的先后顺序,而运算符的结合性决定了并列的 相同运算的先后执行顺序。表 3.9 列出了 Java 语言中主要运算符的优先级和结合性。 表 3.9 优

运算符优先级

1

()

小括号

左/右

2

- !++ - -强制类型转换符

单目运算

3

* / %

算术乘除运算

4

+ -

算术加减运算


第3章

36

数据类型、变量与表达式 续表

3.7

5

< <= > >=

大小关系运算

6

== !=

相等关系运算

7

&

非简洁与

8

|

非简洁或

9

&&

简洁与

10

||

简洁或

11

?:

三目条件运算

12

= 运算符=

简单,复杂赋值运算

数组

数组是数据的有序集合,它作为构造数据类型广泛的应用在程序设计中。数组中的每个元素 具有相同的数组名,数组名和下标用来惟一地确定数组中的元素。Java 的数组在使用前,必须要 声明数据类型和分配存储空间。

3.7.1

一维数组

Java 的数组从本质上说是一维的,多维数组都是在一维数组的基础上扩展而成。 1.一维数组的声明和存储空间的分配 一维数组是程序设计中一种常用的数据结构,它的声明方式有两种: 数组的数据类型 数组的数据类型[] 【语法说明】

数组名[]; 数组名;

数据类型可以是 Java 中任意的数据类型,数组名为一个合法的标识符,[]指明该变量是一 个数组类型变量。例如: int MyArray[]; //声明了一个整型数组 MyArray。 Java 在数组的定义中并不为数组元素分配内存,这与 C/C++不同。因此在声明时[]中可不用 指出数组中元素的个数,即数组长度。数组在分配内存空间前,是不能访问的。 2.一维数组的存储空间的分配 数组的内存空间分配要用到运算符 new,其格式如下: 数组名=new 数组的数据类型[数组长度]; MyArray[]=new int[3]; //为有 3 个元素的整型数组 MyArray 分配内存空间。 通常,数组的声明和存储空间的分配这两部分可以合在一起,格式如下: 数组的数据类型 例如:

数组名=new 数组的数据类型[数组长度];


3.7

37

int MyArray=new int[3]; //声明有 3 个元素的整型数组 MyArray 并分配空间 3.一维数组元素的引用 声明了一个数组,并用运算符 new 为它分配了内存空间后,就可以引用数组中的每一个元素 了。数组元素的引用方式为: 数组名[数组下标] 【语法说明】 数组下标可以为整型常数或表达式,如 a[3],b[i](i 为整型) ,c[6*I]等。下标从 0 开始, 一直到数组的长度减 1。对于上面例子中的 MyArray 数数组来说,它有 3 个元素,分别为 MyArray[0]、MyArray[1]、MyArray[2]。注意,没有 MyArray[3]。 另外,与 C/C++中不同,Java 对数组元素要进行越界检查以保证安全性。同时,每个数组都 有一个属性 length,用来指明数组的长度。例如:MyArray.length 指明数组 MyArray 的长度。 例 3.5 演示了如何运用数组,其功能是对数组中的每个元素赋值,然后按逆序输出。 【例 3.5】ArrayTest.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:

public class ArrayTest{ public static void main(String args[]){ int i; int a[]=new int[5]; for(i=0;i<5;i++) a[i]=i; for(i=a.length-1;i>=0;i--) System.out.println("a["+i+"]="+a[i]); } }

【运行结果】 如图 3.6 所示。

图 3.6

【例 3.5】的运行结果

4.一维数组的初始化 对数组元素可以按照上述的例子进行赋值。也可以在定义数组的同时进行初始化。例如: int a[]={1,2,3,4,5}; 用逗号“,”分隔数组的各个元素,系统自动为数组分配一定的空间。与 C 中不同,这时 Java 不要求数组为静态(static)。


第3章

38

数据类型、变量与表达式

例 3.6 为一个一维数组程序举例: Fibonacci 数列, Fibonacci 数列的定义为: F1=F2=1, Fn=Fn- +Fn1

2

(n>=3)。 【例 3.6】Fibonacci.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:

public class Fibonacci{ public static void main(String args[]){ int i; int f[]=new int[10]; f[0]=f[1]=1; for(i=2;i<10;i++) f[i]=f[i-1]+f[i-2]; for(i=1;i<=10;i++) System.out.println("F["+i+"]="+f[i-1]); } }

【运行结果】 如图 3.7 所示。

图 3.7

3.7.2

【例 3.6】的运行结果

多维数组

在 Java 中,多维数组被看作数组的数组。例如二维数组为一个特殊的一维数组,其中每个 元素又是一个一维数组。 下面我们主要以二维数组为例来进行说明,多维数组类似。 1.二维数组的声明和存储空间的分配 二维数组的声明方式为: 数组的数据类型 数组名[][]; 与一维数组一样,这时的数组元素也没有分配内存空间,同样要使用运算符 new 来分配内存 空间后,然后才可以访问每个元素。


3.7

39

对二维数组来说,分配内存空间两种方法。一种是直接分配;另一种是在一维数组基础上, 为第二维分配空间,这两种方式功能是相同的。在 C/C++中,必须一次性指明每一维的长度。 如可以为数组 a 直接分配内存空间: int a[][]=new int[2][3]; 也可以分别为数组 a 分配内存空间: int a[][]=new int[2][]; a[0]=new int[3]; a[1]=new int[3]; 2.二维数组元素的引用 对二维数组中每个元素,引用方式为: 数组名[数组下标 1][数组下标 2] 数组下标 1、数组下标 2 可为整型常数或表达式,如 a[2][3]等。同样,每一维的下标都从 0 开始。 3.二维数组的初始化 二维数组的初始化有两种方式,一种方式是直接对每个元素进行赋值;另外还可以在定义数 组的同时进行初始化。如: int a[][]={{2,3},{1,5},{3,4}}; 定义了一个 3×2 的数组,并对每个元素赋值。例 3.7 给出了二维数组应用实例:矩阵相乘。两 个矩阵 A(m×n)、B(n×l)相乘得到 C(m×l),每个元素 Cij=aik*bkj(i=1…m,n=1…n)。 【例 3.7】MatrixMultiply.java 1: public class MatrixMultiply{ 2: public static void main(String args[]){ 3: int i,j,k; 4: int a[][]=new int[2][3]; 5: int b[][]={{1,5,2,8},{5,9,10,-3},{2,7,-5,-18}}; 6: int c[][]=new int[2][4]; 7: for(i=0;i<2;i++) 8: for(j=0;j<3;j++) 9: a[i][j]=(i+1)*(j+2); 10: for(i=0;i<2;i++){ 11: for(j=0;j<4;j++){ 12: c[i][j]=0; 13: for(k=0;k<3;k++) 14: c[i][j]+=a[i][k]*b[k][j]; 15: } 16: } 17: System.out.println("***矩阵 A***"); 18: for(i=0;i<2;i++){ 19: for(j=0;j<3;j++) 20: System.out.print(a[i][j]+" "); 21: System.out.println(); 22: } 23: System.out.println("***矩阵 B***");


第3章

40 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: }

数据类型、变量与表达式

for(i=0;i<3;i++){ for(j=0;j<4;j++) System.out.print(b[i][j]+" "); System.out.println(); } System.out.println("***矩阵 C***"); for(i=0;i<2;i++){ for(j=0;j<4;j++) System.out.print(c[i][j]+" "); System.out.println(); } }

【运行结果】 如图 3.8 所示。

图 3.8

【例 3.7】的运行结果

本 章 小 结 本章介绍了 Java 语言的基础知识,包括 Java 语言的基本数据类型、变量和常量的定义和使 用、表达式和各种运算符的使用方法,最后介绍了数组的运用。在程序设计语言中,数据类型是 对变量的一种抽象描述,也是确保程序正确性的重要手段,编译器根据变量的类型来检查程序中 的变量是否执行了合法的操作。

练习与思考 3.1

下面哪一个是 boolean 变量的合法值? (1)"false";(2)no;(3)yes。

3.2

Java 语言的基本数据类型有哪些?给出 short 类型所能表达的最大、最小值。


练习与思考 3.3

Java 语言的字符型有什么特点?

3.4

什么是强制类型转换?在进行强制类型转换时有哪些注意事项?

3.5

试写出下面表达式的运算结果。设 a=8,b=12,c=16 且

41

x=a+b++; y=a+- -c; z=b+c++; 3.6

设 a=5,b= -3,写出下面表达式的运算结果 (1)-a%b++;(2)(a>=1&&a<=12?a:b);(3)a++%b- -。

3.7 编写一个 Java Application 程序,将给定的摄氏温度转换为华氏温度。摄氏温度转换为华氏温度的公 式为:fahrenheit=1.8*celsius+32。 3.8

试写出能产生如图 3.9 所示的数组的 Java 程序。

图 3.9

要求产生的二维数组示意图


第4章

42

第4章

流 程 控 制

流 程 控 制

学习目标  熟练掌握条件语句(if-else) 、多分支语句(switch…case) ;循环语句(for)、循环语 句(while)、循环语句(do-while);break 语句、continue 语句、return 语句  熟练掌握异常处理语句、注释语句 同其他程序设计语言一样,Java 也是通过流程控制语句来实现程序流程的执行,完成一定的 语句组成的程序块。语句块可以是一条语句(如 c=a+b;) ,也可以由用大括号{}括起的若干条语 句组成。 Java 中的流程控制语句主要有分支语句(if-else,break,switch)、循环语句(while, do-while,for) 、跳转语句(return,continue) 、异常处理语句(try-catch-finally,throw, throws)。

4.1

选择(条件)控制

条件分支语句提供了一种根据条件判断的结果进行程序流程控制的方法,使得程序的执行可 以跳过一些语句不执行,而转去执行特定的语句。

4.1.1

条件语句(if-else)

if-else 语句根据判定条件的真假来执行两种操作中的一种,它的格式为: if (条件表达式) 语句块 1; [else 语句块 2;] 【语法说明】 (1)若条件表达式的值为 true,则程序执行语句块 1,否则执行语句块 2。 (2)每个单一的语句后都必须有分号。 (3)语句块 1、2 可以为复合语句,这时要用大括号{}括起。建议对单一的语句也用大括号 括起,这样程序的可读性强,可以在其中填加新的语句,有利于程序的扩充。{}外面不加分号。 (4)else 子句是任选的。 (5)else 子句不能单独作为语句使用,它必须和 if 配对使用。else 总是与离它最近的 if 配对。可以通过使用大括号{}来改变配对关系。if-else 语句可以嵌套使用。 下面的例子中,嵌套使用了 if-else 语句。


4.1

选择(条件)控制

43

if(testscore>=90){ grade='A'; }else if(testscore >= 80){ grade='B'; }else if(testscore>=70){ grade='C'; }else if(testscore>=60){ grade='D'; }else{ grade='F'; } 【例 4.1】比较两个数的大小,并按从小到大的次序输出。 // CompareTwo.java 1: public class CompareTwo{ 2: public static void main(String args[]){ 3: double d1=23.4; 4: double d2=35.1; 5: if(d2>=d1) 6: System.out.println(d2+" >= "+d1); 7: else 8: System.out.println(d1+" >= "+d2); 9: } 10: } 【运行结果】 如图 4.1 所示。

图 4.1

4.1.2

【例 4.1】的结果

多分支语句(switch…case)

switch 语句又称为多分枝语句,它根据表达式的值来选择执行多个操作中的一个,它的一般 格式如下: switch (表达式){ case 判断值 1: 语句块 1; break; case 判断值 2:


第4章

44

流 程 控 制

语句块 2; break; ………… case 判断值 n: 语句块 n; break; [default: 语句块 n+1; ] } 【语法说明】 (1)switch 语句首先计算表达式,根据表达式的返回值与每个 case 子句中的判断值进行比 较,如果相等,则执行该 case 子句后的语句块。 (2)表达式的类型必须是 byte、short、int 型和字符型,case 子句中的判断值必须为常量, 类型与表达式的类型相同,所有 case 子句中的判断值的值是不同的。 (3)default 子句是可选的。当表达式的值与任一 case 子句中的判断值都不相等时,程序执 行 default 后面的语句块。如果表达式的值与任一 case 子句中的判断值都不相等且没有 default 子句时,则程序不作任何操作,而是直接跳出 switch 语句。 (4)break 语句用来在执行完一个 case 分支后,跳出 switch 语句,即终止 switch 语句的执 行。因为 case 子句只是起到一个标号的作用,用来查找匹配的入口值,从此处开始执行,对后 面的 case 子句不再进行匹配,而是直接执行其后的语句序列,因此应该在每个 case 分支后,用 break 来终止后面的 case 分支语句的执行。 在一些特殊情况下,多个不同的 case 值要执行一组相同的操作,这时可以不用 break。 (5)switch 语句的功能可以用 if-else 来实现,但在某些情况下,使用 switch 语句更简练, 可读性更强,且程序的执行效率更高。 例 4.2 的功能是根据考试成绩的等级打印出百分制分数段。从该例中可以看到 break 语句的 作用。 【例 4.2】GradeLevel.java 1: public class GradeLevel{ 2: public static void main( String args[] ){ 3: System.out.println("** 正确用法 **"); 4: char grade='C'; //正确用法 5: switch( grade ){ 6: case 'A': 7: System.out.println(grade+" is 85~100"); 8: break; 9: case 'B': 10: System.out.println(grade+" is 70~84"); 11: break; 12: case 'C': 13: System.out.println(grade+" is 60~69"); 14: break; 15: case 'D':


4.1 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: }

选择(条件)控制

System.out.println(grade+" is <60"); break; default: System.out.println("输入错误"); } System.out.println("** 不含 break 的 case 语句 **"); grade='A'; //一个不含 break 的 case 语句 switch( grade ){ case 'A': System.out.println(grade+" is 85~100"); case 'B': System.out.println(grade+" is 70~84"); case 'C': System.out.println(grade+" is 60~69"); case 'D': System.out.println(grade+" is <60"); default: System.out.println("输入错误"); } System.out.println("** 部分含 break 的 case 语句 **"); grade='B';//部分含 break 的 case 语句 switch( grade ){ case 'A': case 'B': case 'C': System.out.println(grade+" is >=60"); break; case 'D': System.out.println(grade+" is <60"); break; default: System.out.println("输入错误"); } }

【运行结果】 如图 4.2 所示。

45


第4章

46

图 4.2

4.2

流 程 控 制

【例 4.2】的运行结果

循环控制 循环语句的作用是反复执行一段代码,直到满足终止循环的条件为止。 一个循环语句一般应包括以下四部分内容: 循环初始化部分(initialization),用来设置循环的一些初始条件,使计数器清零等。 循环体部分(body),这是反复循环的一段代码,可以是单一的一条语句,也可以是复合语

句。 迭代部分(iteration) ,这是在当前循环结束后,下一次循环开始前时执行的语句,常常用 来使循环计数器加 1 或减 1。 终止部分(termination) ,通常是一个条件表达式,每一次循环要对该表达式求值,以验证 是否满足循环终止条件。 Java 提供的循环语句有 for 语句、while 语句、do-while 语句等。

4.2.1

for 语句

for 语句是最常使用的一种循环语句,它的一般格式为: for (初始化表达式; 终止表达式; 迭代表达式){ 循环体 } 【语法说明】 (1)for 语句执行时,首先执行初始化操作,然后判断终止条件是否满足,如果满足,则执 行循环体中的语句,最后执行迭代部分。完成一次循环后,重新判断终止条件。 (2)可以在 for 语句的初始化部分声明一个变量,它的作用域为整个 for 语句。 (3)for 语句通常用来执行循环次数确定的情况(如对数组元素进行操作) ,也可以根据循环 结束条件执行循环次数不确定的情况。 (4)在初始化部分和迭代部分可以使用逗号语句,来进行多个操作。逗号语句是用逗号分隔 的语句序列。例如:


4.1

选择(条件)控制

47

for(i=0,j=10;i<j;i++,j--){ …… } (5)初始化、终止以及迭代部分都可以为空语句(但分号不能省),三者均为空的时候,相当 于一个无限循环。

4.2.2

while 语句

while 语句实现“当”型循环,即先判断再决定是否执行循环体。while 语句的一般格式为: while (条件表达式){ 循环体 } 【语法说明】 (1)当条件表达式的值为 true 时,循环执行大括号中语句。 (2)while 语句首先计算终止条件,当条件满足时,才去执行循环体中的语句,这是“当” 型循环的特点。 例 4.3 介绍了一个双重循环的例子,它是一个 Applet 小程序,因此,此例包含一个 Applet1.html 文件和一个 Applet1.java 文件。程序的功能是在屏幕上输出一个三角形。 【例 4.3】Applet1.html 和 Applet1.java。 Applet1.html 1: 2: 3: 4: 5: 6: 7: 8: 9:

<HTML> <HEAD> <TITLE>Hello Web Applet!</TITLE> </HEAD> <BODY> <applet name="Applet1" code="Applet1.class" width="500" height="300" align="middle"> </applet> </BODY> </HTML>

Applet1.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:

【运行结果】 如图 4.3 所示。

import java.applet.*; import java.awt.*; public class Applet1 extends Applet{ public void paint(Graphics g){ int i=1,j,n=5; while(i<=n){ for(j=1;j<=i*2-1;j++) g.drawString("* ",(50+10*j+10*(n-i)),i*20); i++; } } }


第4章

48

图 4.3

流 程 控 制

【例 4.3】的运行结果

【语法说明】 程序的第 1 重循环即 while 循环,用于控制构成三角形的行数。第 2 重 for 循环用于控制每 一行*的个数,它的循环次数要随着第 1 重的循环变量 i 变化。当 i=1 时,它要循环 i*2-1 遍, 也就是 1 遍,当 i=5 时,它要循环 5*2-1=9 遍。 另外程序打印出来的三角形高度不是固定的,可以通过修改 n 的值而使得三角形具有不同的 高度。

4.2.3

do-while 语句

do-while 语句的判断过程正好与 while 语句相反,它是先执行循环体再判断决定是否执行下 一次循环。do-while 语句一般格式为: do{ 循环体 }while (条件表达式); 【语法说明】 (1)do-while 语句首先执行循环体,然后计算终止条件,若结果为 true,则循环执行大括 号中的语句,直到布尔表达式的结果为 false。 (2)与 while 语句不同的是,do-while 语句的循环体至少执行一次,这是它的特点。 例 4.4 分别用 while、do-while 和 for 语句实现累加求和,可以比较这三种循环语句的使 用方式。 【例 4.4】Sum.java 1: 2: 3: 4: 5: 6: 7: 8:

public class Sum{ public static void main(String args[]){ System.out.println("** while 语句 **"); int n=10,sum=0; while(n>0){ sum+=n; n--; }


4.2 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:

循 环 控 制

49

System.out.println("sum is "+sum); System.out.println("** do_while 语句 **"); n=0; sum=0; do{ sum+=n; n++; }while(n<=10); System.out.println("sum is "+sum); System.out.println("** for 语句 **"); sum=0; for( int i=1; i<=10; i++){ sum+=i; } System.out.println("sum is "+sum); } }

【运行结果】 如图 4.4 所示。

图 4.4

4.3

【例 4.4】的运行结果

跳转控制 在程序执行时,跳转控制语句可使程序的执行流程转向。Java 的跳转控制语句有 break、

continue 和 return 三种。Java 中没有 goto 语句来实现任意的跳转,因为 goto 语句破坏程序的 可读性,而且影响编译的优化。

4.3.1

break 语句

在 switch 语中,break 用来终止 switch 语句的执行,程序流程转向 switch 语句后的第一条 语句。同样在循环语句 for、while、do-while 中,break 立即终止正在执行循环,程序流程转向 循环语句后的第一条语句。 break 语句分为不带标号的 break 语句和带标号的 break 语句。 不带标号的 break 的语法格式为:


第4章

50

流 程 控 制

break; 带标号的 break 的语法格式为: 标号: 语句块 break 标号; 【语法说明】 (1)不带标号的 break 语句终止当前语句的执行,程序流程转向到从当前语句的下一条语句 开始执行。 (2)带标号的 break 语句从当前正在执行的程序中跳出,程序流程转向到标号后语句开始 执行。 例如在下例中,执行了 break b 后,程序流程转向到用标号 b 标志的、用大括号{}括起来的 一段代码。 {…… b: {…… //标号 b {…… break b; //转移到标号 b 后的语句块执行 …… } …… } …… } 但是从上例看出,Java 用 break 可实现 goto 语句所特有的一些优点。如果 break 后所指定 的标号不是一个代码块的标号,而是一个语句,则这时 break 完全实现 goto 的功能,不过应该 避免这种方式的使用。

4.3.2

continue 语句

continue 语句只能用在循环语句中,它中断本次循环,立即开始下次循环。 continue 语句也分为不带标号的 continue 语句和带标号的 continue 语句。 不带标号的格式为: continue; 带标号的格式为: continue 标号; 【语法说明】 (1)不带标号的 continue 语句使控制无条件地转移到循环语句的条件判定部分,即首先结 束本次循环,跳过循环体中下面尚未执行的语句,接着进行终止条件的判断,以决定是否继续循 环。对于 for 语句,在进行终止条件的判断前,还要先执行迭代语句。 (2)带标号的 continue 语句通常用在多重循环中,标号应放在外循环的开始处。 例如在下例中,当满足 j>i 的条件时,程序执行 continue outer 后,立即从内循环跳转到 标号 outer 标志的外循环。


4.3

跳 转 控 制

outer:

51

for( int i=0; i<10; i++={ //外循环 for( int j=0; j<20; j++={ //内循环 if( j>i ){ …… continue outer; //跳出内循环,开始下一次外循环 } …… } …… } 例 4.5 给出了一个求 100~200 之间的所有素数的程序。该例通过一个嵌套的 for 语句来实

现。 【例 4.5】PrimeNumber.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:

public class PrimeNumber{ public static void main(String args[]){ System.out.println(" ** 界于 100 — — 200 的素数 **"); int n=0; outer: for(int i=101;i<200;i+=2){ int k=15; for(int j=2;j<=k;j++){ if(i%j==0) continue outer; } System.out.print(" "+i); n++; if(n<10) continue; System.out.println(); n=0; } System.out.println(); } }

【运行结果】 如图 4.5 所示。

图 4.5

【例 4.5】的运行结果


第4章

52

4.3.3

流 程 控 制

return 语句

return 语句从当前方法中返回到调用该方法的语句处,并继续执行后面的语句。(有关方法 的内容,将在第六章介绍)。返回语句有两种格式。 第一种格式: return 表达式; 第二种格式: return; 【语法说明】 (1)第一种 return 语句,返回一个值给调用该方法的语句,返回值的数据类型必须与在方法 声明中的返回值类型一致,如果不一致可以使用强制类型转换来使类型一致。 (2)第二种 return 语句不返回任何值。 (3)return 语句通常用在一个方法体的最后,退出该方法并返一个值。Java 中,单独的 return 语句用在一个方法体的中间时,会产生编译错误,因为这时会有一些语句执行不到。但可以通过 把 return 语句嵌入到某些语句中(如 if-else)来使程序在未执行完方法中的所有语句时退出。 例如在下例中,方法 method 根据 num 是否大于 0,决定返回整数 num 否。 int method (int num){ if (num>0) return num; //如果 num>0 ,返回整数 num; …… }

4.4

其他语句 Java 除了常规的流程控制语句外,还有特殊的语句如异常处理语句。另外 Java 同样也有注

释语句,用来专门在程序中给出注释。 异常处理语句包括 try、catch、finally 以及 throw、throws 语句。异常处理语句是 Java 所特有的,将在后面章节中作专门的介绍。 Java 中可以采用下面三种注释方式: ∥——用于单行注释。注释从∥开始,终止于行尾。 /* … */——用于多行注释。注释从/*开始,到*/结束,且这种注释不能互相嵌套。 /** … */——是 Java 所特有的 doc 注释。它以/**开始,到*/结束。这种注释主要是为支 持 JDK 工具 javadoc 而采用的。javadoc 能识别注释中用标记@标识的一些特殊变量,并把 doc 注 释加入它所生成的 HTML 文件。对 javadoc 的详细讲述可参见 JDK 的相关工具参考书。

本 章 小 结 本章重点讨论了 Java 的控制语句(用于判断的 if 和 if-else,选择结构 switch,循环 结构 do-while、while、for),并介绍了跳转语句的使用。 在编写程序的时候,构造良好的控制结构,可提高整个程序的质量和可读性。


4.3

跳 转 控 制

练习与思考 4.1

用 for 结构求出 1~100 中所有偶数的和。

4.2

用 while 循环和计数变量 x 打印从 1 到 50 的中的奇数。要求每行只打印 6 个。

4.3

编制程序计算 1+1/2+1/4+1/8+…+1/100 的和。

4.4

试指出 for、while 和 do-while 语句特点,它们之间的区别和联系。

4.5

编制程序输出一个杨辉三角形。

4.6

求阶乘 n!,分别采用 int 和 long 数据类型,看 n 在什么取值范围以内,结果不会溢出。

53


5.1

第5章

Java 面向对象基础

53

Java 面向对象程序设计的基本概念

学习目标  理解 Java 面向对象概念  掌握对象创建和引用、对象方法的调用  熟练掌握方法声明、方法调用、方法参数的传递和方法修饰符 面向对象的程序设计方法按照现实世界的特点,把复杂的事物抽象为对象。对象具有自己的 状态和行为,通过对消息的反应来完成一定的任务。 本章我们介绍有关面向对象的基本概念,如类的定义与封装、对象的创建、成员变量及方法 等,这些概念将在后面的章节中被使用,因为面向对象是 Java 的重要特征。

5.1

Java 面向对象基础 面向对象思想有两大特点,即抽象和封装。面向对象正是通过抽象和封装来反映和描述现实

世界中存在的事物。 所谓抽象就是从研究的现实世界事物中抽取与工作有关的、能反映事物性质的东西,把它用 对象来进行描述。类又是对一组具有相同特性对象的抽象,若将类进行实例化与具体化,则会生 成这个类中的一个个对象。Java 是通过类来创建一个个对象,从而达到代码复用的目的。 封装则是在描述对象时,把对象的数据和对这些数据进行处理的操作结合起来,形成对象的 两大组成部分。封装使对象只表现出外部功能和特性,而内部的实现细节则不表现出来。Java 的 对象就是变量和方法的集合,其中变量表示这个对象的状态,方法实现这个对象所具有的行 为。

5.1.1

类和对象

现实世界中的任何东西都可以是对象。通过封装对象的属性和反映对象行为的方法,可以构 造与现实世界具体事物相对应的抽象模型。类是对象的抽象及描述。类刻画了一组具有公共特性 的对象。 类是一个静态的概念,它是一个模型,而对象则对应一个值。对象是一个具体的存在,它是 类的具体实例化。类与对象的关系相当于模型和具体实例的关系。 单一对象本身并不是很有用处,多个对象合作工作才能完成比较复杂的工作。在一个较大类 型程序中往往包含许多对象,通过这些对象的信息交互,可以使程序完成各种功能。 在面向对象的程序设计语言中,世界被看成独立存在的各种对象的集合,对象相互间通过消 息来通信。因而面向对象的程序设计语言以对象为中心,而不以具体的处理过程为中心。 对象间传递的信息称为消息,一个消息是由三部分所组成:


第5章

54

Java 面向对象程序设计的基本概念

(1)消息目标对象; (2)执行方法的名字; (3)执行方法所需用的参数(parameters)。 (因 Java 是一个对象化的语言,对象也常被用来 当作参数传递。) 这三个部分已经足够让接受消息的对象执行所需用的方法,并不需要其他的信息或上下文说 明。采用消息可以让对象的行为通过它的方法来表达。

5.1.2

类的定义

Java 中的类将数据和有关对数据的操作封装在一起,描述一组具有相同性质的对象。它封装 了相同性质对象的属性及方法,是这些对象的原型。 Java 的类是组成 Java 程序的基本单位, 因此编写 Java 程序的过程实际上就是编写类的过程。 例 5.1 给出了一个简单 HelloWorldApp 类的定义。 【例 5.1】HelloWorldApp.java 1: 2: 3: 4: 5:

public class HelloWorldApp{ public static void main(String args[]){ System.out.println("HelloWorld!"); } }

由例 5.1 可以看出,一个类在使用前必须要声明,类名 HelloWorldApp 后用大括号{}括起部 分为类体(body)。 Java 的类是由类声明和类体二部分构成。类中定义的成员变量和方法的数量不受限制。 1.类声明 类声明定义的格式为: [类修饰符] class 类名 [extends 父类名][implements 接口名[,接口名]] {类体} 【语法说明】 (1)其中类修饰符用于指明类的性质,可缺省。 (2)关键字 class 指示定义类的名称,类名最好是惟一的。 (3)“extends 父类名”通过指出所定义的类的父类名称来表明类间的继承关系,当缺省时 意味着所定义类为 Object 类的子类。 (4) “implements 接口名”用来指出定义的类实现的接口名称。一个类可以同时实现多个 接口。 【例 5.2】下面给出要定义一个表示图书的类 Book: 1: 2: 3: 4: 5: 6: 7: 8:

class Book{ String Title; String Author; int Pages; public String ShowBook(){ return("Title:"+Title+"Author:"+Author+"Pages:"+Pages); } }


5.1

Java 面向对象基础

55

【程序说明】 (1)程序第 1 句定义了 Book 类的类头。 (2)2~8 句定义了类体。类体包括三个成员变量和一个方法。 (3)在类体中,Title 是字符串变量,表示图书名;Author 也是字符串变量,表示作者;Pages 是整型变量,表示图书页数;ShowBook()方法用于返回某一特定的图书的信息。 2.类体 类体中定义了该类所有的成员变量和方法。通常变量在方法前进行定义,如下所示: class 类名{ 变量声明; 方法声明; } 类中所定义的变量和方法都是类的成员。对类的成员可以给定访问权限,来限定其他对象对 它的访问,访问权限有以下几种:private,protected,public,friend。同时,对类的成员来 说,又可以分为实例成员和类成员两种,将在后面章节中详细讨论。 【例 5.3】定义一个 Point 类,并且声明了它的两个变量 x、y,同时声明了 init()方法。 1: 2: 3: 4: 5: 6: 7:

5.1.3

class Ponit{ int x,y; void init(int ix,int iy){ // init()方法对 x、y 赋初值。 x=ix; y=iy; } }

类修饰符

类修饰符是用以指明类的性质的关键字。用户在自定义类时,指定不同的修饰符,就可以让 类具备不同的存取权限。一个类总能访问和调用它自己定义的成员变量和方法,但在这个类之外 的其他类能否访问和调用,除与类的修饰符有关外,还与类的成员变量和方法的控制符有关。基 本的类修饰符有 public,abstract 和 final 三个。 1.public 修饰符 如果一个类被声明为 public(公共类) ,那么与它不在同一个包中的类也可以通过引用它所在的 包来使用这个类,也就是说在同一包中的类可自由取用此类,而别的包中的类也可通过 import 关键 词来引入此类所属的包加以运用。如果不被声明为 public,这个类就只能被同一个包中的类使用。 使用 public 修饰符的类有几个特性: (1)一个程序里只能有一个类被修饰为 public,否则编译会报错。 (2)源程序文件中有用 public 修饰的类,则该源文件名必须同 public 类名。 (3)若程序中没有任何 public 类,则文件名可任取。 2.默认修饰符 如果一个类没有修饰符,就说明它具有默认的访问控制特性。默认修饰符的类只允许与类处 于同一个包中的类访问和调用,而不允许被其他包中的类使用。


第5章

56

Java 面向对象程序设计的基本概念

3.abstract 修饰符 如果一个类被声明为 abstract,那么它是一个抽象的类,抽象类不需给出类中每个方法的完 整实现。如果某个方法没有完整实现,必须要由子类的方法来覆盖,因此含有抽象型方法的类也 必须声明为抽象的。为了把一个类声明为抽象的,只需在类定义的 class 关键词前放置关键词 abstract。这些类不能直接用 new 操作符生成实例,因为它们的完整实现还没有定义。在类中不 能定义抽象的构造函数或抽象的静态方法。被声明为 abstract 的抽象类往往包含有被声明为 abstract 的抽象方法,这些方法由它的非抽象子类完成实现细节。 abstract 类有下列特性: (1)一个抽象类里可以没有定义抽象方法。但只要类中有一个方法是被声明为 abstract,则 该类必须为 abstract。抽象类中可以有抽象方法(只给出定义的方法)。 (2)抽象类不能创建对象,只能用来派生子类。 (3)抽象类不能被实例化,即不能被 new 成一个实例对象。 (4)若一个子类继承一个抽象类,则子类需用覆盖的方式来实化该抽象超类中的抽象方法。 若没有完全实化所有的抽象方法,则子类仍是抽象的。 下面的例子演示了一个带有抽象方法的抽象类和一个实现了该抽象方法的子类。 【例 5.4】 Abstract_Demo.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

abstract class A{ abstract void callme( ); void metoo( ){ system.out.println("在 A 的 metoo 方法里"); } } class B extends A{ void callme( ){ System.out.println("在 B 的 callme 方法里"); } } class Abstract_Demo{ public static void main(String args[]){ A a = new B( ); a.callme( ); a.metoo( ); } }

【运行结果】 如图 5.1 所示。


5.1 图 5.1

Java 面向对象基础

57

【例 5.4】的运行结果

【程序说明】 (1)第 1~6 行定义了一个抽象类,抽象类中定义了一个抽象方法 callme( )和一个非抽象方 法 metoo( )。 (2)在第 7~11 行中定义了类 B 来具体实现抽象类 A 中的抽象方法 callme( )。 4.final 修饰符 如果一个类被声明为 final,则意味着它不能再派生出新的子类,不能作为父类被继承。因 此一个类不能既被声明为 abstract 的,又被声明为 final 的。final 类是不能有子类的类,它可 以避免盲目地继承,以提高系统的安全性。将一个类声明为 final 类可以提高系统安全性。 例如,为防止意外情况的发生,可以将某些处理关键问题的信息类说明为 final 类。另外, 将一些具有固定作用的类说明为 final 类,可以保证调用这个类时所实现的功能正确无误。Java 系统用来实现网络功能的 InetAddress、Socket 及 ServerSocket 等类都是 final 类,因为实现 网络应用程序需要与服务器进行通信,对安全性要求比较高。 使用 final 类,就意味着不能继承并覆盖其内容。用两个修饰符 public final 的意思是: 此 final 类可被 import 来引用,但不能被继承。System 类关系到系统级控制,为了安全性,故 必须为 final 类,以避免被覆盖。

5.2

对象创建和引用

类名可以作为变量的类型来使用,如果一个变量的类型是某个类,那么它将指向这个类的实 例,称为对象实例。所有对象实例和它们的类型(某个类)的子类的实例都是相容的。就像可以 把 byte 型的值赋值给 int 型的变量一样,可以把 Object 的子类的任何实例赋给一个 Object 型 的变量。

5.2.1

对象的定义

对象是这样一个实例,它是类模板的单独的拷贝,带有自己的称为实例变量的数据集。当定 义一个变量的类型是某个类时,它的缺省值是 null,null 是 Object 的一个实例。null 是没有任 何值的意思,它和整数 0 不同。 下面这个例子中,声明变量 u 的类型是类 University: University u;//变量 u 的值是 null。 对象的创建包括声明、实例化和初始化三方面的内容。 (1)对象的声明格式为: 类型 对象名 注意,类型可以是组合类型(包括类和接口)。对象的声明并不为对象分配内存空间。 (2)对象的实例化,返回对该对象的一个引用(即该对象所在的内存地址) 。用 new 可以为 一个类实例化多个不同的对象。这些对象分别占用不同的内存空间,因此改变其中一个对象的状 态不会影响其他对象


第5章

58

Java 面向对象程序设计的基本概念

(3)执行构造函数,进行初始化。由于对构造函数可以进行重写,所以通过给出不同个数或 类型的参数会分别调用不同的构造函数。 以【例 5.3】所定义的类 Point 为例: class Ponit{ int x,y; void init(int ix, int iy){ x=ix; y=iy; } } 生成类 Point 的对象: Point p1=new Point(); Point p2=new Point(5, 10); 这里,为类 Point 生成了两个对象 p1、p2,它们分别调用不同的构造函数,p1 调用缺省的 构造函数(即没有参数),p2 则调用带参数的构造函数。 虽然 new 运算符返回对一个对象的引用,但与 C/C++中的指针不同,对象的引用是指向一个 中间的数据结构,它存储有关数据类型的信息以及当前对象所在的堆的地址,而对于对象所在的 实际内存地址是不可以进行操作的,这就保证了安全性。对象的使用包括引用对象的成员变量和 方法,通过运算符可以实现对变量的访问和方法的调用,变量和方法可以设定一定的访问权限。

5.2.2

对象成员变量的引用

要访问对象的某个成员变量,其格式为: 对象名.成员变量名 【语法说明】 对象名是对象的一个引用,这种引用包括对象的成员变量。 例如,用 Point p=new Point();生成了类 Point 的对象 p 后,可以用 p.x,p.y 来访问该点 的 x、y 坐标: p.x=10;p.y=20;

5.2.3

对象方法的调用

对象方法的调用格式为: 对象名.方法名 通过方法调用才体现了面向对象的特点,因为对象的行为实际上是通过方法表现出来的。 例 5.5 先定义一个 Point 类,然后在 main()中演示了如何引用 Point 的构造函数产生对象及 如何调用成员变量和方法。 【例 5.5】 UsingObject.java 1: 2: 3:

class Point{ int x,y; String name="apoint";


5.2 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54:

对象创建和引用

Point(){ x=0; y=0; } Point(int x,int y,String name){ this.x=x; this.y=y; this.name=name; } int getX(){ //得到参数 x 的值 return x; } int getY(){ //得到参数 y 的值 return y; } void move(int newX,int newY){ //移至对应的坐标(x,y) x=newX; y=newY; } Point newPoint(String name){ //产生新的坐标(-x,-y) Point newP=new Point(-x,-y,name); return newP; } boolean equal(int x,int y){ //判断对应值是否相等 if(this.x==x&&this.y==y) return true; else return false; } void print(){ //输出对应的点及坐标(x,y) System.out.println(name+":x="+x+" y="+y); } } public class UsingObject{ public static void main(String args[]){ Point p=new Point(); p.print(); //调用成员方法 p.move(50,50); System.out.println("**移动之后**"); System.out.println("直接得到 x,y"); System.out.println("x="+p.x+" y="+p.y); //访问成员变量 System.out.println("通过调用得到 x,y"); System.out.println("x="+p.getY()+" y="+p.getY()); if(p.equal(50,50)) System.out.println("找到(50,50)"); else System.out.println("未找到(50,50)"); p.newPoint("新产生的点").print(); new Point(10,15,"其它新产生的点").print(); } }

59


第5章

60

Java 面向对象程序设计的基本概念

【运行结果】 如图 5.2 所示。

图 5.2 【例 5.5】的运行结果

【程序说明】 (1)第 1~36 行定义了 Point 类,类中定义了一系列方法,第 4~12 行定义了两个 Point() 构造函数,调用时依据参数来判断使用哪一个构造函数,这是方法的多态性,将在下一章中介绍。 (2)第 13~35 行定义了类的其他方法。其中方法 equal 返回布尔值,以用来作为判断条件。 方法 newPoint 返回该点关于原点的对称点,返回值也是一个 Point 类型。 (3)第 37~54 行演示了如何调用类中的方法及变量。

5.3

成员变量 成员变量和方法是类的两大组成部分,因此成员变量只能存在于类体中,而不能出现在方法

里,否则编译会产生错误。

5.3.1

成员变量的定义

成员变量声明: [变量修饰符] 【语法说明】

类型 变量名[=初值][,变量名[=初值]];

(1)类型是成员变量的类型。 (2)变量名称是 Java 的合法定义标识符,它可以是多个。在多个变量名称间必须用“,”隔 开。每个变量可以设置自己的初始值。 (3)修饰符说明了成员变量的访问权限和使用规则。 变量可一次定义一个或多个,定义时可以给出初值。例如:定义下列几个成员变量。其中对 后两个变量进行了初始化。 public int a,b=12; protected String s="Hot Java";


5.3

5.3.2

成 员 变 量

61

成员变量修饰符

变量修饰符是用来指明特性的关键字,它的修饰符可分为存取性修饰符和存在性修饰符两 类,所谓存取性指的是控制类间的存取,存在性指的是成员变量本身在类中存在的特性。存取性 修饰符包括 public,protected 和 private,存在性修饰符包括 static 和 final。 1.public 修饰符 一个类中被声明为 public 的变量和方法是“公开”的,意味着只要能使用这个类,就可以 直接存取这个变量的数据,或直接使用这个方法。public 变量允许变量自身所属的类访问,也允 许同一个包中的其他类访问,还允许其他包中的类访问该变量。 2.protected 修饰符 一个类中被声明为 protected 的变量和方法是“受保护”的,意味着它们仅能被与该类处于 同一个包的类及该类的子类所直接存取和使用。 3.private 修饰符 被声明为 private 的变量和方法是“私有”的,表示除了声明它们的类外,不能被任何其他 的类直接存取和使用。此修饰符使变量受限于仅能在该类里面存取,而与其他类无关,扩展的子 类也无法实现。此类变量属特殊类内部数据,用于类内部处理。 当变量或方法前不加以上三种修饰符时,被认为是 friend 状态,即只允许定义它的类自身 以及在同一个包中的类访问和调用。但不存在 friend 关键字。 4.static 修饰符 被声明为 static 的变量是属于类而不是属于对象的,因此这种变量又被称为类变量(class variables) 。类变量不是被保存在由该类所创建的某个具体对象的内存单元中,而是保存在该类 的内存区域的公共存储单元中。类变量由该类所创建的对象共享,因此不管这个类产生了多少个 对象,它们都共享这个类变量。static 变量是独立于该类中的任何对象的,可以直接进行访问, 而不必通过类的对象去访问它。被声明为 static 的方法,称为类方法,它的原理与类变量一样。 可以在不创建类实例对象时直接使用类变量和类方法。一般来说,在 Java 中,引用一个特 定的变量或方法的形式是: 对象名.变量名 对象名.方法名 例如: int a=rectangle.length; g.drawString("Welcome to Java World!"); 即变量和方法是受限于对象的,但声明为 static 的变量或方法受限于类,使用形式是: 类名.变量名 类名.方法名 例如: System.out.println("Welcome to Java World!"); String s=String.valueOf(123); 这里并没有创建 System 类或 String 类的对象, 而直接调用 System 类的类变量 out 和 String


第5章

62

Java 面向对象程序设计的基本概念

类的类方法 valueOf。其中 valueOf 方法将整形参数转换为 String 类对象。被声明为 static 的 类方法在使用时有两点要特别注意: (1)类方法的方法体中只能使用类中其他同样是 static 的变量或方法; (2)类方法不能被子类修改或重新定义。 5.final 修饰符 将变量或方法声明为 final,可以保证它们在使用中不被改变。被声明为 final 的变量必须 在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能 使用,不能重载。此修饰符使变量的值只能被设置一次,不能被其他类或本身类更改。此类变量 就相当于一般程序语言中的常数(constant) 。但 const 这个关键词仍被保留着,尚未成为正式 的 Java 语言一部分。

5.4

方法

在 Java 中,成员变量和方法是类的两大组成部分,成员变量反映了类的性质,而方法反映 了类的行为。

5.4.1

方法声明

方法的声明格式如下: [方法修饰符]

返回值类型 方法名(参数表){方法体}

【语法说明】 定义方法时一定要给出返回值类型和参数表。当没有返回值时,返回值类型记为 void。参数 表的形式为: 参数类型 参数值[,参数类型 参数值] 各参数间以逗号分隔。下面是一些简单的例子: public static void main(String args[]){...} public void paint(Graphics g){...} public int area(int length,int width){return length*width;} 其中前两个是我们已经见过的方法声明,这里略去了具体语句组成的方法体。第三个则是一 个计算长方形面积的简单方法,接受整数类型的长度和宽度参数并返回它们的乘积作为结果。

5.4.2

方法调用

在程序中通常需要调用已经声明的方法,Java 根据具体情况使用了不同的调用方法。 1.欲调用的方法是否有返回值 (1)若方法有返回值,则将这些方法的调用当作一个数值来处理(数值类型与返回值的类型 需一致)。例如: int x=avg(a,b,c); 有时可直接使用此方法的调用,而不用再另行设置一个变量来存储返回值。例如: System.out.println(avg(a,b,c));


5.4

63

(2)若方法没有返回值,就可自行调用。例如: equipment(); 2.调用方法的所在位置 程序中调用一个方法的所在位置,通常是在类方法或实例方法中,而方法调用的写法就要看 被调用的方法所在位置,被调用的方法有可能是在本身这个类中,也有可能是在超类或其他的类 中。因此,原则就是: (1)this 与 super 不能用在由 static 修饰的类方法里,否则会产生编译错误信息: non-static variable this cannot be referenced from a static context non-static variable super cannot be referenced from a static context (2)在类方法中可直接调用本身类方法,但不可直接调用实例方法。 (3)在实例方法中可直接调用本身类中的类方法与实例方法。 (4)this 与 super 能用在实例方法中。 (5)[xx 实例].[xx 方法]的方式可用于任何情况里。

5.4.3

方法参数的传递

在前面方法声明格式里,有一个参数表。在方法声明参数表里的参数,称为形式参数(formal parameters)。当方法真正要被调用时,才被变量或其他数据所取代,而这些被称为实际参数 (actual parameters)。要调用一个方法时,需要提供实际参数(参数是可选择性的),这些实 际参数的类型与次序,需完全与每一个别相对应的形式参数吻合。 方法的参数传递,依参数的类型,分为传值调用(call by value)与传址调用(call by reference)两种。若方法的参数为一般变量的话,这时采用传值调用;若方法的参数为对象时, 则采用传址调用。传值调用并不会改变所传之参数的值,而传址调用因所传的参数是一个指向对 象的地址,这个对象内容是可以改变的。 例 5.6 给出了方法参数的传递实例。从中可以看出传值和传址的区别。 【例 5.6】PassingParam.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:

public class PassingParam{ static class OneObject{ public String Y="a"; } static void changeParam(int xx,OneObject oobject1){ xx=3; oobject1.Y="A"; } public static void main(String args[]){ int x=1; OneObject oobject=new OneObject(); //创建对象 oobject System.out.println("传递前的参数值:x="+x+"y="+oobject.Y); PassingParam.changeParam(x,oobject); //传递变量 x 和对象 oobject 地址 System.out.println("传递后的参数值:x="+x+"y="+oobject.Y); } }


第5章

64

Java 面向对象程序设计的基本概念

【运行结果】 如图 5.3 所示。

图 5.3

5.4.4

【例 5.6】的运行结果

方法修饰符

方法修饰符(method modifiers)大部分的种类及意义与变量修饰符一样,不同的是多了存 在性的 abstract 以及动作性修饰符 synchronized。由于存取性修饰符 public,protected 和 private 与变量修饰符的功能类似,这里只讨论存在性修饰符 static,abstract,final 和动作 性修饰符 synchronized。 1.static 修饰符 静态方法即类方法,是用 static 修饰的方法,它是属于整个类的,也就相当于非面向对象 语言中的全局方法、函数,所以静态方法不能处理属于某个对象的成员变量,只能处理静态变量。 使用时要特别注意静态方法只能使用静态变量与静态方法,也就是只能使用 static 修饰的变量 与方法,而不能使用其余的实例变量。如果要使用,只能用[对象].[数据]的方式。静态方法在 内存中的代码是随着类的定义而分配和装载的,不属于任何对象,应直接用类名作前缀来调用静 态方法。 此修饰符会使方法成为惟一,与类相同,不会因实例的产生而受影响。static 方法在使用上, 注意以下几点: (1) static 方法只能使用 static 变量, 否则编译会出错。 像在 main()里, 方法通常是用 public static 来修饰,所以只能用 static 的变量。 (2)一个类的 static 变量与 static 方法,可直接用该类的名称,按下面方法来取用: [类名称].[静态方法] [类名称].[静态变量] [类名称].[静态变量].[静态方法] 例如: color=Color.blue; String password=System.getProperty("user.password"); System.out.println(); 2.abstract 修饰符 抽象方法存在于抽象类中,并不建立程序代码,而是留给继承的子类来覆盖。被声明为


5.4

65

abstract 的方法不需要实际的方法体,只要提供方法原型接口,即给出方法的名称、返回值类型和参数表, 格式如下: abstract 返回值类型 方法名(参数表); 即声明抽象方法时,并不用写出大括号{}。定义了 abstract 抽象方法的类必须被声明为 abstract 的抽象类。 3.final 修饰符 被声明为 final 的方法不能被其他类变更方法里的内容,即使是继承的子类。 4.synchronized 修饰符 此方法修饰符用于同步化监控处理。被 synchronized 的方法,一次只能被一个线程来使用, 就好像一台 PC 机,虽然有很多人会操作电脑,但同时只能有一个人可以使用,必须等待计算机 有空闲时,另一位才能使用。

本 章 小 结 本章我们主要从面向对象的原理进行一些介绍,重点对一些基本概念、思想、方法,然后结 合 Java 语言来进行详细的描述。如对象和类、成员变量、方法的概念。Java 作为面向对象程序 设计语言,在程序设计中,处处涉及对象、类、方法和成员变量的定义和使用,对它们的灵活运 用是一名程序员应该掌握的最基本的技术之一。

练习与思考 5.1

什么是对象,在 Java 语言中,对象的状态和行为是用什么描述的?

5.2

什么是抽象?什么是类?

5.3 在现实世界中的盒子具有宽度、高度和深度等属性,定义一个表示盒子的类 Box,定义构造函数对盒 子的属性进行初始化,并定义一个方法 ShowBox()用于显示盒子的属性。 5.4

对第 5.3 题进行编程实现,并创建 Box 类的对象,并将此对象的信息显示在屏幕上。


第6章

66

第6章

继承与多态

继承与多态

学习目标  理解继承子类创建(extends 关键字)、this 与 super 关键字、多重继承与接口  熟练掌握接口定义、接口实现;多态:方法覆盖、方法重载、构造函数  理解包的定义、包的引用 Java 程序设计的过程就是设计类的过程。Java 的类只能有一个直接父类,为了实现多重继 承,Java 语言引入了接口的概念。在 Java 中定义好的类依据实现功能的不同,可以分为不同的 集合,每个集合是一个包,所有的包合称为类库。 这一章将进一步讨论 Java 类、包和接口等面向对象机制。首先给出基本概念,然后结合具 体实例阐明 Java 的类、接口、包以及封装、继承、重载等有关内容。

6.1

继承

继承性是面向对象的重要特性。继承允许一个类成为另一个类的子类,子类继承了父类的所 有特性,并且可以扩展出自己特征。类的继承性提供了一种明确描述共性的方法,减少了类似的 重复说明。继承机制提高了软件的可用性、代码的复用性以及界面的一致性。 通过使用子类,可以实现继承。从最一般的类开始,逐步特殊化,可派生出一系列的子类。 父类和子类之间的关系呈现出层次化。同时,继承实现的代码复用,使程序复杂度线性地增长, 而不是呈几何级数增长。 在 Java 中任何一个类都有父类(除了 object 类以外) 。Java 只支持单重继承,大大降低了 继承的复杂度,而 C++支持多重继承,即一个类可以继承多个父类,使得实现变得非常复杂且不 可预料。同时在 Java 中,通过接口可以实现多重继承,接口的概念非常简单,使用更方便,而 且不仅仅限于继承,它使多个不相关的类可以具有相同的方法。

6.1.1

子类创建(extends 关键字)

继承是关于有层次关系的类之间的概念。一个类的后代可以继承它的祖先的所有变量和方 法,就像自己创建的一样。一旦创建了一个这样的类,可以很方便的创建它的子类。一个类的子 类是它的继承了父类的成员变量和方法的特殊版本。创建子类的语法格式如下: 子类名 extends 父类名 例 6.1 给出了一个创建子类的实例。它从“大学”父类中继承,并定义了 country 这个字符 串和两个构造函数。 【例 6.1】UniversityWorld.java


6.1

67

1: class UniversityWorld extends University{ 2: String country; 3: UniversityWorld(String name, String city, String country){ 4: this.name = name; 5: this.city = city; 6: this.country = country; 7: } 8: UniversityWorld( ){ 9: this("北京大学", "北京", "中国") 10: } 11: }

【运行结果】 如图 6.1 所示。

图 6.1

【例 6.1】的运行结果

【程序说明】 在这个实例中,把 University 类派生为含有叫做 country 元素的子类。关键字 extends 用 来表示创建的是 University 的子类。name 和 city 不需要在 UniversityWorld 中进行声明,因为 它们是从 University 中继承的。Java 允许在 UniversityWorld 中声明变量 name 和 city,但应 避免这种用法,因为它会隐藏 University 中的 name 和 city,是与使用子类的目的相矛盾的。在 UniversityWorld 的实例中,name、city 和 country 的地位是一样的。

6.1.2

this 与 super 关键字

Java 中有三个特殊的关键字:null,this 和 super。这三个关键字是所有的类都可以使用的, 用来指示一些特定的对象。 (1)null 相当于“空”,可以用来代指任何对象,但没有实例。如: Rectangle r=null; 创建了一个 Rectangle 的变量 r,但并没有一个 Rectangle 的实例对象由 r 来代表。r 就如 同一个可放置 Rectangle 的盒子,只是这个盒子现在是空的。 (2)this 用以指代一个对象自身。它的作用主要是将自己这个对象作为参数,传送给别的对 象中的方法。 (3)super 用来取用父类中的方法和变量数据。它的用法如在下面的例子中所示。 (4)super 和 this 的另一个重要用途是用在构造函数中。当一个类中不止一个构造函数时, 可以用 this 在一个构造函数中调用另一个构造函数。若想调用父类的构造函数,则直接使用 super。


第6章

68

继承与多态

例 6.2 演示了如何在类中使用 super 关键字。 【例 6.2】Check.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:

class Father{ void speak(){ System.out.println("子类调用:父类 speak()输出"); } void speak(String s){ System.out.println(s+"父类 speak(s)输出"); } } class Son extends Father{ void speak(){ System.out.println("子类 speak()输出."); super.speak(); //相当于调用 Father 类的 speak()方法 super.speak("子类调用:");//相当于调用 Father 类的 speak(String s)方法 } } class Check{ public static void main(String args[]){ Son s=new Son(); s.speak(); } }

【运行结果】 如图 6.2 所示。

图 6.2

【例 6.2】的运行结果

【程序说明】 在这个例子中,类 Son 的 speak()方法语句: super.speak(); super.speak("hunting"); 实际调用了 Son 的父类 Father 中的 speak()和 speak(String s)方法,以实现执行结果后两 行的输出。使用父类的变量形式也采用类似的格式: super.变量名 就像在 University 例子中用 this 指向第一个构造函数一样,在 Java 里有另一个变量叫做 super,它直接指向超类的构造函数。 例 6.3 演示了这种调用构造函数的方法。其主要功能是用 super 来初始化变量 name 和 city,


6.1

69

然后打印出这个对象的内容。 【例 6.3】UniversityWorld.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:

class class University{ String name, city; University(String name, String city){ this.name = name; this.city = city; } } class UniversityWorld extends University{ String country; UniversityWorld(String name, String city, String country){ super(name, city); // 调用了构造函数 University(name, city) this.country = country; } public static void main(String args[]){ UniversityWorld u = new UniversityWorld("北京大学", "北京", "中国"); System.out.println("大学:" + u.name + " 城市:" + u.city + " 国家:" + u.country); } }

【运行结果】 如图 6.3 所示。

图 6.3

6.1.3

【例 6.3】的运行结果

多重继承与接口

一个对象可以隶属于多种不同的类。例如,一位父亲可以是属于父亲类、丈夫类、公司主管 类等。这种多元性的继承关系就叫做多重继承。 而 Java 为了免去 C++由于多重继承而衍生的问题,限定类的继承只能单一继承。但实际上又 有多重继承的需要,故采用接口(interface)这个概念来解决这个问题。 interface 这个英文字的意思是一种装置或系统,使得不同性质的工具能够交互地运作。比 如,计算机的操作系统,可把它称作是一种“人机接口”。可使得用户(人)与计算机通过鼠标 或键盘等装置作为一种沟通接口与计算机做沟通,指挥计算机做事。另外像 RS232 串行端口,常 作为输入设备传输接口,是一种计算机与输入设备间的接口装置。 而在 Java 中,设计接口的用意,是可使得类不必受限于单一继承的关系,而灵活地共同继 承一些共有的特性,达到多重继承的目的,但可免去 C++中因多重继承的复杂关系所容易产生的


第6章

70

继承与多态

BUG。

6.2

接口

接口(interface)可看成一个空的抽象的类,只声明了一组类的若干同名变量和方法,而 不考虑方法的具体实现。 Java 的接口也是面向对象的一个重要机制。它的引进是为了实现多继承,同时免除 C++中的 多继承那样的复杂性。前面讲过,抽象类中包含一个或多个抽象方法,该抽象类的子类必须实现 这些抽象方法。接口类似于抽象类,只是接口中的所有方法都是抽象的。这些方法由实现这一接 口的不同类具体完成。在使用中,接口类的变量可用来代表任何实现了该接口的类的对象。这就 相当于把类根据其实现的功能来分别代表,而不必顾虑它所在的类继承层次。这样,可以最大限 度地利用动态绑定来隐藏实现细节。接口还可以用来实现不同类之间的常量共享。

6.2.1

接口定义

Java 的接口类似于抽象类,因而它的声明也和抽象类类似,只定义了类中方法的原型,而没 有直接定义方法的内容。它的声明格式为: [接口修饰符] interface 接口名 [extends 父类名]{ ...//方法的原型定义或静态常数 } 【语法说明】 接口修饰符可以是 public 或 abstract,其中 abstract 缺省时也有效。public 的含义与类 修饰符是一致的。要注意的是一个编译单元,即一个.java 文件中最多只能有一个 public 的类或 接口,当存在 public 的类或接口时,编译文件必须与这个类或接口同名。 被声明的变量总是被视为 static 和 final 的,因而必须在声明时给定初值。被声明的方法 总是 abstract 的,abstarct 缺省也有效。与抽象类一样,接口不需要构造函数。接口的继承与 类是一样的,当然一个接口的父类也必须是接口。下面是一个接口的例子: interface Booksale { void show(); void sale(); void replay(); }

6.2.2

接口实现

为了将接口实现,必须使用关键字 implements。它的实现格式如下: [类修饰符] class 类名 [extends 父类名] [implements 接口名表] 【语法说明】 接口名表可包括多个接口名称,各接口间用逗号分隔。一个实现接口的非抽象类必须写出


6.2

71

实现接口中定义的方法的具体代码,同时可以读取使用接口中定义的任何变量。 例 6.4 首先定义一个接口 Booksale 和图书类 Book,随后创建一个具体 Book 的子类 Book_1, 这个子类实现了接口的全部方法。 【例 6.4】Book_1.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40:

interface Booksale { void show(String Title); void sale(); void replay(); } class Book{ String Title; String Author; String book_type; float book_price; int Pages; public String ShowBook(){ return("Title:"+Title+"Author:"+Author+"Pages:"+Pages); } } public class Book_1 extends Book implements Booksale{ static int book_no=82555; String serial="100005"; public Book_1(){ book_type="computer"; book_price=15; } public static void main(String args[]){ Book_1 book_1=new Book_1(); book_1.show("计算机文件基础"); book_1.sale(); book_1.replay(); } public void show(String Title){ System.out.println("书的名称:"+Title); System.out.println("书的类型"+book_type); System.out.println("书的价格"+book_price); } public void sale(){ System.out.println("书的编号"+book_no); } public void replay(){ System.out.println("书的定购"+serial); } }

【运行结果】 如图 6.4 所示。


第6章

72

图 6.4

6.3

继承与多态

【例 6.4】的输出结果

多态

多态是指把类中具有相似功能的不同方法用同一个方法名来定义,从而可以使用相同的方式 来调用这些具有不同功能的同名方法。Java 通过覆盖和重载来实现多态。 “重载”是指同一样东西在不同的地方具有多种含义;而“覆盖”是指它随时随地都只有一 种含义,只是原先的含义完全被后来的含义取代了。面向对象程序设计中的多态可以通过子类对 父类方法的覆盖来实现,也可以利用重载在同一个类中定义多个同名的不同方法来实现。 对于方法覆盖,子类可以重新实现父类的某些方法,使其具有自己的特征。覆盖隐藏了父类 的方法,使子类拥有自己的具体实现方法,更进一步表明子类继承了父类特性,同时子类又具有 的一定的特殊性。 通过重载,一个类中可以有多个具有相同名字的方法,通过传递给它们不同个数和类型的参 数来决定使用哪种方法。例如,对于一个作图的类,它有一个 draw()方法用来画图或输出文字, 可以传递给它一个字符串、一个矩形、一个圆形,甚至还可以再指定作图的初始位置、图形的颜 色等,对于每一种实现,只需实现一个新的 draw()方法即可,而不需要新起一个名字,这样大大 简化了方法的实现和调用,程序员和用户都不需要记住更多的方法名,只需要传入相应类型的参 数即可。

6.3.1

方法覆盖

覆盖的意思是不用考虑父类的方法是如何写的,只要重新定义改写,就可以采用新的方法。 子类变更父类的方法,则可用覆盖的方式进行。与面向过程的 程序性设计语言比较,这种做法有突出的优点。在面向过程的 程序性设计语言中,若要修改一个函数,则需彻底了解函数中 的每一个细节,然后才能对程序代码进行修改,否则可能引起 程序出现问题。面向对象的程序设计方法用覆盖的方式,大大 地降低了修改的成本。覆盖的思想如图 6.5 所示。 例 6.5 中演示了方法的覆盖,University 的新的子类继 承了它的超类的方法 samecity。samecity 这个方法判断的是 两个城市的名字,因为有可能两个名字一样的城市属于不同的 国家,因此需要用同时判断城市和国家的方法来覆盖它。 【例 6.5】UniversityWorldCity.java。

图 6.5

子类对超类方法的覆盖


6.3 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44:

73

class University{ String name, city; University(String name, String city){ this.name = name; this.city = city; } boolean samecity(String city){ if (city.equals(this.city)) return true; else return false; } boolean samecity(University u){ return samecity( u.city); } } class UniversityWorld extends University{ String country; UniversityWorld u; UniversityWorld(String name, String city, String country){ super(name, city); this.country = country; } boolean samecity(String city, String country){ if (city.equals(u.city) && country.equals(u.country)) return false; else return false; } boolean samecity(UniversityWorld other){ return samecity(other.city, other.country); } } class UniversityWorldCity{ public static void main(String args[]){ String city = "上海"; String country = "中国"; UniversityWorld u1 = new UniversityWorld("北京大学", "北京", "中国"); UniversityWorld u2 = new UniversityWorld("清华大学", "北京", "中国"); System.out.println("u1 = " + u1.name + ", " + u1.city + ", " + u1.country); System.out.println("u2 = " + u2.name + ", " + u2.city+ ", " + u2.country); System.out.println("city = " + city + ", country = " + country); System.out.println("u1.samecity(u2) = " + u1.samecity(u2)); System.out.println("u1.samecity(city, country) = " + u1.samecity(city, country)); 45: } 46: }

【运行结果】 如图 6.6 所示。


第6章

74

图 6.6

6.3.2

继承与多态

【例 6.5】的运行结果

方法重载

重载可理解为一个方法名有两个或两个以上的含义,即在 Java 中,可以在同一个类中定义 多个同名的方法。在方法重载时,根据方法参数的个数、类型、顺序来决定采用哪个方法。 例 void f() { 代码块 1 } void f(long number) { 代码块 2 } 根据方法的参数决定到底采用哪一个 f 方法,f()与 f(12)会在编译时自动对应不同代码行。

6.3.3

构造函数

构造函数是一种特殊的方法。Java 中的每个类都有构造函数(Constructor) ,用来初始化该 类的一个新的对象。每创建一个类的实例都去初始化它的所有变量是乏味的,如果一个对象在被 创建时就完成了所有的初始工作,将是简单的和简洁的。因此,Java 在类里提供了构造函数。

构造函数具有和类名相同的名称,而且不返回任何数据类型,在构造函数的实现中,也 可以进行方法重载。一旦定义好一个构造函数,创建对象时就会自动调用它。构造函数没有返回 类型,即使是 void 类型也没有。这是因为一个类的构造函数的返回值的类型就是这个类本身。 构造函数的任务是初始化一个对象的内部状态,所以用 new 操作符创建一个实例后,立刻就会得 到一个清楚、可用的对象。 当用运算符 new 为一个对象分配内存时,要调用对象的构造函数,而当创建一个对象时,必 须用 new 为它分配内存。因此用构造函数进行初始化避免了在生成对象后每次都要调用对象的初 始化方法。如果没有实现类的构造函数,则 Java 为其产生一个默认的空方法。另外,构造函数 只能由 new 运算符调用。 class point{ int x,y; point(){


6.3

75

x=0;y=0; } point(int x,int y){ this.x=x;this.y=y; } } 上例中, Point 类实现了两个构造函数,方法名均为 Point,与类名相同。而且使用了方法 重载,根据不同的参数分别使用不同的同名构造函数对点的 x、y 坐标赋予不同值,对点的 x、y 坐标进行初始化。二者完成相同的功能。 例 6.6 中用构造函数初始化对象,new 语句中类名后的参数是传给构造函数的。 【例 6.6】UniversityCreate.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:

class University{ String name,city; University(String name, String city){ this.name = name; this.city = city; } } class UniversityCreate{ public static void main(String args[]){ University u = new University("北京大学", "北京"); System.out.println("大学:" + u.name + " 城市:" + u.city); } }

【运行结果】 如图 6.7 所示。

图 6.7 【例 6.6】的运行结果

6.4

在 Java 中,包的概念和目的都与其他语言的函数库非常类似,所不同的只是其中封装的是 一组类。为了开发和重用的方便,可以将写好的程序类整理成一个个程序包。Java 自身提供了多 个预先设定好的包,具体包的定义及功能参看 Java 的 API。设计时可以引用这些包,也可以创建 自己的包。 Java 的包(package)中包含一系列相关的类,同一个包中的类可直接互相使用,对包外的


第6章

76

继承与多态

类则有一定的使用限制。Java 的包近似于其他语言的函数库,可提供重用的方便。

6.4.1

包的定义

为了声明一个包,首先,必须建立一个相应的目录结构,子目录名与包名一致。然后,在需 要放入该包的类文件开头声明包,形式为: package 包名; 这样这个类文件中定义的所有类都被装入到该包中。例如: package Family; class Father{ ...//类 Father 装入包 Family } class Son{ ...//类 Son 装入包 Family } class Daughter{ ...//类 Daughter 装入包 Family } 不同的程序文件内的类可以同属于一个包,只要在这些程序文件前都加上同一个包的说明即 可。例如: //文件 Cat.java package Animals; class Cat{/*将类 Cat 放入包 Animals 中*; ... } //文件 Dog.java package Animals; class Dog{/*将类 Dog 放入包 Animals 中*/ ... }

6.4.2

包的引用

在 Java 中,为了装载已编译好的包,通常可使用以下三种方法: (1)在要引用的类名前带上包名作为修饰符。如: Animals.Cat cat=new Animals.Cat(); 其中 Animals 是包名,Cat 是包中的类,cat 是类的对象。 (2)在文件开头使用 import 引用包中的类。如: import Animals.Cat; class Check{ Cat cat=new Cat();


6.4

77

} 同样 Animals 是包名,Cat 是包中的类,cat 是创建的 Cat 类对象。 (3)在文件前使用 import 引用整个包。如: import Animals.*; class Check{ Cat cat=new Cat(); Dog dog=new Dog(); ... } Animals 整个包被引入,Cat 和 Dog 为包中的类,cat 和 dog 为对应类的对象。 在引用包时,可以用点“.” 表示出包所在的层次结构,如经常使用的: import java.io.*; import java.applet.*; 实际是引入了/java/io/或/java/applet/这样的目录结构下的所有内容。需要指出的是, java.lang 这个包无需显式地引用,它总是被编译器自动调入的。使用包时还要特别注意系统 classpath 路径的设置情况,它需要将包名对应目录的父目录包含在 classpath 路径中,否则编 译时会出错,提示用户编译器找不到指定的类。

本 章 小 结 本章继续介绍了 Java 面向对象的有关概念如继承、多态、接口及包等,它们增加了 Java 的 简单性、灵活性和安全性。 接口使 Java 允许一个类实现多个接口,从而实现了类似于多重继承的某些特征。这样,即 保留了类的单一继承机制所具有的程序结构简洁和清晰等特点,又可以利用接口实现多重继承。 包的使用可以避免类名的冲突,同时类的层次结构分明,便于共享。

练习与思考 6.1

abstract 类与方法有哪些特性?

6.2

什么是方法的覆盖?什么是方法的重载?两者有什么区别?

6.3

什么是包,简述包的作用。

6.4

import 关键字的作用是什么?

6.5

Java 语言为什么要引入接口的概念?接口具有哪些特点?

6.6

接口与抽象有什么区别?接口与类有什么区别?

6.7 定义一个接口,它含有两个抽象方法:第一个抽象方法用于实现在两个数中求最小数:第二个抽象方 法用于实现在三个数中求最大数。定义一个类实现这个接口;再定义一个含有 main()方法的主类来实现最小数和 最大数的输出显示。


第7章

78

第7章

异 常 处 理

异 常 处 理

学习目标  了解异常的定义、异常处理的特点  理解异常处理机制和异常处理方式  掌握捕获异常时使用的 try 与 catch 语句以及多个 catch 语句、try 语句的嵌套  掌握抛出异常、自定义异常 异常处理指的是对程序运行时出现的非正常情况进行处理。异常处理可提高程序的健壮性和 容错性。 本章首先给出了异常处理的概念和基本过程,随后讨论了异常处理的关键字,异常的处理机 制,如何捕捉异常、抛出异常及用户自定义异常,并对各种 Error 和 Exception 进行了举例。

7.1

异常的概念 在用传统的语言编程时,程序员只能通过函数的返回值来发出错误信息。这易于导致很多错

误,因为在很多情况下需要知道错误产生的内部细节。通常,用全局变量 errno 来存储异常的类 型。这容易导致误用,因为一个 errno 的值有可能在被处理之前被另外的错误覆盖掉。即使是 C 语言程序,为了处理异常情况,也常求助于 goto 语句。Java 对异常的处理是面向对象的。Java 的一个 Exception 是一个描述异常情况的对象。当出现异常情况时,一个 Exception 对象就产生 了,并放到产生这个异常的成员函数里。 1.异常定义 在异常类层次的最上层有一个单独的类叫做 Throwable,这个类用来表示所有的异常情况, 每个异常类型都是 Throwable 的子类。Throwable 有两个直接的子类,一类是 Exception,是用 户程序能够捕捉到的异常情况,我们将通过产生它的子类来创建自己的异常。另一类是 Error, 它定义了那些通常无法捕捉到的异常,要谨慎使用 Error 子类,因为它们通常会导致灾难性的失 败。在 Exception 中有一个子类 RuntimeException,它是程序运行时自动地对某些错误作出反应 而产生的。 2.Java 异常处理的特点 下面这三个例子分别是没有处理异常,用常规方法处理异常和用 Java 异常机制处理异常的 三种处理方式的源代码,从中我们可以体会 Java 异常处理的特点。

【例 7.1】没有处理错误的程序。 1: 2: 3:

read-file { openTheFile; determine its size;


7.1 4: 5: 6:

异常的概念

79

allocate that much memory; closeTheFile; }

【例 7.2】以常规方法处理错误。 1:

openFiles;

2:

if (theFilesOpen) {

3:

determine the lenth of the file;

4:

if (gotTheFileLength){

5:

allocate that much memory;

6:

if (gotEnoughMemory) {

7:

read the file into memory;

8:

if (readFailed) errorCode=-1;

9:

else errorCode=-2;

10:

}else

11:

errorCode=-3;

}else errorCode=-4 ;

12: }else errorCode=-5;

【例 7.3】java 用异常机制处理错误。 1:

read-File;

2:

{

3:

try{

4:

openTheFile;

5:

determine its size;

6:

allocate that much memory;

7:

closeTheFile;

8:

}

9:

catch(fileopenFailed) { DO SOMETHING; }

10:

catch(sizeDetermineFailed) { DO SOMETHING;}

11:

catch(memoryAllocateFailed){ DO SOMETHING;}

12:

catch(readFailed){ DO SOMETHING;}

13:

catch(fileCloseFailed) { DO SOMETHING; }

14: }

与传统的方法相比较,Java 异常处理具有以下特点: (1)Java 通过面向对象的方法进行异常处理,把各种不同的异常事件进行分类,体现了良好 的层次性,提供了良好的接口,这种机制对于具有动态特性的复杂程序提供了强有力的控制方式。 (2)Java 的异常处理机制使得处理异常的代码和常规代码分开,大大减少了代码量,增加了 程序的可读性。 (3)Java 的异常处理机制使得异常事件可以沿调用栈自动向上传播,而不是 C 语言中通过函 数的返回值来传播,这样可以传递更多的信息并且简化代码。


80

第7章

异 常 处 理

图 7.1

异常处理机制

(4)由于把异常事件当成对象处理,利用类推层次性可以把多个具有相同父类的异常统一处 理,也可以区分不同的异常分别处理,使用非常灵活。

7.2

异常处理机制 在 Java 语言中,所有的异常都是用类表示的。当程序发生异常时,会生成某个异常类的对

象。Throwable 是 java.lang 包中一个专门用来处理异常的类,它有两个直接子类:Error 和 Exception 见图 7.2。

图 7.2

异常类的层次结构

1.Error 类 Error 类型的异常与 Java 虚拟机本身发生的错误有关,用户程序不需要处理这类异常。 2.Exception 类 程序产生的错误由 Exception 的子类表示,用户程序应该处理这类异常。Exception 中定义 了许多异常类,每个异常类代表了一种执行错误,类中包含了对应于这种运行错误的信息和处理 错误的方法等内容。当程序执行期间发生一个可识别的执行错误时,如果该错误有一个异常类与 之相对应,那么系统就会产生一个相应的该异常类的对象。一旦一个异常对象产生了,系统中就 一定有相应的机制来处理它,从而保证用户程序在整个执行期间不会产生死机、死循环等异常情 况。Java 语言采用这种异常处理机制来保证用户程序执行的安全性。 (1)Exception 类的两个构造函数: public Exception(),系统提示异常信息。 public Exception(String s),接收字符串参数传入的信息,对异常对象所对应的错误进行


7.1

异常的概念

81

描述。 (2)Exception 类从 Throwable 继承的常用方法: public StringtoString(),返回当前 Exception 类信息的字符串。 public void printStackTrace(),向屏幕输出异常信息。 表 7.1 名

7.3

部分常见的异常类

ArithmeticException

算术错误

ArrayIndexOutOfBandsException

数组越界

ArrayStoreException

数组存储空间不足

IOException

输入输出错误

FileNotFoundException

指定文件没有发现

NullPointerException

访问一个空对象成员

MalformedURLException

URL 格式错误

NumberFormatException

数据格式错误

异常处理方式 异常处理主要有三种: (1)对于运行时异常可以不做处理。 (2)使用 try-catch-finally 语

句捕获异常;(3)通过 throws 子句声明自己的异常,并用 throw 语句来抛出它。 Java 的异常处理是通过 5 个关键词来实现的:try,catch,throw,throws 和 finally。用 try 来执行一段程序,如果出现异常,系统抛出(throws)一个异常,你可以通过它的类型来捕 捉(catch)它,或最后(finally)由缺省处理器来处理。 下面是异常处理程序的基本形式: try { //程序块} catch (ExceptionType1 e) { // 对 ExceptionType1 的处理} catch (ExceptionType2 e) { // 对 ExceptionType2 的处理 throw(e); //再抛出这个异常 } finally { } 异常对象是 Java 在运行时对某些异常情况作出反应而产生的。例 7.4 给出了一个包含一个 整数被 0 除出现异常的小程序。 【例 7.4】Exc0.java


第7章

82 1: 2: 3: 4: 5: 6:

异 常 处 理

class Exc0{ public static void main(String args[]){ int d = 0; int a=42/d; } }

【程序说明】 当 Java 执行这个除法时,由于分母是 0,就会构造一个异常对象来使程序停下来并处理这个 错误情况,在运行时抛出(throw)这个异常。程序流将会在除号操作符处被打断,然后检查当 前的调用堆栈来查找异常。一个异常处理器是用来立即处理异常情况的。在这个例子里,我们没 有编写异常处理器,所以缺省的处理器就发挥作用了。缺省的处理器打印 Exception 的字符值和 发生异常的地点。

7.4

捕获异常

通常我们希望自己来处理异常并继续运行。可以用 try 来指定一块预防所有异常的程序。紧 跟在 try 程序后面,应包含一个 catch 子句来指定你想要捕捉的异常的类型。 1.try 与 catch 下面的例子是一个基本的异常处理实例,它是在例 7.4 基础上构造的,但它包含一个 try 程 序块和一个 catch 子句。 【例 7.5】exc1.java 1: class exc1{ 2: public static void main(String args[]) { 3: try { 4: int d = 0; 5: int a = 42 / d; 6: } 7: catch(ArithmeticException e){ 8: System.out.println("被零除"); 9: } 10: } 11: }

【运行结果】 如图 7.3 所示。

图 7.3

【例 7.5】的运行结果


7.3

异常处理方式

83

catch 子句的目标是解决异常情况,把一些变量设到合理的状态,并象没有出错一样继续运 行。如果一个子程序不处理某个异常,则返到上一级处理,直到最外一级。 2.多个 catch 子句 在某些情况下,同一段程序可能产生不止一种“异常”情况。你可以放置多个 catch 子句, 其中每一种“异常”类型都将被检查,第一个与之匹配的子句就会被执行。如果一个类和其子类 都有的话,应把子类放在前面,否则将永远不会到达子类。例 7.6 给出了一个有两个 catch 子句 的程序的例子。 【例 7.6】MultiCatch.java 1: class MultiCatch{ 2: public static void main(String args[]) { 3: try { 4: int a = args.length; 5: System.out.println("a = " + a); 6: int b = 42/a; int c[] = {1}; 7: c[42] = 99; 8: } 9: catch(ArithmeticException e) { 10: System.out.println("div by 0: " + e); 11: } 12: catch(ArrayIndexOutOfBoundsException e) { 13: System.out.println("array index oob: " + e); 14: } 15: } 16: }

【运行结果】 如图 7.4 所示。

图 7.4

【例 7.6】a=3 时产生的异常

【程序说明】 如果在程序运行时不跟参数,将会引起一个 0 做除数的异常,因为 a 的值为 0。如果我们提 供一个命令行参数,将不会产生这个异常,因为 a 的值大于 0。但会引起一个 ArrayIndexOutOfBoundexception 的异常,因为整型数组 c 的长度是 1,却给 c[42]赋值。 3.try 语句的嵌套 可以在一个成员函数调用的外面写一个 try 语句,在这个成员函数内部,写另一个 try 语句 保护其他代码。每当遇到一个 try 语句,异常的框架就放到堆栈上面,直到所有的 try 语句都完


第7章

84

异 常 处 理

成。如果下一级的 try 语句没有对某种异常进行处理,堆栈就会展开,直到遇到有处理这种异常 的 try 语句。 【例 7.7】try 语句嵌套例子。 1: class MultiNest{ 2: static void procedure(){ 3: try { 4: int c[] = { 1 }: c[42] = 99; 5: } 6: catch(ArrayIndexOutOfBoundsexception e) { 7: System.out.println("array index oob: " + e); 8: } 9: } 10: public static void main(String args[]) { 11: try { 12: int a = args.length; 13: system.out.println("a = " + a); 14: int b = 42/a; 15: procedure(); 16: } 17: catch(arithmeticException e) { 18: System.out.println("div by 0: " + e); 19: } 20: } 21: }

【运行结果】 如图 7.5 所示。

图 7.5

【例 7.7】的运行结果

【程序说明】 成 员 函 数 procedure() 里 有 自 己 的 try/catch 控 制 , 所 以 main() 不 用 去 处 理 ArrayIndexOutOf- BoundsException。

7.5

抛出异常 在执行期间,若引发一个 Java 系统能够识别的错误,就会产生一个相对应的异常类对象,

这个过程称为抛出异常。


7.4

捕 获 异 常

85

1.throw 语句 throw 语句用来明确地抛出一个“异常” 。首先,必须得到一个 Throwable 的实例的控制柄, 通过参数传到 catch 子句,或者用 new 操作符来创建一个。下面是 throw 语句的通常形式: throw ThrowableInstance; 【语法说明】 程序会在 throw 语句后立即终止,它后面的语句执行不到,然后在包含它的所有 try 块中从 里向外寻找含有与其匹配的 catch 子句的 try 块。 例 7.8 给出了一个含有 throw 语句的例子。 【例 7.8】ThrowDemo.java 1: class ThrowDemo{ 2: static void demoproc(){ 3: try { 4: throw new NullPointerException("抛出异常示例"); 5: } 6: catch(NullPointerException e) { 7: System.out.println("捕捉过程内部的异常"); 8: throw e; 9: } 10: } 11: public static void main(String args[]) { 12: try { 13: demoproc(); 14: } 15: catch(NullPointerException e) { 16: system.out.println("捕捉抛出异常: " + e); 17: } 18: } 19: }

【运行结果】 如图 7.6 所示。

图 7.6

ThrowDemo.java 的运行结果

2.throws 语句 throws 用来标明一个成员函数可能抛出的各种异常。对大多数 Exception 子类来说,Java 编 译器会强迫你声明在一个成员函数中抛出的异常的类型。如果异常的类型是 Error 或 RuntimeException,或是它们的子类,则这个规则不起作用,因为这些在程序的正常部分中是不期待出 现的。如果你想明确地抛出一个 RuntimeException,你必须用 throws 语句来声明它的类型。这 就重新定义了成员函数的定义语法:


第7章

86

异 常 处 理

type method-name(arg-list) throws exception-list { } 【例 7.9】抛出但不捕捉异常的例子。 1: 2: 3: 4: 5: 6: 7: 8: 9:

class ThrowsDemo1{ static void procedure( ){ System.out.println("内部过程"); throw new IllegalAccessException("抛出异常示例"); } public static void main(String args[]){ procedure( ); } }

【程序说明】 上面的程序抛出了一个异常,但既没有捕捉它,也没有用 throws 来声明,这在编译时将不 会 通 过 。 为 了 让 这 个 例 子 通 过 编 译 , 我 们 不 但 需 要 在 成 员 函 数 procedure 里 抛 出 异 常 IllegalAccess- Exception,还需要在调用它的成员函数 main()里捕捉它。 【例 7.10】例 7.9 修改为使用 throws 的例子。 1: 2: 3:

class ThrowsDemo{ static void procedure( ) throws IllegalAccessException{ System.out.println("内部过程");

4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: }

throw new IllegalAccessException("抛出异常示例"); } public static void main(String args[]){ try{ procedure( ); } catch (IllegalAccessException e){ System.out.println("捕捉异常 " + e); } }

【运行结果】 如图 7.7 所示。

图 7.7

【例 7.10】的运行结果


7.5

7.6

抛 出 异 常

87

自定义异常 自定义异常是那些不能由 Java 系统监测到的异常(下标越界,被 0-除等) ,而是由用户自己

定义的异常。用户定义的异常同样要用 try--catch 捕获,但必须由用户自己抛出,格式为: throw new MyException 由于异常是一个类,用户定义的异常必须继承自 Throwable 或 Exception 类,建议用 Exception 类。如: public class MyException extends Exception{//类体 }; 例 7.11 给出一个计算两个数之和的例子。当一个数超出取值范围,则抛出一个异常。 【例 7.11】public class NumberRangeException extends Exception{ 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:

public NumberRangeException(String msg){ super(msg); } public boolean action(Event evt,Object arg){ try { int answer = CalcAnswer(); answerStr = String.valueOf(answer); }catch (NumberRangeException e){ answerStr = e.getMessage(); } //repaint(); return true; } public int CalcAnswer() throws NumberRangeException{ int int1, int2; int answer = -1; String str1 = textField1.getText(); String str2 = textField2.getText(); try { int1 = Integer.parseInt(str1); int2 = Integer.parseInt(str2); if ((int1<10)||(int1>20)||(int2<10)||(int2>20)){ NumberRangeException e=new NumberRangeException("Numbers not within the specified range."); 25: throw e; 26: } 27: answer = int1 + int2; 28: }catch (NumberFormatException e){ 29: answerStr = e.toString(); 30: } 31: return answer; 32: } 33: }


第7章

88

异 常 处 理

本 章 小 结 处理程序执行过程中出现的各种情况,是程序设计中非常重要的一个环节。只有那些能将各 种问题充分考虑的程序,才能长期稳定的执行下去。 本章的内容,涵盖了异常处理的基本概念以及 Java 中的异常处理机制,将异常处理妥善的 运用到程序中,就可提高程序的稳定性。

练习与思考 7.1

在 Java 程序执行过程中,产生的异常通常有几种类型?

7.2

简要说明 Java 的异常处理机制。

7.3

异常类的最上层是什么类?它又有哪两个子类?


8.2

第8章

java.lang 包

89

Java API 简介

学习目标  了解 Java API 的基本概念和 API 中的常用包的功能  掌 握 java.lang 包 中 的 java.lang.System java.lang.StringBuffer 类、java.lang.Math 类

类 、 java.lang.String

类 、

掌握 java.util 包中日期类 Date、随机数类 Random、Java 输入输出类、输入流与输出流、 文件输入输出和缓冲区输入输出 所谓应用程序接口 API(Application Program Interface)实际上就是一些已经写好、可供 直接调用的类库。Java 功能强大的 API 可以使编写程序不必从头开始,这也正是面向对象程序设 计的优点所在。 本章先介绍 Java API 中主要的包(Package) ,这些包中常用的接口(Interface) 、类(Class) 、 方法(method)的主要功能,然后通过举例说明它们的使用方法。

8.1

Java API 中的包 Java API 是以包的形式组织起来的,熟悉和掌握这些包是学习 Java 的重要工作之一,表 8.1

列出 Java API 中主要的包。 表 8.1 包

API 中常用的包

功 能 说 明

java.awt

包含所有制作用户界面以及绘图、影像所需的类

java.beans

接口组件(Java Beans)发展所需之类

java.io

通过数据流,连续性文件系统为系统提供输入输出

java.lang

Java 程序语言设计的基础类

java.math

提供任意精确度的整型及浮点数运算

java.net

网络应用程序类

java.rmi

远程方法调用(RMI)类

java.security

与网络安全有关的类,如存取控制、数据加密等

java.sql

使用 Java 程序语言存取及处理数据库

java.text

处理文字、日期、数字、信息的类

java.util

公用程序类,如随机数产生器、日历、时钟等

java.swing

一套新的可视化组件,在所有平台上效果一样


第8章

90

Java API 简介

为进一步说明,表 8.2 又按照功能对上述包进行了分组。 表 8.2

常用 Packages 的分组

分 类 名 称

基本类

java.lang,java.io,java.math,java.util,java.text

图形接口类

java.awt,javaxswing,java.applet

数据库类

java.sql

网络程序设计类

java.net,java.security,java.servlet,java.rmi

其他类

java.beans,java.corba

包必须通过 import 语句引入,并经系统加载后,其中的类才能直接被程序所使用。这一章 节中,我们主要对基本类进行介绍,对于其他的类,将在后续相关的章节中进行讲解。

8.2

java.lang 包

java.lang 包是 Java 中最重要的包,它在 Java 虚拟机运行时自动被加载,因此它可直接使 用,不需 import 语句引入。java.lang 包中的类比较多,这一节仅介绍常用 System 类、String 类、StringBuffer 类。

8.2.1

java.lang.System 类

System 类提供了标准输入输出、运行时的系统信息等重要工具。System 类不能创建对象, 即 System 类的所有属性和方法都是静态的,引用时要以 System 作为前缀。 System.in 与 System.out 是 System 类的两个静态属性,分别对应了系统的标准输入与输出。 标准输入流也称控制台输入流,用于程序输入,通常读取用户从键盘输入的信息。标准输出流又 称为控制台输出流,用于程序输出,通常向用户显示信息;System.err 称为标准错误流,用于是 向用户显示错误信息。 表 8.3 方

System 类中输入/输出方法

使 用 说 明

public void print()

输出其参数指定的变量或对象

public void println()

输出其参数指定的变量或对象,并换行

public void read()

从输入流读入一字节

public void read(byte[])

把 b.length 个字节放入到一个字节数组中

public void off,int len)

read(byte[],int

把 len 个字节读入到一个字节数组中

System.in.read()必须放在 try 语句块中,而且 try 语句块的后面应该有一个可以接收


8.2

java.lang 包

91

IOException 异常的 catch 语句块。 例 8.1 对 System 类中的 read()和 print()方法的用法进行了演示。程序运行时,首先提示 输入一个字符,然后从键盘读入字符,并从屏幕上显示出来。 【例 8.1】ReadHello.java 1: import java.io.*; 2: public class ReadHello{ 3:

public static void main(String args[]){

4: 5: 6:

char inChar; System.out.println("Enter a character:"); try{

7:

inChar=(char)System.in.read();

8:

System.out.println("

9: 10:

"+ inChar);

} catch(IOException e){

11:

System.out.println("Error reading from user");

12: 13:

} }

14: }

【运行结果】 如图 8.1 所示。

图 8.1

【例 8.1】的运行结果

【程序说明】 程序执行时,提示输入一个字符,输入“d”后,回车,输出刚输入的内容;其中第 7 条语 句完成从键盘读取输入,第 8 条语句完成向屏幕的输出。8.2.2

java.lang.String 类

Java 对字符串的处理使用了 java.lang 包中 String 和 StringBuffer 这两个类。 String 类封装了有关字符串的操作。这里的字符串是常量,即创建后就不可进行修改和变动。 在程序中只做字符串比较、搜索等操作时,通常使用 String 类。 1.字符串常量的创建 字符串数据类型是由 String 类所建立的对象,其内容是由一对双引号括起来的字符序列。 因此,在创建 String 类的对象时,通常需要向 String 类的构造函数传递参数来指定所创建的字


第8章

92

Java API 简介

符串内容。表 8.4 给出了 String 类中的构造函数。 表 8.4 方

String 类中的构造函数

使 用 说 明

public String()

创建一个空的字符串常量

public String(String str)

利用一个已经存在的字符串创建一个 String 对象

public Buf)

String(StringBuffer

利用一个已经存在的 StringBuffer 对象为新建的 String 对象初始化

public String(char str[])

利用已经存在的字符数组的内容初始化新建的 String 对象

2.字符串与其他数据类型的转换 在 java.lang 包中的其他基本类型的派生类中提供将字符串常量转换为字节型、短整型、整 数、双精度小数等其他数据类型进行转换的方法。String.valueOf(value)可将各种数据类型的 value 转换为字符串。表 8.5 给出了字符串的数据类型转换方法。 表 8.5 方

String 类型与其他类型转换方法

使 用 说 明

String.valueOf(value)

将各种数据类型的 value 转换为字符串

Byte.parseByte(String str)

将字符串 str 转换为字节

Short.parseShort(String str)

将字符串 str 转换为短整型

Integer.parseInt(String str)

将字符串 str 转换为整数

Double.parseDouble(String)

将字符串 str 转换为双精度小数

例 8.2 对表 8.4 和表 8.5 中的方法进行了演示。程序把一段字符串转换为大写字符串,然后 再转换为小写字符串,并从中截取一段字符串。最后把双精度小数转换为字符串。 【例 8.2】StringChange.java 1:

import java.lang.*;

2:

import java.io.*;

3:

public class StringChange{

4:

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

5:

String s1=new String("Character String.");

6:

String s2=" Character String. ";

7:

System.out.println(" 转 换 为 大 写 :"+s1.toUpperCase()+"\n

转 换 为 小

写:"+s2.toLowerCase());//改变大小写的方法 8:

char ch[]=s1.toCharArray();//转化成字符数组

9:

System.out.println(String.valueOf(ch,3,5));//从字符数组的一段得到字符串

10:

double d=123.456;


8.2 11:

java.lang 包

93

System.out.println(String.valueOf(d));//从双精度数得字符串

12:

}

13:

}

14: }

【运行结果】 如图 8.2 所示。

图 8.2

【例 8.2】的运行结果

【程序说明】 第 7 行把 s1 转换为大写字符串,把 s2 转换为小写字符串。 第 9~11 行分别演示了 String.valueOf()方法从数组中得到一个字符串和从双精度数中得到 一个字符串。 3.字符串的查找与处理 String 类中提供了求字符串的长度、返回字符的位置、搜索字体串的子串等操作。在整数型 返回值中,若没有找到对应的字符串,则返回-1。 表 8.6 方

String 类中的查找和处理方法

使 用 说 明

int length()

返回字符的个数

char charAt(int index)

返回第 index 位置的字符

int indexOf(int ch)

返回第 1 次出现字符 ch 的位置

int indexOf(int ch,int index)

返回在 index 位置后第 1 次出现字符 ch 的位置

int indexOf(String str)

返回第 1 次出现字符串 str 的位置

int indexOf(String str,int index)

返回在 index 位置后第 1 次出现字符串 str 的位置

String substring(int index)

返回从开始位置到字符串结尾的子串

String end)

substring(int

start,int

返回从开始位置到字符串 end 位置的子串

4.字符串比较和连接 Java 字符串的比较是依据两个字符串中的第 1 个字符的 ASCII 码值的大小来进行的,ASCII 码大的便是最大的字符串,如果两个字符串的第 1 个字符的 ASCII 码值相等则依次比较第 2 个,


第8章

94

Java API 简介

第 3 个...。表 8.7 给出了字符串的比较和连接方法。 表 8.7 方

String 类中的比较和连接方法

使 用 说 明

int compareTo(String str)

比较字符串对象 str 的大小

String concat(String str)

返回字符串对象与 str 连接后的字符串

String new)

replace(char

old,char

将字符串中的 old 字符替换成 new 字符

String trim()

返回删除字符串对象前后空格后的字符串

String toLowerCase()

返回字符串对象所有字符转换为小写后的字符串

String toUpperCase()

返回字符串对象所有字符转换为大写后的字符串

例 8.3 演示了表 8.6 和表 8.7 中的方法。该例对两个字符串进行不忽略大小写的大小比较和 忽略大小写的大小比较。然后对字符串进行查找并求子串。 【例 8.3】StingFind.java 1:

import java.lang.*;

2:

import java.io.*;

3:

public class StringFind{

4:

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

5:

String s1=new String("Character String.");

6:

String s2="Character String.";

7:

s2=" "+s2+"";

8:

String s3=s1.replace('i','I');

9:

if(s3.equals(s1))

10: 11:

System.out.println("s3 equals s1"); else

12: 13:

System.out.println("s3 不等于 s1"); if(s3.equalsIgnoreCase(s1))

14: 15:

System.out.println("s3 等于(忽略大小写) s1"); else

16:

System.out.println("s3 不等于(忽略) s1");

17:

System.out.println("首尾除去空格后 s2:"+s2.trim());

18:

System.out.println(s1.charAt(s1.indexOf('i')));

19:

System.out.println(s1.substring(s1.indexOf("r",0),s1.lastI ndexOf("r")));

20: 21: }

}

//求子串


8.2

java.lang 包

95

【运行结果】 如图 8.3 所示。

图 8.3

【例 8.3】的运行结果

【程序说明】 (1)程序第 7 行到 8 行,对字符串进行变化处理。 (2)程序第 9~13 行,判断字符串 s1、s3 是否相等。 (3)程序第 18~19 行,应用了 indexOf()方法。该方法求出某个字符在字符串中第一次出现 的位置,它的参数是 int 型的,返回值也是整型。

8.2.3

java.lang.StringBuffer 类

StringBuffer 类则是动态可变的字符串缓冲,它提供了一系列方法(见表 8.8) ,把不同类 型(如整型、布尔型等)数据插入缓冲或追加在缓冲的末尾。 表 8.8

StringBuffer 类中的常用方法

public StringBuffer()

创建一个空的字符串对象,初始容量为 16 个字符

public StringBuffer(String str)

创建一个初始值为 str 的字符串对象

public StringBuffer.append(object obj) public StringBuffer.insert(int,object obj) public StringBuffer.setLength(int num)

在原对象 StringBuffer 字符串对象后追加 obj 在指定位置插入给出的参数 obj 该方法强制限定字符缓冲的大小为 num

例 8.4 演示了对表 8.8 中的方法的运用。程序建立了两个字符串对象,并对对象进行了添加、 插入工作。最后对设置缓冲区尺寸后的字符串进行了输出。 【例 8.4】StringBufferDemo.java 1: import java.lang.*; 2: import java.io.*; 3: public class StringBufferDemo{ 4: 5:

public static void main(String args[]) throws IOException{ boolean b1=true;


第8章

96 6:

Java API 简介

int i=100;

7:

long l=200000;

8:

String s1=new String("Character String.");

9:

StringBuffer sbuf1=new StringBuffer(s1);

10:

System.out.println("sbuf1's value:"+sbuf1);

11:

sbuf1=sbuf1.append(" ").insert(0,"

12:

System.out.println("sbuf1's value(插入空格):"+sbuf1);

13:

StringBuffer sbuf2=new StringBuffer(10);

14:

System.out.println("sbuf2'value:"+sbuf2.append(b1).insert(2

");

,i).insert(5,l)); 15:

try{

16:

sbuf2.setLength(9);//设置缓冲区长度

17:

System.out.println("sbuf2's value(设置长度):"+sbuf2);

18:

}catch(StringIndexOutOfBoundsException ex){

19:

System.out.println("String index out of bounds.");

20: 21:

} }

22: }

【运行结果】 如图 8.4 所示。

图 8.4

【例 8.4】的运行结果

【程序说明】 (1)程序第 11 行,用了 StringBuffer 的 append()和 insert()方法。 (2)第 14 行中给出了一些例子,演示如何将布尔值、整数、长整数放入缓冲区。 (3)第 16~20 行演示了 StringBuffer 的 setLength()方法,该方法强制限定字符缓冲的大 小。若设小了,会发生字符的丢失(我们的例子中有意制造了这种情况),设大了,多出来的字 符会置为零值。

8.3

java.lang.Math 类 Math 类主要完成一些常用的数学运算,它提供了基本的科学运算函数的方法,这些方法


8.3

java.lang.Math 类

97

都是公共的和静态的,可以直接使用类名作前缀调用这些方法。表 8.9 列出了 Math 类的主要方 法。 表 8.9

Math 中一些数学常数与方法

常数与方法

使 用 说 明

double E

数学常量

double PI

圆周率常量

double abs(double a)

返回 a 的绝对值

double max(double a,double b)

返回 a 和 b 中的较大值

double min(f loat a,f loat b)

返回 a 和 b 中的较小值

double pow(double a,double n)

返回 a 的 n 次方的值

double random()

产生 0 到 1(不含 1)之间的随机双精度数

double sqrt(double a)

返回 a 的平方根

long round(double a)

返回 a 的四舍五入后的长整数

ceil(double)

返回大于等于 double 型数的最小整数

f loor(double)

返回小于等于 double 型数据的最大整数

rint(double)

把 double 值转化为双精度格式的整数值

log(double)

取自然对数

例 8.5 演示了表 8.9 中的方法。输出常数及 ceil(),f loor(),log(),rint(),pow(),sin(), random()方法运算后的结果 【例 8.5】MathDemo.java 1:

import java.lang.*;

2:

import java.io.*;

3:

public class MathDemo{

4:

public static void main(String[] args){

5:

System.out.println("Math.E="+Math.E); //Math.E 为常量

6:

System.out.println("Math.PI="+Math.PI);

7:

System.out.println("ceil(E)="+Math.ceil(Math.E)); //Math 方法

8:

System.out.println("floor(E)="+Math.floor(Math.E));

9:

System.out.println("lnE="+Math.log(Math.E));

10:

System.out.println("rint(PI)="+Math.rint(Math.PI));

11:

System.out.println("sin(pi/4)="+Math.sin(Math.PI/4));

12:

try{


第8章

98

Java API 简介

13:

double rand=Math.random();

14:

double rand2=-Math.random();

15:

System.out.println("power("+rand+","+rand2+")="+Math.pow(rand, rand2)); //求幂

16:

}

17:

catch(ArithmeticException ex){

18:

System.out.println("ArithmeticException occured!");

19:

}

20: 21:

} }

【运行结果】 如图 8.5 所示。

图 8.5

【例 8.5】的运行结果

【程序说明】 该例演示了 Math 类的使用。在运行引程序时,最后一行输出可能与图示不同,这是随机数 发生器引起的。 Math 类中 random()方法功能有限。java.util 类库中的 Random 类具有更为丰富的功能,我 们将在后面的章节中介绍。

8.4

java.util 类 java.util 是 Java 的实用工具类包。这个包提供了一些如日期(Date)类、随机数(Random)

类,堆栈(Stack)类等,为程序设计提供了便利。java.util 包中还有其他一些类,其具体用法 用户可以查阅 API 相关书籍。下面将对几个常用的类作具体介绍。

8.4.1

日期类 Date

Java 在日期类中封装了有关日期和时间的信息,用户可以通过调用相应的方法来获取系统时 间或设置日期和时间。常用的 date 类的主要方法见表 8.10。


8.4 表 8.10 方

java.util 类

99

date 类的常用方法

public Date()

创建日期类对象

public Date (long date)

按 long 型的 date 产生一日期对象

public Date(String s)

按字符串 s 产生一日期对象

public Date(int year,int month,int date)

按给定的年月日创建一日期对象

public Date(int year,int month,int date,int hrs,int min) public Date(int year,int month,int date,int hrs,int min,int sec)

按给定的年月日时分创建一日期对象 按给定的年月日时分秒创建一日期对象

public static long parse(String s)

将字符串 s 转换成一个 long 型的日期

public String toLocaleString()

将日期对象转换成 Local 格式的字符串

public String toString()

将日期对象转换成字符串

public String toGMTString()

将日期对象转换成 GMT 格式的字符串

public void setMonth(int month)

设定月份值

public int getMonth()

获取月份值

public int getTimezoneOffset()

获取日期对象的时区偏移量

例 8.6 演示了表 8.10 中 date 类的方法。对 Date()、toLocaleString()、getYear()等方法 的调用结果进行输出。 【例 8.6】DateApp.java:date 类的常用方法举例 1: import java.lang.System; 2: import java.util.Date; 3: public class DateApp{ 4:

public static void main(String args[]){

5:

Date today=new Date();

6:

System.out.println("标准格式日期: "+today);

7: 8: 9:

System.out.println("系统格式日期"+today.toLocaleString()); System.out.println("当年是第几年: "+today.getYear()); System.out.println("当月是第几月: "+(today.getMonth()+1));

10:

System.out.println("当日是第几日: "+today.getDate());

11:

Date day1=new Date(103,10,11,10,12,34);

12:

System.out.println("给定年月日时分创建日期: "+day1);

13:

Date day2=new Date("Tue 11 Nov 2003 10:12:34");

14:

System.out.println("给定字符串 s 产生一日期: "+day2);

15:

}


第8章

100

Java API 简介

16: }

【运行结果】 如图 8.6 所示。

图 8.6

【例 8.6】的运行结果

【程序说明】 (1)第 5 行 today 中的日期被设成创建时刻的日期和时间。第 6 行返回一般的时间表示法。 第 7 行返回结果为本地系统格式时间表示法。 (2)第 8~10 行调用 Date 类中方法,得到当前的年、月、日。 (3)第 11~14 调用了两种不同的构造函数来创建 Date 类的对象。

8.4.2

随机数类 Random

Java 实用工具类库中的类 java.util.Random 提供了产生各种类型随机数的方法。它可以产 生 int、long、f loat、double 以及 Gaussian 等类型的随机数。这也是它与 java.lang.Math 中 的方法 Random()最大的不同之处,后者只产生 double 型的随机数。类 Random 中的方法比较简单 (见表 8.11)。 表 8.11

类 Random 中常用的方法

public Random(long seed)

产生一个以 seed 为基值的随机数,基值缺省时,以系统时间为 seed

public synonronized void setSeed(long seed)

设定基值 seed

public int nextInt()

产生一个整型随机数

public long nextLong()

产生一个 long 型随机数

public f loat nextFloat()

产生一个 Float 型随机数

public double nextDouble()

产生一个 Double 型随机数

例 8.7 对表 8.11 中的方法进行演示。程序的主要功能是输出两组随机数,第一组为不同类 型的随机数;第二组为同种类型不同的随机数。 【例 8.7】RandomApp.java。


8.4

java.util 类

101

1: import java.lang.*; 2: import java.util.Random; 3: public class RandomApp{ 4:

public static void main(String args[]){

5:

Random ran1=new Random();

6:

Random ran2=new Random(12345);

7:

System.out.println("第一组随机数:");

8:

System.out.println("\t 整型:"+ran1.nextInt());

9:

System.out.println("\t 长整型:"+ran1.nextLong());

10:

System.out.println("\t 浮点型:"+ran1.nextFloat());

11:

System.out.println("\t 双精度型:"+ran1.nextDouble());

12:

System.out.print("第二组随机数:");

13:

System.out.println();

14:

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

15:

System.out.println("\t 第"+i+"个随机数:"+ran2.nextInt()+" ");

16: 17:

} }

18: }

【运行结果】 如图 8.7 所示。

图 8.7

【例 8.7】的运行结果

【程序说明】 (1)第 5、6 行创建了两个类 Random 的对象。其中第一个对象是以系统时间为随机种子,第 二个对象是以 12345 作为随机种子。 (2)第 8~11 产生各种类型的随机数。 (3)第 14~16 产生 4 个同种类型的不同的随机数。


第8章

102

8.5

Java API 简介

Java 输入输出类 Java 对 I/O(系统输入/输出)提供了全面支持,使很多工作得到的简化。下面将介绍如何

利用这些支持来完成各种复杂的输入、输出。

8.5.1

输入流与输出流

字节输入流 InputStream 与字节输出流 OutputStream 是两个抽象类。它们为 java.io 包中 的字节输入和输出流打下了基础。由于是抽象类,它们不能被实例化(也就是说,不能得到其对 象),但它们的方法可以被派生类所继承或重写。 对于字符流,相应的流类是 Reader 和 Writer。它们的方法与 InputStream 和 OutputStream 对应,只是把对字节的操作改为对字符的操作。InputStream 的方法见表 8.12。 表 8.12 方

类 InputStream 中常用的方法

使 用 说 明

public abstract int read()

从输入流中读一字节数据,当遇到文件尾时,返回-1

public int read(byte b[])

从输入流中读一组字节数据,当遇到文件尾时,返回-1

public int read(byte b[],int offset,int length)

从输入流中读数据。offset 是指把结果放在 b[]中从第 offset 个字节开始的空间,length 为长度

public void close()

关闭输入流并释放资源

public long skip (long n)

从输入流跳过几个字节

表 8.13 给出了 OutputStream 的常见方法。 表 8.13 方

类 OutputStream 中常用的方法

使 用 说 明

public abstract void write(int b)

向输出流写一字节数据

public void write(byte b[])

向输出流写一组字节数据

public void write(byte b[],int offset,int length)

向输出流写数据,offset 是指 b[]中从第 offset 个字节开 始写,length 为长度

public void flush()

清除缓冲区,将缓冲区内尚未写出的数据全部输出

public void close()

关闭输出流,释放资源

8.5.2

文件输入输出

文件输入输出可分为多个类别,对字节流类来说,包括把文件作为源进行流式输入的 FileInputStream 类;把文件作为目的进行流式输出的 FileOutputStream 类;随机存取文件类,


8.5

Java 输入输出类

103

即在文件的任意位置读、 数据的 RandomAccessFile 类。 字符类则包含有 FileReader 和 FileWriter 类。它们的功能对应于前两个字节流类。 除此之外,还有两个类是与文件访问有关的:用以访问文件或目录的 File 类;封装了操作 系统用以追踪被访问文件的信息的 FileDescriptor 类。 下 面 将 先 介 绍 文 件 输 入 输 出 类 库 , 然 后 结 合 实 例 讨 论 File , FileInputStream , FileOutputStream 和 RandomAccessFile 类的方法与使用。 1.File 类 File 类的主要方法见表 8.14。 表 8.14 方

类 File 中常用的方法

使 用 说 明

public File(String path)

根据文件名创建实例

public File(String path,String name)

根据文件路径与文件名创建实例

public File(File dir,String name)

根据文件对象(目录)与文件名创建实例

public boolean exists()

判断文件是否存在

public boolean canRead()

判断文件是否可读

public long length()

返回文件长度

public boolean mkdir()

创建目录

public boolean renameTo(File dest)

文件改名

2.FileInputStream 类 文件输入流类的主要方法 read()、skip()、available()、close()分别重写了抽象类 InputStream 的同名方法,功能类似。其他方法见表 8.15。 表 8.15 方 public fileName)

类 FileInputStream 中常用的方法

FileInputStream(String

public FileInputStream(File file) public FileInputStream(FileDescriptor fd)

使 用 说 明 根据文件名创建一个文件输入流 根据文件对象创建一个文件输入流 根据文件描述符创建一个文件输入流

public final FileDescriptor getFD()

返回相应的文件描述符

protedted void finalize()

关闭输入流,并收集无用内存空间

3.FileOutputStream 类 文件输出流。有三个构造函数,其参数、返回值及异常均与 FileInputStream 的相对应。 write() 、 close() 方 法 重写 了 OutputStream 的 同名 方 法 。 getF D() 与 finalize() 功 能 与


第8章

104

Java API 简介

InputStream 的类似。 4.ReadomAccessFile 类 该类用于随机访问文件。 构造函数有二种: public RandomAccessFile(String Filename,String mode) public RandomAccessFile(File file,String mode) 此类的成员方法很多。除了重写 InputStream 的 read()方法之外,还可以读、写一个布尔值、 一个字节、一个整数......等对象。这些方法都不可重写,并且抛出 I/O 异常(IOException) 。 读方法名为“read”加类型名(类型名的第一字母大写),写方法名为“write”加类型名。如 readInt()读一个整型数,writeDouble()写一个双精度浮点数等。 例 8.8 是一个文件的输入输出流例子。其功能是创建一个输入输出流,从源文件读入字符并 写入新创建的文件中。 【例 8.8】fileIODemo.java。 1:

import java.io.*;

2:

import java.lang.*;

3:

public class fileIODemo{

4:

public static void main(String args[]){

5:

try{

6:

FileInputStream inStream = new FileInputStream("test.txt");//创 建文件输入流对象

7:

FileOutputStream outStream = new FileOutputStream("text.txt");

8:

boolean eof = false;

9:

while(!eof){

//创建文件输出流对象

10:

int c = inStream.read();

11:

if(c==-1) eof = true;

12:

outStream.write((char)c);

//从输入流中读入一个字符 //向输出流中写入一个字符

13:

}

14:

inStream.close();

//关闭输入流

outStream.close();

//关闭输出流

15: 16:

}catch(FileNotFoundException ex){

17: 18:

System.out.println("没有找到文件"); }catch(IOException ex){

19:

System.out.println("IO 异常.");

20:

}

21:

try{

22:

RandomAccessFile rafile = new RandomAccessFile ("text.des",

23:

rafile.seek(0);

"rw");//创建文件随机访问对象


8.5

Java 输入输出类

24:

boolean eof=false;

25:

System.out.println("目标文件内容:");

26:

while(!eof){

27:

int c = rafile.read();

28:

if(c==-1) eof = true;

29:

105

//读文件

else System.out.print((char)c);

30:

}

31:

rafile.seek(0);

32:

rafile.skipBytes(3);

33:

System.out.println("\n 指示器位置:"+rafile.getFilePointer());

34:

System.out.println("从指示器位置读取文本内容:");

//文件指针指向第 3 个字节

//显示文件当前指针位置 35:

eof=false;

36:

while(!eof){

37:

int c=rafile.read();

38:

if(c==-1) eof=true;

39:

else System.out.print((char)c);

40:

}

41:

System.out.flush();

42:

rafile.close();

43:

}catch(IOException ex){

44: 45:

//强制输出缓冲区中所有内容

System.out.println("随机文件类引起的异常!"); }

46: } 47: }

【运行结果】 如图 8.8 所示。

图 8.8

【例 8.8】的运行结果

【程序说明】 (1)为了充分展示与文件 I/O 相关的类的作用,例子中用了一些输入输出文件。这个程序位 于 C:\java 应用书籍\第 8 章:Java API 简介\路径下,路径下又有一个文件 test.txt。程序运 行后,将在 C:\java 应用书籍\第 8 章:Java API 简介\下创建一个新文件 text.txt,test.txt 的内容被写入此文件。对 File 类的演示说明了文件的部分管理信息。然后我们又使用了 RandomAccessFile,试验了文件在指定位置的读写。


第8章

106

Java API 简介

(2)第 6~7 行创建输入/输出流。第 9~13 行读入输入流并写入输出流 (3)第 21 行中创建 RandomAccessFile 对象,以便随机读写。可用文件名加读写方式或 File 对象加读写方式来创建其对象。其中读写方式用“r”表示只读,“rw”表示可读写。 (4)第 23~30 行、第 36~40 行从当前指针开始把文件输出。 (5)第 41 行的 Sytem.out.flush()语句不可以被省略,flush()的作用就是把缓冲区中数据 全部输出,将数据输出到输出流以后,某些输出流(有缓冲区的流)只是把数据写进了缓冲区而 已,不会立即写到目的地。如果不强制输出,部分数据可能就来不及在程序结束前输出了。第一 次用 RandomAccessFile 读文件时,输出语句后面没有 flush(),是因为 System.out 返回的 PrintStream 可以将缓冲区中的内容自动清除出去。因此许多地方就不必加 flush()了。 PrintStream 的这个特点,在其创建对象时是可以更改的。

8.5.3

缓冲区输入输出

字节流类有 ByteArrayInputStream 类,将字节数组转化为输入流,或从一个字符串创建输 入 流 。 字 符 流 类 有 CharArrayReader , CharArrayWriter , StringReader , 此 外 还 有 一 个 StringWriter 用来写字符串。 缓冲区 I/O,对字节流来说指的是 ByteArrayInputStream 和 ByteArrayOutputStream 类的运 用。字符流的使用与字节流类类似。 1.ByteArrayInputStream 类 这个类用于从一个字节数组取得输入数据。它的主要方法见表 8.16。 表 8.16 方

类 ByteArrayInputStream 中常用的方法

使 用 说 明

public ByteArrayInputStream(byte Buf[]) public ByteArrayInputStream(byte offset,int length)

由字节数组创建相应的输入流 buf[],int

public synchronized int read() public synchronized offset,intrlength)

int

由字节数组中起点为 offset 长为 length 的一 段创建输入流 读一个字节

read(byte

b[],int

读取多个字节,返值一般为读到的字节数。但 读到末尾时返回-1

public synchronized long skip(long n)

跳过 n 个字节。若返回值不等于 n,可能是遇到 末尾

public synchronized int available()

求得缓冲区内字节数目

public synchronized void reset()

该指针重新设置到输入流的开始处

2.ByteArrayOutputStream 类 这个类用于把数据写进字节数组(缓冲区),其主要方法见表 8.17。 表 8.17

类 ByteArrayOnputStream 中常用的方法


8.5 方

Java 输入输出类

107 使 用 说 明

public ByteArrayOutputStream()

把数据写进字节数组

public ByteArrayOntput Stream(int size)

把数据写进字节数组,size 指定缓冲区的初始大 小

public synchronized void write(int b)

写一个字节

public synchronized offset,int length)

void

write(byte

b[],int

把数组 b 中由 offset 开始长为 length 的一部分 写入缓冲区 续表

使 用 说 明

public synchronized void writeTo(OutputStream out)

把缓冲区内容写到另一输出流 out

public synchronized void reset()

指针定到缓冲区开始

public syschronized byte[] toByteArray()

将缓冲区内容作为一个字节数组返回

public int size()

当前缓冲区大小

public string toString()

把缓冲区内容转化为字符串

3.StringBufferInputStream 类 它的构造函数以一个字符串为参数,原型为: public StringBufferInputStream(String s) 其余成员变量及方法均与 ByteArrayInputStream 的同名且基本功能相似。 这三个类的共性是内存中开辟了一段空间来做 I/O 缓冲区,故称缓冲区 I/O 类。 例 8.9 给出了一个缓冲区 I/O 实例, 其功能是把字节数组的内容写进一个字节数组输出流对 象。然后,用一个字节数组输入流对象读数据,并显示到屏幕上。这个例子主要用来演示字节数 组 I/O 的部分方法。 【例 8.9】ByteArrayIODemo.java 1:

import java.io.*;

2: public class ByteArrayIODemo{ 3:

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

4:

String s ="This a test";

5:

byte buffer[]=s.getBytes();

6:

ByteArrayOutputStream byteArrayOut = new ByteArrayOutput Stream();

//创建一个字节数组输出流

7:

for(int i=0;i<buffer.length;++i)

8:

byteArrayOut.write(buffer[i]);

9:

ByteArrayInputStream inStream = new ByteArray InputStream (byte Array Out.toByteArray());//创建一个字节数组输入流

10:

boolean eof=false;


第8章

108 11:

Java API 简介

while(!eof){

12:

int c=inStream.read();

13:

if(c==-1) eof=true;

14:

else System.out.print((char)c);

15:

}

16:

System.out.print("\n 'writeTo'方法可以产生相同结果:");

17:

byteArrayOut.writeTo(System.out);

18:

byteArrayOut.close();

19:

System.out.println("\n 缓冲输入流的长度(未定指针):"+inStream. available());

20: 21:

inStream.reset(); System.out.println("缓冲输入流的长度:"+inStream.available());

22: 23:

inStream.close(); }

24: } 25: }

【运行结果】 如图 8.9 所示。

图 8.9

【例 8.9】的运行结果

【程序说明】 (1)程序显示了 writeTo()方法的作用。另外在 reset()前、后用了两次 available()方法, 请注意两方法先后产生的不同结果。 (2)第 9 行由字节数组创建相应的输入流,第 17 行用 ByteArrayOutputStream 的 writeTo() 方法写。第 20~22 行演示了 available()与 reset()的使用。

本 章 小 结 本章介绍了一些基础的 Java API ,其中重点介绍了 Java.lang 包中的 System 类、String 类、 StringBuffer 类;Math 类;Java.util 包的日期类 Date、随机数类 Random;Java.io 包中的输 入输出流类、文件 I/O 类、缓冲区 I/O 类,并对每个类的调用方法给出了示例。通过这一章学习, 应能够掌握 Java API 中常见的类的方法及功能。


8.5

Java 输入输出类

109

练习与思考 8.1

Java.io 类软件包中有哪 4 个主要的类?它们的主要用途是什么?

8.2

什么叫做流?Java 语言中的流分为哪两种类型?这两种类型的流各有什么特点?

8.3

在 System 类中,标准输入与输出流是如何定义的?

编写一个简单的学生成绩录入程序。要求从键盘输入人数,然后依次输入每个学生的英语、计算机语言和数据库 基础的成绩,最后将输入的学生成绩信息全部显示在屏幕上。


9.1

第9章

客户机/服务器结构

109

Web 服务器和 Applet 程序

学习目标 理解客户机/服务器结构、WEB 服务器、统一资源定位符、超文本传输协议 HTTP 和 Applet 的生命周期  理解 TOMCAT 服务器的安装与配置  掌握 HTML 表单设计基础  掌握在页面中加入 Applet 和传递参数给 Applet 的方法 前面几章已介绍过,Java 程序分为 Application、Applet 和 Servlets 三类。其中 Applet 小 程序是从 Web 服务器下载到客户端支持 Java 的浏览器上来解释执行的。 在这一章,首先对 Web 服务器的基本概念及其安装进行学习。然后介绍 Applet 的执行过程 及如何接收 HTML 的参数,另外还介绍了 FORM 表单设计及 java.applet 包所提供的支持。通过本 章的学习,应能掌握安装配置自己的 Web 服务器并能开发 Applet 和动态页面。

9.1

客户机/服务器结构 Internet 上的计算几乎都采用客户机/服务器(Client/ Server)结构的模式。在这种模式

中,客户机通过网络向服务器提出处理的请求,服务器接受请求后,按照要求进行处理,并将处 理的结果再通过网络发回给客户机。 在这里,客户机和服务器既是指硬件,又是指软件。因此,有时把客户机称客户机程序,服 务器称为服务器程序。客户机/服务器结构通常都是在网络环境上实现,如果需要也在同一台计 算机上实现。 图 9.1 强调了客户端与服务器建立通信关系的两个主要步骤,即客户端首先发起链接请求, 而服务器接受请求建立连接。

图 9.1

客户端与服务器建立连接


第9章

110

9.1.1

Web 服务器和 Applet 程序

Web 服务器

WWW(World Wide Web)的中文名称为万维网,它利用客户机/服务器结构进行工作。Web 服 务器与客户机通信采用 HTTP 协议(HyperText Transfer Protocol,超文本传输协议) 。客户机 程序向服务器程序发出请求,服务器程序向客户端程序送回其所需要的 HTML 文档,这些文档即 是我们通常所说的网页。在 Internet 上,客户机通常指用来显示 HTML 文档的 Web 浏览器,Web 服务器软件可以是微软的 IIS(Internet Information Server,Internet 信息服务器)或开放 源代码的 Apache 等。

9.1.2

统一资源定位符

Internet 上的资源或对象用统一资源定位符 URL(Uniform Resource Locator)来进行描述, URL 是对可以从 Internet 上得到的资源的位置和访问方法的一种简洁的表示。URL 给资源的位置 提供了一种抽象的识别方法。其一般形式如下: <协议名称>://<用户>:<密码>@<主机>:<端口>/<路径> 其中各部分含义为: (1)<协议名称>说明 URL 的访问方式,主要有 ftp、http、gopher、mailto、telnet 等。 (2)<用户>:<密码>表明登录的用户的身份。 (3)<主机>:<端口>/<路径>表明所访问的 Internet 主机的详细地址。

9.1.3

超文本传输协议 HTTP

HTTP 是实现 Web 服务器和浏览器之间通信、交流信息的协议。HTTP 是基于客户/服务器进程 通信的基本模式,即请求/回答模式。当一个用户点击网页中一个链接或一个图标, (假设其 URL 是 http://www.yahoo.com.cn/)后,将发生如下事件: (1)浏览器分析链接指向的 URL; (2)浏览器向 DNS 请求解析 www.yahoo.com.cn 的 IP 地址; (3)DNS 应答主机的 IP 地址为 202.197.96.2; (4)浏览器与服务器建立连接,然后浏览器发出取 index.html 文件的命令; (5)Web 服务器响应请求,发送文件 index.html 给浏览器; (6)连接断开; (7)浏览器显示 index.html 中的所有文本及图像内容。 一个 HTTP 请求由请求行、通用首部、请求首部、实体首部和实体主体组成。请求行包含有 请求方式、文档位置和协议版本。通用首部指明连接的相关信息。请求首部包含有 HTTP 题头, 主要是向服务器传送有关的请求,以及客户本身的其他信息。实体首部指明实体的一些信息。实 体主体包含传送给服务器的其他数据。只有在向 Web 服务器提交表单时,实体主体通常才包含信 息。 下面是一个 HTTP 请求的例子: GET /index.html HTTP/1.0

请求行

Mime version:1.0

通用首部


9.1

客户机/服务器结构

Accept:text/html

请求首部

User-agent:MacWeb …

用户代理的信息

111

对应的,Web 服务器的响应可以是: http/1.0 200 OK Server:Apache/1.1

应答头 应答

Mime version:1.0

通用首部

Content-type:text/html Content-length:2000 <HTML>

实体数据(HTML 文档)

Welcome to the Yahoo Server … </HTML> 在上面的例子中,请求行使用的请求方式是 GET 方式,请求的文档是 index.html,协议是 HTTP Version 1.0。另外客户端还向服务器传送了用户接受题头和代理程序题头。Web 服务器响 应的格式由状态行、响应首部、一个空行和主体部分组成。状态行包含协议版本、状态以及相应 的解释。响应首部包含 HTTP 响应头部域,主要是向客户端传送有关响应和服务器自身的其他信 息。主体部分包含客户端请求的文档或目标。 GET 方式用于提取来自服务器的信息,主要用来提取 Web 服务器的文档。这个方式仅仅是请 求,在实体中不向服务器发送任何信息。服务器返回的文档可以是静态的 HTML 文档,还可以是 CGI 程序产生的输出结果。 另外一种常用的请求方式是 POST 方式。POST 方式允许服务器从客户端接收数据。所以当需 要服务器对数据进行处理时,就可以采用 POST 方式以 HTTP 格式向服务器发送数据。这种方式会 在请求的实体中包含向服务器传送的数据内容。

9.1.4

Tomcat 服务器

现在世界上被使用得越来越多的 HTTP 服务器是由美国国家超级计算机应用中心开发的 Apache 服务器。并且 Apache 服务器是开放源代码的,向用户提供了比较规范的范例,这就方便 了普通用户通过阅读源代码来明白 Web 服务器的工作原理,而且还可以根据实际需要对服务器的 特性和要求进行修改和定制。Tomcat 是 Apache 组织提供的一个开放源代码的 servlet 容器,同 样广受用户喜欢,因此应用前景越来越广。 1.Tomcat 服务器简介 Tomcat 是 Jakarta 项目中的一个重要的子项目,被 JavaWorld 杂志的编辑选为 2001 年度最 具创新的 Java 产品(Most Innovative Java Product) ,同时它又是 Sun 公司官方推荐的 servlet 和 JSP 容器(具体可以见 http://java.sun.com/products/jsp/Tomcat/) ,servlet 和 JSP 的最 新规范都可以在 Tomcat 的新版本中得到实现。因此受到越来越多的软件公司和开发人员的喜爱。 2.Tomcat 服务器的安装与配置 Tomcat 较新的版本为 4.0,这个版本用了一个新的 servlet 容器 Catalina,完整的实现了


第9章

112

Web 服务器和 Applet 程序

servlet2.3 和 JSP 1.2 规范。注意安装之前你的系统必须安装了 JDK1.2 以上版本。然后下载 http://www.apache. org/dist/jakarta/jakarta-tomcat-4.0/release/v4.0/jakarta-tomcat-4.0.exe,按照一般的 Windows 程序安装步骤即可安装好 Tomcat,安装时它会自动寻找你的 jdk 和 jre 的位置。 安装后 Tomcat 的目录结构见表 9.1。 表 9.1 目

tomcat 的目录结构

bin

存放启动和关闭 Tomcat 的脚本

conf

包含不同的配置文件,server.xml(Tomcat 的主要配置文件)和 web.xml

work

存放 jsp 编译后产生的 class 文件

webapps

存放应用程序示例,以后你要部署的应用程序也要放到此目录

logs

存放日志文件

lib/japser/commo n

这三个目录主要存放 Tomcat 所需的 jar 文件

在 Tomcat 程序菜单中,选择 Start Tomcat 项,启动,如果出现一个 MS-DOS 窗口,如图 9.2 所示,这意味着 Tomcat 服务器已经启动成功了。

图 9.2

Tomcat 运行界面显示

3.server.xml 配置文件简介 Tomcat 的系统配置信息是放在 server.xml 文件中的,server.xml 文件反映的系统配置 信息见表 9.2。这里仅给出一些常见的配置信息,更具体的配置信息见 tomcat 的文档。 表 9.2 元 server

属 port

server.xml 中一些常见参数的配置 解

指定一个端口,这个端口负责监听、关闭 tomcat 的请求


9.1 shutdown

客户机/服务器结构

113

指定向端口发送的命令字符串 续表

service

Connector (表示客户端和 service 之间的连 接)

性 指定 service 的名字

port

指定服务器端要创建的端口号,并在这个端口监听来自客户端 的请求

minProcessors

服务器启动时创建处理请求的线程数

maxProcessors

最大可以创建的处理请求的线程数

enableLookups

如果为 true,则可以通过调用 request.getRemoteHost()进行 DNS 查询来得到远程客户端的实际主机名,若为 false 则不 进行 DNS 查询,而是返回其 IP 地址

redirectPort

指定当服务器正在处理 http 请求时收到一个 SSL 传输请求后 重定向的端口号

acceptCount

指定当所有可以使用的处理请求的线程数都被使用时可以放 到处理队列中的请求数,超过这个数的请求将不予处理

connectionTimeou

Context (表示一个 Web 应 用程序,通常为 WAR 文件,关于 WAR 的 具 体 信 息 ( 见 servlet 规范)

host (表示一个虚拟主 机) Realm (表示存放用户 名,密码及 role 的 数据库)

name

t Engine ( 表 示 指 定 service 中的请求 处理机,接收和处 理 来 自 Connector 的请求)

指定超时的时间数(以 ms 为单位)

defaultHost

指定缺省的处理请求的主机名,它至少与其中的一个 host 元 素的 name 属性值是一样的

docBase

应用程序的路径或者是 WAR 文件存放的路径

path

表示此 Web 应用程序的 url 的前缀,这样请求的 url 为 http://localhost:8080/path/****

reloadable

这个属性非常重要,如果为 true,则 tomcat 会自动检测应用 程序的/Web-INF/lib 和/Web-INF/classes 目录的变化,自 动装载新的应用程序,我们可以在不重启 Tomcat 的情况下 改变应用程序

name

指定主机名

appBase

应用程序的基本目录,即存放应用程序的目录

unpackWARs

如果为 true,则 tomcat 会自动将 WAR 文件解压,否则不解压, 直接从 WAR 文件中运行应用程序

className

指定 Realm 使用的类名, 此类必须实现 org.apache. catalina. Realm 接口

(1)经过测试,设置 Context 的 path="",reloadable=true,然后放一个 WAR 文件到 webapps 目录,结果 tomcat 不能检测出此文件(重启 tomcat 即可重新检测) ,而把此文件解压,则 tomcat


第9章

114

Web 服务器和 Applet 程序

会自动检测出这个新的应用程序。 (2)默认的 server.xml 中,Realm 元素只设置了一个 className 属性,但此文件中还包含几 个通过 JDBC 连接到数据库进行验证的示例(已被注释掉) ,通过 Realm 元素我们可以实现容器安 全管理(Container Managed Security)。 (3)还有一些元素没有介绍,如 Parameter、loader,可以通过 tomcat 的文档获取这些元素 的信息。

9.2

HTML 表单设计基础 HTML 超文本标记语言(Hypertext Markup Languarg)是用来编写网页的,它定义信息逻辑

的组织方式并提供相关信息的链接(叫超文本链接) 。在进一步学习 Applet 之前,先介绍 HTML 表单有关知识,主要是为在写交互式的网页中应用 Java 打下基础。

9.2.1

Form 语法结构

同其他的 HTML 标识符相同,表单有一个开始标识符和一个结束标识符。在每个表单内你可 以加入任意个的输入元素,诸如文本输入域、按钮、下拉菜单、复选框或可点击图片,也可以使 用 HTML 语言标识符,在标识符之间插入一般的 HTML 内容,像文本和图片。文本可以像表单标签 和提示一样,用来提供如何填写表单的帮助。 用户填写完表单以后,当 Submit 按钮被单击的时候,浏览器将会把所有从用户那里收集的 数据打包并把它传给一个服务进程。服务器将会把这个包发送到适当的目的地,在那里处理数据, 创建一个响应(通常是另一个 HTML 页面)并将响应返回给浏览器。 一个表单可以被放置在 HTML 页面正文部分的任何一个地方,在一个 HTML 页面中也可以放置 多个表单。浏览器把表单元素放置在格式化好的 HTML 页面上,并使小图片嵌入到文本中。表单 元素没有什么特殊的布局规则,因此,你要使用 HTML 语言的格式化元素(如表格)来控制具体 输入元素在 HTML 页面上的布局。 表单标识符的基本语法结构: <Form Action="url" Method="POST" or GET"> Form contents </Form>

9.2.2

Form 属性

HTML 规范中定义了 Form 的几个属性,同时 Microsoft 和 Netscape 还定义了一些扩展的表单 标识符的属性。在这里,我们主要讲解在表 9.3 中的三个属性。 表 9.3 属

是 否 必 需

表单标识符属性 描


9.1

客户机/服务器结构

115

Action

服务器端进程的统一资源定位符(URL),当表单被提交时,相应进程将 用于接收提交的数据

Enctype

指定数据如何被编码以可靠的传输

Method

控制数据以何种方式发送到服务器端

1.Action 属性 Action 属性作为必要属性,定义了服务器端进程的统一资源定位符(URL) ,当数据被提交时 相应进程将用于接收提交的数据。对于使用 SERVLET 来说,URL 将指向在一个特定的服务器上的 一个 Servlet。这个 URL 可以包含一个真实的 Servlet 名称,一个 Servlet 别名,一个经由前缀 调用的 Servlet 或后缀映射表。 下面是一个有关 Action 属性的例子: <Form Action="http://sureness:8080/servlet/myServlet"...> </Form> 还可以将一个电子邮件地址放在 URL 中。在 Action 属性中使用“mailto”URL。这样可以把 所有的表单参数和数据都发送到 URL 所指定的地址中。邮件接收者就可以按照需要处理相应的表 单数据。 下面是一个包含有一个电子邮件 URL 的 Action 属性的例子: <Form Action="mailto:sureness@263.net"...> </Form> 电子邮件的正文部分包括成对出现的窗体参数和它的值: name=Bruce Wayne address=1 bat Cave 注意,如果在 Action 属性中使用了电子邮件 URL,需要注意以下几点: (1)表单将只能在支持“mailto”URL 的浏览器上工作。 (2)当一个表单被提交给一个电子邮件 URL 后,用户应该对可能发生的任何事情都不感到惊 奇。将数据提交给一个脚本程序或是一个 servlet,相应的程序将会反馈回某种类型的确认 HTML 页面。而将数据提交给电子邮件 URL 就与它们不同,用户将只能注视着自己完成的表单。 (3)为了确保表单参数和它们的值是可读的,通常将 Enctype 属性(我们将在下一部分讲述) 设置成“text/plain”。 (4)必须按某种方式来处理在电子邮件中的表单参数值。这就需要一些批处理程序来读取每 个电子邮件,分析出各参数的值,然后再依据这些参数确定要执行的动作。 2.Enctype 属性 Web 浏览器在表单数据发往服务器之前会对它们进行编码。服务器将会对发来的参数进行解 码或者直接将这些参数发给相应的应用程序。缺省的编码方式是 Internet 媒体方式叫做 “application/x-www-form-urlencoded” 。你可以在表单标识符 Enctype 属性中修改这个缺省编 码方式。表 9.4 列出了有效编码方式清单。 表 9.4

表单标识符中 Enctype 属性值


第9章

116

Web 服务器和 Applet 程序

application/ x-www-form-urlencoded

缺省的编码方式。这种编码方式将任何表单参数值中的空格替换为加 号(+),将数字或字母的字符用百分号(%)加两位十六进制的字 符的 ASCII 值替换,将有多个行的值中的换行符用%0D%0A(回车/ 换行符)替换 续表

multipart/form-data

用于包含文件选择域的表单中。数据通过一个有多个域的单独文件传送

text/plain

用于要经由电子邮件方式传送表单参数和数据的情况,表单中的每一个 元素对应传输文件中的一行。这一行中,参数名和它的值用等号隔开。 有多个行的值中的换行符用%0D%0A(回车符/换行符)替换

3.Method 属性 Method 属性是一个必须的属性,它指定 Web 浏览器将表单数据传送到服务器过程中所用的方 法。在传送表单数据时有两种方法可供选择:POST 和 GET。 POST 方法会使 Web 浏览器分两步传送数据。浏览器首先尝试与在 Action 属性中指定的服务 器进行连接,当连接完成,再将表单数据通过这个特定传输通道(separate transmission)传 送给服务器。服务器要以标准方式读取表单的参数。 GET 方法会使 Web 浏览器连接服务器并通过这个单一传输通道传送表单数据。浏览器将会把 表单数据追加到 Action 所指定的 URL 之后(就像命令行参数一样) ,并用问号将 URL 与参数值分 隔开。 通常我们依据下列原则对两种方法进行选择:  如果表单只有少量的输入域,那么就使用 GET 方法,这样可以得到很高的效率。 由于一些服务器限制命令行参数的长度,对于含有超长字符串值参数的表单,则只能选用 POST 方法。  存在安全问题,可以使用 POST 方法。由于 GET 方法把表单数据就像命令行参数一样加在 URL 后传送,因此通过网络探测器或是分析服务器的记录文件都可以很容易地获取所传数据的具 体值。而对于 POST 方法来说,数据将通过特定传输通道传送。 传送附加参数:可以非常方便地把附加参数通过 Action 属性所指定的统一资源地址传送出 去。你只要对附加变量进行编码并把它们按命令行参数的方式添加上去。例如有两个参数名为 “a” 、 “b” ,你可以按照“application/x-www-form-urlencoded”的方法(参看表 9.4) ,将这些 参数编码如下: a=3&b=24 一个使用了上述附加参数的 URL 将会像下面的这个样子。 <Form Action="http://www.myhost.com/servlet/myServlet?a=3&b=24"...> </Form> 注意,这里有一个要点:&符号是一个 HTML 语言的保留符号,它用来指定字符串的插入点。


9.2

HTML 表单设计基础

117

为了解决&符号的传输问题,需要将所有&符号用它的字符数码值代替,也就是替换为&#38 或&amp。 <Form Action="http://www.myhost.com/servlet/myServlet?a=3&ampb=24"...> </Form> 为了避免混淆,一些 Web 服务器要求使用分号(;)分隔参数。

9.2.3

Form 输入元素

通过 Form 的输入元素,网页可以接收用户的输入信息,这里介绍的输入元素主要有以下几 种。 1.Input 标识符 Input 标识符用来定义表单中的输入域,如文本输入域和按钮。Input 标识符的基本语法的 结构如下。 <INPUT TYPE=input type NAME=parameter name [additional attributes]> Type 属性作为必要属性指定所使用输入域类型。而同是必要属性的 NAME 属性则是指定相应 域提供给服务器时的名称。要避免使用任何一个特殊字符(下划线除外)并且参数的首字符要使 用字母。特别注意不要使用+、&或%这些符号。它们在使用“application/x-www-form-urlencoded” 编码方式时都具有特殊的意义。 2.Button 类型 通过使用按钮类型(Button Input Type) ,可以创建一个可以被表单用户单击的按钮,但单 击后并不会提交表单或是生成表单。表 9.5 列出了按钮类型的属性。 表 9.5 属

按钮的属性

是 否 必 需

Name

按钮的名称

Value

按钮上的标签

【例 9.1】Buttons.html 向用户介绍含有多个按钮的实例。 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:

<html> <head> <title>HTML Form Input - Buttons</title> </head> <body> <form> <input type=button name=action value="Next"> <input type=button name=action value="Previous"> </form> </body> </html>


第9章

118

Web 服务器和 Applet 程序

【运行结果】 如图 9.3 所示。 【程序说明】当提交上述表单的时候,一个名为“action”的参数将会被传送到服务器上,它的 值就是按钮上的标识。

图 9.3

【例 9.1】的运行结果

3.File 类型 文件类型(File Input Type)允许用户选择一个本地机上的文件,当 Submit 按钮被按下时 被选文件也将被发送到服务器上。Web 浏览器将创建一个文本输入域,它将接受用户输入,同时 还将创建一个 Browse 按钮,当 Brower 按钮被按下时,它将提供给用户一个依赖于所用平台的对 话框用来选择一个文件。表 9.6 列出了文件类型的属性。 表 9.6 属

文件选择类型属性

是否必需

Accept

设置要选文件的类型。用户可以通过一个用逗号分隔的文件后缀列表选 择特定类型文件,比如“image/”选择所有的图片文件

Maxlength

文件名最大长度(以字符为单位)

Name

文件输入域的名称

Size

输入域的长度(以字节为单位)

Value

缺省文件名

【例 9.2】Infile.html 显示了一个使用文件选择类型的 HTML 示例文件。 1: 2: 3: 4: 5: 6: 7: 8:

<html> <head> <title>HTML Form Input - File Selection</title> </head> <body> <form> My favorite file is: <input type=file name=myfile size=25>


9.2 9: 10: 11:

HTML 表单设计基础

119

</form> </body> </html>

【运行结果】 如图 9.4 所示。

图 9.4

【例 9.2】的运行结果

4.Image 类型 图像类型(Image Input Type)将创建可单击的图像按钮,这个按钮由用户指定的图像创建 出来,当用户单击它的时候,它将提交表单并在同时将鼠标单击时在图像上的位置的 X、Y 坐标 传送给服务器;X、Y 坐标值将会以<name>.x 和<name>.y 的方式传送。也就是说,如果创建了一 个名为“map”的图像类型,鼠标所在位置的 X、Y 坐标将会以 map.x 和 map.y 方式传送给服务器。 表 9.7 列出了单击图像按钮的属性。 表 9.7 属

是否必需

图像按钮类型 描

Align

图像与文字的位置关系:OP,TEXTTOP,MIDDLE,ABSMIDDLE,CENTER,BOTTOM, BASELINE, ABSBOTTOM

Border

以像素为单位指定图像边框的粗细程度

Name

图像按钮的名称

Src

图像的 URL

【例 9.3】Inimage.html 1: 2: 3: 4: 5: 6:

<html> <head> <title>HTML Form Input - Image Button</title> </head> <body> <form>


第9章

120 7: 8: 9: 10: 11:

Web 服务器和 Applet 程序

Click here to submit the form: <input type=image name=submit src="submit.gif" align=middle> </form> </body> </html>

【运行结果】 如图 9.5 所示。

图 9.5

9.3

【例 9.3】的运行结果

Applet 的执行过程 Applet 是嵌入在网页中的 Java 程序,必须要在支持 Java 的浏览器中运行。Applet 利用了

浏览器的资源,当它被装入时,它的外部环境是浏览器预先定义好的执行环境,然后浏览器会根 据 HTML 文本中的信息提供 Applet 所要求的环境,Applet 程序执行后,可以显示图像、演奏声音 等。Applet 就是通过使用这些浏览器所提供的功能来工作的。 Applet 小程序是在 Internet 中传播并在浏览器中运行的一段小 Java 应用程序,它的用途主 要是执行某些动态显示功能,这一节将详细介绍 Applet 的执行过程。 1.Applet 的执行过程 Applet 小程序的解释、执行由支持 Java 的 Web 浏览器完成。Applet 小程序在 Web 浏览器中 的执行过程如图 9.6 所示.

图 9.6

Applet 程序的执行过程


9.2

HTML 表单设计基础

121

Applet 小程序的字节码文件与 HTML 文件保存在 Web 服务器(如 Apache 服务器)中。当用户 在浏览器的地址栏中输入一个 URL 向 Web 服务器发出请求时,浏览器首先将 HTML 文件下载到客 户机,并由浏览器解释 HTML 的各种标记。当遇到有<Applet>…</Applet>标记时,浏览器会根据 Applet 小程序的名字和位置自动把字节码文件从 Web 服务器下载到本地客户机上,并由浏览器自 身的 Java 解释器直接解释执行这个字节码文件。所以 Applet 小程序的执行过程就 是一个网络程序的发布过程。如果 Applet 小程序发生变化,只需要对 Web 服务器上的程序进行 一次修改而不必对每一台运行该 Applet 小程序的客户机都进行修改。 2.Applet 的生命周期 Applet 在 Web 浏览器中执行时是有生命周期的。当 Applet 所在的页面和字节码被浏览器装 入后,Applet 就自动产生。这时系统自动调用 Applet 的 init()方法,进行一些必要的初始化工 作。接着,系统将调用 start()方法启动 Applet 的执行。当浏览器切换到别的页面时,stop() 方法被调用,以终止 Applet 的执行。假如浏览器又切换回 Applet 所在页面,那么运行时系统将 从调用 start()方法开始 Applet 的又一次运行。当浏览器关掉时,Applet 走到了它一生的终点。 系统将先调用 stop()停止它的执行,然后用 destroy()方法来完成资源回收等收尾工作。如果浏 览器重载页面,就会先令 Applet 结束工作(依次调用 stop()和 destroy()) ,然后装入页面(调 用 init()),开始 Applet 的又一次生命。Applet 的生命周期图示见图 9.7。

图 9.7

Applet 的生命周期图

Applet 的方法主要有 init()、start()、stop()、destroy()四种。 init()方法进行初始化操作,如配置用户接口组件、参数取得、加载图像或绘图、建立新线 程等。当 Applet 第一次被加载,或重新加载(reload)时被调用。 start()方法开始执行 Applet,紧接着 init()方法之后被调用;当用户从别的网页又回到 Applet 所在之网页时会被重新调用。 stop()方法暂停 Applet 的执行,当用户离开 Applet 所在的网页时,就会调用此方法。 destroy()方法终止 Applet 的执行,当用户关闭 Applet 所在的网页或浏览器时调用此方法。 意思是 Applet 已经不再被需要了,要释放所分配的资源。在执行此方法时,会先调用 stop()方 法。当重新加载新网页时也要调用此方法,以便释放分配给 Applet 的资源。 小应用程序是在浏览器中运行的,每个小应用程序中必须有一个主类,声明为 public,并且 继承自 java.applet。 除了上面介绍的方法外还有 paint()方法。Applet 小程序首次被装载,以及每次窗口放大、 缩小、刷新时都要调用 paint 方法。paint()是由浏览器调用的,而不是由程序调用的,当程序


第9章

122

Web 服务器和 Applet 程序

希望调用 paint 方法时,用 repaint 命令。

9.4

在页面中加入 Applet

运行 Java Applet 时必须将其字节码嵌入到 HTML 文件中才能够运行。<HTML>和</HTML>这一 对标记标志着 HTML 文件的开始和结束。 同样,在 HTML 文件中嵌入 Java Applet,需要通过使用一组特殊标记<APPLET>和</APPLET>。 嵌入 Java Apple 的格式为: <APPLET CODEBASE = codebaseURL ARCHIVE = archiveList CODE = appletFile ...or...

OBJECT = serializedApplet

ALT = alternateText NAME = appletInstanceName WIDTH = pixels HEIGHT = pixels ALIGN = alignment VSPACE = pixels

HSPACE = pixels

> <PARAM NAME = appletAttribute1 VALUE = value> <PARAM NAME = appletAttribute2 VALUE = value> . . . alternateHTML </APPLET> CODE、CODEBASE 等是 APPLET 标记的属性,它们给浏览器提供有关 Applet 的信息。其中必需 的参数是 CODE、WIDTH 和 HEIGHT。下面分别介绍每一个参数。 (1)CODEBASE = codebaseURL 当 Applet 字节码文件与 HTML 文件保存在不同位置时,该参数使用 URL 格式来指明字节码文 件的位置。如果未指定该参数,则使用文件的 URL。例如: CODE=MyApplet.class CODEBASE=http://sureness:8080/ (2)ARCHIVE = archiveList 这个可选参数描述一个或多个包含有将要“预加载”的类或其他资源。使用 AppletClassLoader 实例通过给定的 CODEBASE 加载这些类。ArchiveList 中的各项用“, ”分隔。由于安全方 面的原因,Applet 的类加载器只能从 Applet 所启动的那个 codebase 中读取信息。这意味着 archiveList 中,与 codebase 位于相同目录或位于其子目录中的形如../a/b.jar 的项将无效, 除非在安全策略文件中显式的声明。 (3)CODE = appletFile 这个必需的参数提供了包含 Applet 类的编译文件名。 该文件是相对于 Applet 的基本 URL 的, 它不能是绝对 URL。CODE 和 OBJECT 两个参数必须有一个存在。值 AppletFile 的形式可以为


9.4

在页面中加入 Applet

123

classname.class 或 packagename.classname.class。 (4)OBJECT = serializedApplet 该属性给出了包含 Applet 序列化表示的文件名,该 Applet 将被序列化恢复。init()方法将 不会再被调用;但是其 start()方法还会被调用。在源对象被序列化时有效的属性不会被重置, 传给该 APPLET 的任何属性都可继续用于该 Applet;因此我们建议严格限制使用,并且序列化 之 前应停止 Applet。 (5)ALT = alternateText 这个可选属性指定当浏览器能识別 APPLET 标记但不能运行 Java Applet 时应该显示的正文 內容。 (6)NAME = appletInstanceName 这个可选参数用于指定 Applet 小程序的名字,从而使得相同页上的 Applet 可以相互传递 信息。 (7)WIDTH = pixels HEIGHT = pixels 这些必要的参数用于设定 Applet 显示区域的初始宽度和高度(以像素为单位) ,但不包括 Applet 显示的任何窗口或对话框。 (8)ALIGN = alignment 这个可选参数指定 Applet 的对齐方式。该属性的可能值与 IMG 标记相同,即 left、right、 top、texttop、middle、absmiddle、baseline、bottom 和 absbottom。 (9)VSPACE = pixels HSPACE = pixels 这些可选参数指定 Applet 高度(VSPACE)和宽度(HSPACE)的像素数。对它们的处理方式 与 IMG 标记的 VSPACE 和 HSPACE 属性相同。 (10)Param 属性 用于参数的传递,将在随后的一节中进行介绍。

9.5

传递参数给 Applet

上一节介绍了嵌入 Applet 小程序的 HTML 标记<APPLET>和</APPLET>。在嵌入 Applet 小程序 时,至少应该包含 3 个参数:CODE,HEIGHT,WIDTH。在<APPLET>和</APPLET>标记中加入<PARAM> 标记,可以从 HTML 文件中传递参数给 Apple 小程序。 <PARAM>标记的格式为: <PARAM NAME=name VALUE=values> 其中,NAME 是参数名,VALUES 是参数值,参数值是要传递给 Applet 小程序的逻辑的字符串。 在 Applet 小程序中读取参数的方法为 getParameter(),该方法一般是在 init()方法中被调 用。getParameter()方法的一般语法格式为: public String getParameter(String name) getParameter()方法将根据参数名获取参数值,并存放在 String 里再返回。如果没有这项


第9章

124

Web 服务器和 Applet 程序

参数,那么该方法获得的值是 null。为了防止这种情况的发生,最好能够在程序中以下列方法加 以控制: String s=getParameter("ParamString"); if(Param_String==null) Param_String=""; 【例 9.4】在 init()方法中接收从 HTML 文件中传递过来的参数。 (1)HelloApplet.html 的源代码 1: <HTML> 2: <HEAD> 3: <TITLE> 4: Hello Web Applet! 5: </TITLE> 6: </HEAD> 7: <BODY> 8: <APPLET code=HelloApplet.class width=200 heigth=60> 9: <PARAM name="vstring" value="Hello,Java Applet "> 10: </APPLET> 11: </BODY> 12:

</HTML>

(2)HelloApplet.java 的源程序代码 1: import java.applet.*; 2: import java.awt.*; 3: public class HelloApplet extends Applet { 4:

String AppletParam="";

5:

public void init() {

6:

AppletParam=getParameter("vstring");

7:

}

8:

public void paint(Graphics g) {

9:

if(AppletParam!=null)

10:

g.drawString(AppletParam+"小程序",5,20);

11: 12:

} }

【运行结果】 如图 9.8 所示。


9.5

图 9.8

传递参数给 Applet

125

【例 9.4】的运行结果

【程序说明】 (1)HTML 文件中嵌入一个名为 HelloApplet 的 Applet 小程序,同时在 Applet 小程序运行时 将向它传递一个参数名为 vstring,取值为“Hello,Java Applet 小程序”的参数。 (2)Applet 小程序利用 getParameter()方法来获得 HTML 文件传递过来的参数,方法中有一 个字符串参数,它代表所希望获得的、在 HTML 文件中由 name 指定的参数名字。方法的返回值是 一个字符串对象,也就是 HTML 文件中由 value 指定的字符串。

本 章 小 结 Internet 采用了客户机/服务器的结构,Internet 上的 Web 服务器主要用来发布信息,Web 服务器的信息是通过 HTML 超文本标记语言组织起来的。Applet 是嵌入在 HTML 网页文件中的 Java 程序,它主要用于产生交互式的、动态的网页。

练习与思考 9.1

什么是客户端?什么是服务器?什么是端口监听程序?

9.2

什么是 Web 服务器?Web 服务器的主要功能是什么?

9.3 什么是 URL?什么是 HTTP?每个 HTTP 请求由哪几部分组成?对应的 Web 服务器的响应由哪几部分组 成? 9.4

下载并安装、配置、测试管理 Tomcat 服务器,查阅其中的文档说明。

9.5

简述 Applet 的小程序的执行过程。

9.6

Applet 类有哪 4 个重要的方法?它们在 Applet 生命周期中有什么作用?

9.7

简述 HTML 文件与 Applet 小程序之间参数传递的过程。

9.8

编写一个 Applet 小程序,接受 HTML 文件传递过来的 3 个参数,其中第 1 个参数是字符串常量“来

自 HTML 文件的参数”,另外两个参数是第 1 个参数在 Applet 显示区域的坐标。编写对应的 HTML 文件在 Tomcat 服务器中运行这个 Applet 小程序。


第 10 章

126

第 10 章

图形用户界面程序设计

图形用户界面程序设计

学习目标  了解 Java 图形用户界面编程基础理论和 Java.awt 基本构成  掌握 AWT 常见控制组件和布局管理器的编程应用  熟练掌握常见 Swing 组件和事件处理机制 图形用户界面(Graphics User Interface) ,简称 GUI,已经成为目前几乎所有软件的标准 界面。它使用图形的方式,借助菜单、按钮等标准界面元素和鼠标操作,帮助用户方便地向计算 机系统发出指令,启动操作,并将系统运行的结果同样以图形方式显示给用户。图形用户界面画 面生动、操作简单,省去了字符界面用户必须记忆各种命令的麻烦。 本章首先对 Java AWT 的基本概念、常见组件及布局管理器进行简介,随后详细介绍 Swing 的组件的用法及 Java 的事件处理机制,并对各种用法进行了举例。

10.1

Java 图形用户界面编程基础

AWT(Abstract Window Tookit)是 Java 提供的用于窗口环境的程序包。在设计 Java 应 用程序的图形界面(GUI)时,其中一个很重要的方面是实现和用户的动态交互。这一功能可以 通过 Java 抽象窗口工具集的事件处理机制来实现。

10.1.1

Java.awt

Java 抽象窗口工具集(AWT)可以使得 Java 图形用户界面程序跨平台工作。AWT 提供了各种 用于设计图形用户界面的标准组件:

图 10.1

类的层次结构

1.基本控制组件 包括按钮、校验盒、选择框、列表框、菜单和单行文本编辑域等组件。这些组件提供了基本 的人机交互界面。


10.1

Java 图形用户界面编程基础

127

校验盒有“on”和“off”两种状态,当鼠标单击校验盒时,其状态会发生转换。可以给校 验盒设置初始状态和添加标记,也可以通过类将多个校验盒构成一个校验盒组,校验盒组实现了 多选一功能。 应用程序通过选择框可以生成弹出式菜单。当前的选择显示在选择框上,通过鼠标单击选择 框,可以弹出整个菜单,用户此时能够拖动鼠标进行选择。同时,通过相应的方法可以在选择框 中加入选择项,选择项从零开始编号。 列表框的作用类似于选择框,它用于在多个列表项中进行选择,列表项也从零开始编号。与 选择框不同的是,列表中的所有选项都显示于屏幕,如果列表所占有的区域不够,那么系统会自 动出现滚动条。列表框支持多选一和多选多。 2.复杂输入输出控制组件 包括组件画布和多行文本编辑域等组件。复杂输入输出控制组件可实现基本控制组件不能实 现的功能。画布是一块空白区域,可以通过调用 paint()方法在画布上绘图。 3.其他控制组件 包括滚动条和标签等组件,其中滚动条用于控制其他组件的滚动,标签则用于显示不可编辑 的文本。 4.容器组件 容器组件用来组织其他界面成分和元素的单元。一般说来一个应用程序的图形用户界面首先 对应于一个复杂的容器,如一个窗口。这个容器内部将包含许多界面成分和元素,这些界面元素 本身也可以是一个容器,这个容器将再进一步包含它的界面成分和元素,依次类推就构成一个复 杂的图形界面系统。

10.1.2

AWT 常见控制组件

与容器不同,控制组件是图形用户界面的最小单位之一,它里面不再包含其他的成分。控制 组件的作用是完成与用户的一次交互,包括接受用户的一个命令,接受用户的一个文本输入,向 用户显示一段文本或一个图形等。从某种程度上来说,控制组件是图形用户界面标准化的结果, 目前常用的控制组件有: (1)复选框(Checkbox) (2)单选按钮(CheckboxGroup 或 RadioButton) (3)下拉列表(List 或 Choice) (4)标签(Label) (5)文本编辑区(Edit 或 Textfield,TextArea) (6)按钮(Button) 下面举两个例子来说明如何使用这些组件。其中例 10.1 演示了如何运用单选按钮和复选按 钮。 【例 10.1】checkboxtest.java (1)Checkapplet.html 1:

<html>


第 10 章

128

图形用户界面程序设计

2:

<head>

3:

<title> Checkapplet</title>

4:

</head>

5:

<body>

6: <applet name="Checkapplet" code="checkboxgrouptest.class" width="500" height="300" align="middle"> 7:

</applet>

8:

</body>

9:

</html>

(2)checkboxgrouptest.java 1:

import java.awt.*;

2:

import java.applet.*;

3:

import java.awt.event.*;

4:

public class checkboxgrouptest extends Applet implements ItemListener{

5:

public void init(){

6:

Checkbox one=new Checkbox("计算机类");

7:

Checkbox two= new Checkbox("经济学类",true);

//创建复选框 one

8:

Checkbox three=new Checkbox("化工化学类",false);

9:

CheckboxGroup cbg=new CheckboxGroup();//创建单选按钮组

10:

Checkbox four=new Checkbox("计算机类",false,cbg);

11:

Checkbox five=new Checkbox("经济学类",false,cbg);

12:

Checkbox six=new Checkbox("化工化学类",true,cbg);

13:

one.addItemListener(this);

14:

two.addItemListener(this);

15:

three.addItemListener(this);

16:

four.addItemListener(this);

17:

five.addItemListener(this);

18:

six.addItemListener(this);

19:

add(one);

20:

add(two);

21:

add(three);

22:

add(four);

23:

add(five);

24:

add(six);

//添加对象到面板中

25: 26:

}

27:

public void itemStateChanged(ItemEvent e){


10.1 28:

}

29:

}

Java 图形用户界面编程基础

129

【运行结果】 如图 10.2 所示。 这里运行的 HTML 文档内容和以下各 Applet 实例所使用的 HTML 文件类似,不再一一进行说 明,只需将 code 参数更改为对应的 Java 字节代码文件名。

图 10.2

【例 10.1】的运行结果

【程序说明】 (1)第 1~3 行:加载相应的包。 (2)第 6~8 行:创建三个复选框,其中第二个复选框默认为选中。 (3)第 9~12 行:创建单选按钮组,调用函数 Checkbox 建立第 1 个选项,名称为"one",默 认状态为假,即没有事先选中。第 12 行建立一个事先已经默认为正确的选项,名称为"six"。 (4)第 13~18 行:当用户点击对象使其状态发生变化时就会引发 ItemEvent 类代表的选择 事件。这个对象已经把自身注册给 ItemEvent 事件的监听者 ItemListener。系统会自动调用这个 ItemListener 中的 public void itemStateChanged(ItemEvent e)方法来响应复选框的状态改 变。 (5)第 19~24 行:将 6 个对象加入到面板中去,以使之能够在屏幕上显示出来。 (6)第 27 行则是事件处理函数,用来处理选项改变的效果。这儿没有加入代码。没有进行 任何事件处理。 例 10.2 演示了文本区和文本框的用法。先创建文本框和文本区,然后创建两个功能按钮。 【例 10.2】textfiletest.java 1: import java.awt.*; 2: import java.applet.*; 3: import java.awt.event.*; 4: public class textfiletest extends Applet implements ItemListener{ 5:

public void init() {

6:

TextField text1= new TextField("TextField",20);//创建文本框

7:

TextArea text2 = new TextArea ("TextArea",4,20);//创建文本区

8:

Button b = new Button("提 交");

9:

Button c = new Button("取 消");


第 10 章

130 10:

add(text1);

11:

add(text2);

12:

add(b);

13:

add(c);

图形用户界面程序设计

14:

}

15:

public void textValueChanged(TextEvent e) {

16:

}

17:

public void itemStateChanged(ItemEvent e) {

18:

}

19:

}

【运行结果】 如图 10.3 所示。

图 10.3

10.1.3

【例 10.2】的运行结果

布局管理器

容器仅仅记录其包含的构件,而布局管理器则指明了容器中构件的位置和尺寸大小。通过布 局管理器,您只需要告知您想放置的构件同其他构件的相对位置即可,这有助于用户实现软件的 平台无关性。AWT 提供了五种类型的布局管理器。 不熟悉 AWT 的程序设计员可能会搞不清楚在给定环境下应该使用何种 AWT 标准布局管理器。 实际上,这主要应根据实践中得出的经验来进行选择。表 10.1 提出了一些提示。 表 10.1

布局管理器

布局管理器

功 能 说 明

BorderLayout

一个容器把它的构件分到几个区域:北和南,或东和西;一个单独的构件需要填满它 所位于容器的整个空间

CardLayout

希望在不同环境下控制成组构件的可见性

FlowLayout

容器的构件从左到右、从上到下填充容器

GridLayout

容器把它的构件分到一个网格中


10.1 GridBagLayout

Java 图形用户界面编程基础

131

一个容器具有复杂的布置需要

下面以 BorderLayout 和 GridBagLayout 为例说明布局管理器的用法,其他几种布局管理器 可参阅相关的 Java 工具手册。 1.BorderLayout 布局管理器 一旦布置带有嵌套的构件,就可以深刻意识到 BorderLayout 按地理区域布置构件的巧妙作 用。几乎每一个带有嵌套的布置里面都有一个潜在的 BorderLayout。 与 CardLayout 和 GridBagLayout 一样,Borderlayout 实现 LayoutManager2 接口,即在它布 置的构件上加上约束条件。约束条件是字符串,可以传递给容器的 add(Container,Object)方 法。这个字符串指定了构件的位置——“东”、“南”、“西”、“北”或“中心”。 图 10.4 显示了一个 Applet,是使用 BorderLayout 的一个实例。 程序范例 10.3 列出了 BorderLayout 类的代码,添加了五个按钮,每一个代表不同的方向, 即东、南、西、北和中心。 【例 10.3】BorderLayoutApplet.java 1:

import java.applet.Applet;

2:

import java.awt.*;

3:

import java.awt.event.*;

4:

public class BorderLayoutApplet extends Applet {

5:

private Button north, south, east, west, center;

6:

public void init() {

7:

north = new Button("北方");

8:

east = new Button("东方");

9:

west = new Button("西方");

10:

center = new Button("中心区");

11:

south = new Button("南方");

12:

setLayout(new BorderLayout(2,2));//设置布局方式

13:

add(north, "North");

//把 north 按钮放置到北面

14:

add(south, "South");

//把 south 按钮放置到南面

15:

add(east, "East");

//把 east 按钮放置到东面

16:

add(west, "West");

//把 west 按钮放置到西面

17:

add(center, "Center");

//把 center 按钮旋转到中央

18:

}

19: }

【运行结果】 如图 10.4 所示。 【程序说明】 BorderLayout 水平地扩展南北构件,使这些构件跨越它们所在的容器的整个宽度,但是根据


第 10 章

132

图形用户界面程序设计

构件的首选高度垂直地调整它们的高度。东西构件被垂直的扩展,因此它们跨越它们所有的容器 的整个高度减去南北构件的高度后的高度,同时根据它们的首选宽度水平地调整它们的宽度。中 心构件忽略它的首选尺寸而得到剩下的空间。

图 10.4

【例 10.3】的运行结果

注意我们已经指定构件之间水平和垂直间隙为 2 个象素,间隙规定了构件之间的距离。 BorderLayout 为设置并得到水平和垂直间隙提供了相应的方法。 2.GridLayout 布局管理器 GridLayout 在一个网格中布置构件,客户程序可以在构造 GridLayout 时或在构造好以后设 置构件的间隙和行数列数。当在 Applet 中布置电子表格或日历这样的构件时,GridLayout 很有 用。图 10.5 显示了带有由 GridLayout 定位的 ImageButton 构件的 Applet。 在程序范例 10.4 中,给出了 GridLayoutApplet 代码。注意 Applet 在访问 GridLayout 构造 器时显式地设定了行数和列数,同时也将构件之间的水平和垂直间隙设置为 10 个像素。 【例 10.4】GridLayoutApplet.java 1: import java.applet.Applet; 2: import java.awt.*; 3: public class GridLayoutApplet extends Applet { 4:

private Button one, two, three, four, five, six,seven, eight, nine, ten;

5:

public void init() {

6:

Panel buttonPanel = new Panel();

7:

one = new Button(" 第 1 本书 ");

8:

two = new Button(" 第 2 本书 ");

9:

three = new Button(" 第 3 本书 ");

10:

four = new Button(" 第 4 本书 ");

11:

five = new Button(" 第 5 本书 ");

12:

six = new Button(" 第 6 本书 ");

13:

seven = new Button(" 第 7 本书 ");

14:

eight = new Button(" 第 8 本书 ");

15:

nine = new Button(" 第 9 本书 ");

16:

ten = new Button(" 第 10 本书 ");


10.1

Java 图形用户界面编程基础

17:

add(one);

18:

add(two);

19:

add(three);

20:

add(four);

21:

add(five);

22:

add(six);

23:

add(seven);

24:

add(eight);

25:

add(nine);

26:

add(ten);

27:

setLayout(new GridLayout(3,0,10,10));//设置成网格布局方式

28:

}

29:

}

133

【运行结果】 如图 10.5 所示。

图 10.5

【例 10.4】的运行结果

【程序说明】 (1)这是 Applet 构造 GridLayout 的一个实例,指定行数为 3,列数为 0。如果行数或列数 被规定为零,那么另一个值是要通过计算确定的。例如,上面的例子有 10 个按钮,3 行将导致共 有 4 列。构件之间的水平和间隙也要被规定。 (2)GridLayout 布置的构件将完全填满它所占据的网格单元。但是,不能控制一个构件的任 何涉及网格单元属性的约束条件。例如,不能规定一个构件占据多少个网格单元,因为每个构件 总是正好占据一个网格单元。另一方面,除了许多其他的约束条件外,GridBagLayout 允许在每 一个构件上增加约束条件,规定构件占据了一个网格的多少和跨越了多少网格单元。


第 10 章

134

10.2

图形用户界面程序设计

Swing 简介

抽象窗口工具包(Abstract Window Toolkit,AWT)是 Java 开发用户界面最初的工具包。 Swing 是建立在 AWT 之上的,它是包括了大多数轻量组件的组件集。除提供了 AWT 所缺少的、 大量的附加组件外,Swing 还提供了替代 AWT 重量组件的轻量组件。Swing 还包括了一个使人印 象深刻的、用于实现包含插入式界面样式等特性的图形用户界面的下层构件。因此,在不同的平 台上,Swing 组件都能保持组件的界面样式特性,如双缓冲、调试图形和文本编辑包等。 Swing 组件比较多而且使用比较复杂,下面仅对几个比较重要和常用的组件作简单说明。 1.Jbutton JButton 类继承自 Abstract Button 类,主要是作为一个按钮,当此按钮被按下时便会产生 动作事件而去执行程序所提供的处理此事件的方法。如表 10.2 所示: 表 10.2 方

JButton 类中的方法

简 要 说 明

getText

返回按钮组件的按钮面文字

setText

设置按钮组件的按钮面文字

2.JLabel 用来放置文字的类,主要是用来显示单行文字信息。如表 10.3 所示: 表 10.3 方

JLabel 类中的方法

简 要 说 明

JLabel

JLabel 的建构

getText

返回按钮面文字字符串

setText

设置按钮面文字字符串

3.JTextField JTextField 提供单行文字编辑的功能,如表 10.4 所示: 表 10.4 方

JTextField 类中的方法

简 要 说 明

JTextField

JTextField 对象建构

getHorizontalAlignment

返回水平对齐方式

setHorizontalAlignment

设置水平对齐方式

getColumns

返回行位数


10.2

Swing 简介

135

setFont

设置字体

addActionListener

加入动作监听对象

例 10.5 的功能是求最大公因数,Jbutton、Jlabel、JtextField 在实例中的主要功能是创建 一个用来求最大公因数的图形界面程序。 【例 10.5】JButtonTest.java。 1:

import java.awt.*;

2:

import javax.swing.*;

3:

import java.awt.event.*;

4:

public class JButtonTest extends JFrame implements ActionListener{

5:

private JTextField ShowAnswer;

6:

private JTextField Input1,Input2;

7:

private JButton Ok;

8:

private static final String OK="计算";

9:

public JButtonTest(){

10:

super("JButtonTest Testing!!!");

11:

Container c=getContentPane();

12:

c.setLayout(new FlowLayout());

13:

Input1=new JTextField(4);

14:

Input2=new JTextField(4);

15:

ShowAnswer=new JTextField(4);

16:

Ok=new JButton(OK);

17:

c.add(new JLabel("请输入两整数以求最大公因数:")); //添加标签

18:

c.add(Input1);

19:

c.add(Input2);

20:

c.add(new JLabel("最大公因数:"));

21:

c.add(ShowAnswer);

22:

c.add(Ok);

23:

Ok.addActionListener(this);

24: 25:

//创建输入文本框 1 //创建输入文本框 2 //创建计算结果显示文本框 //创建计算按钮

} public static void main(String[] args){

26:

JButtonTest J_T=new JButtonTest();

27:

J_T.addWindowListener(new WindowAdapter(){

28:

public void windowClosing(WindowEvent e){

29:

System.exit(0);

30:

}

31:

});


第 10 章

136 32:

J_T.setSize(310,100);

33:

J_T.show();

34:

}

35:

图形用户界面程序设计

public void actionPerformed(ActionEvent e){

36:

int num1=Integer.parseInt(Input1.getText());//把输入 1 赋值给 num1

37:

int num2=Integer.parseInt(Input2.getText()); //把输入 2 赋值给 num2

38:

if(num1<0) num1=-1*num1;

39:

if(num2<0) num2=-1*num2;

40:

if(num1<num2){

41:

int tmp=num1;

42:

num1=num2;

43:

num2=tmp;

44:

}

45:

do{

46:

int x;

47:

x=num2;

48:

num2=num1%num2;

49:

num1=x;

50:

} while(num2!=0);

51:

ShowAnswer.setText(String.valueOf(num1));//把计算结果传递给结果框

52:

}

53:

}

【运行结果】 如图 10.6 所示。

图 10.6

【例 10.5】的运行结果

【程序说明】 (1)该程序利用了 Swing 中的 JFrame、JButton、JLabel 及 JTextField 对象来作出图形化 界面,其中使用了 JLabel 对象来显示文字的说明内容,用 JTextField 所作出来的对象 Input1、 Input2,作为使用者输入数字的界面;以及 ShowAnswer 对象用于把经过计算的结果显示出来。 (2)程序中第 5 行声明 JTextField 对象,第 7 行声明了 JButton 对象,第 13 行产生一个大 小为四个字符的 JTextField 对象,第 16 行产生一个按钮,第 17 行产生一个 JLabel 对象,同时


10.2

Swing 简介

137

把它加在上面的 JFrame 对象中,第 23 行为按钮作登记。 (3)第 35 行定义了一个方法,主要功能是把输入到 JTextField 的数字字符串转换成整数。 4.JRadioButton 定义一群互斥的选取方法,也就是在同一群组的选项中只能有一个被选取。 5.JCheckBox 一般而言,对于 JCheckBox 组件,它有两种状态可以被选择,主要是用在多重选项群组中。 6.JComboBox 下拉式选项列表组件,用来提供一个显示菜单,如表 10.5 所示: 表 10.5 方

JComboBox 类中的方法

简 要 说 明

JcomboBox

JComboBox 对象建构

AddItem

加入数据项

getActionCommand

返回动作命令字符串

getItemAt

返回某个索引值数据项对象

getItemCount

返回数据项计数

getMaximumRowCount

返回最大行数

getSelectItem

返回所选数据项

getSelectedIndex

返回所选数据项索引值

getSelectedObject

返回所有已经被选的数据项

removeAllItems

移除所有数据项

removeItem

移除数据项

removeItemAt

移除索引值所代表的数据项

setEditable

设置是否可编辑

setMaximumRowCount

设置最大的行数

setSelectedIndex

设置所选数据项的索引值,索引值从 0 开始算

setSelectedItem

设置被选取的数据项

7.JList JList 组件提供使用者一个可以滚动的表列,以供选择表列中的一个或多个数据选项。如表 10.6 所示: 表 10.6 方 Jlist

JList 类中的方法 简 要 说 明

Jlist 对象构建


第 10 章

138

图形用户界面程序设计

GetFirstVisibleIndex

返回第一个可见行的索引值,如为空白则返回-1

getLastVisibleIndex

返回最后一个可见列的索引值,如为空白则返回-1 续表

简 要 说 明

getSelectedIndex

返回第一个选项索引值

getSelectedIndices

返回所有选项索引值至整数数组中

getSelectedValue

返回第一个选项对象

getSelectedValues

返回所有选项至对象数组中

getSelectionMode

返回 SelectionMode

getVisibleRowCount

返回可见列数

setSelectedIndex

设置 index 所代表的项目被选取状态

setSelectedIndices

设置整数数组中的值所代表的选项为选取状态

setSelectionMode

设置 SelectionMode

setVisibleRowCount

设置可见行数

例 10.6 中,用两个 JList 对象 BaseList 和 ToList 放置初始可选择列表和选定内容。选择 按钮的功能则是从 BaseList 对象中把选中的数据传送到 ToList 对象中。 【例 10.6】JListTest.java 1: import java.awt.*; 2: import javax.swing.*; 3: import java.awt.event.*; 4: public class JListTest extends JFrame implements ActionListener{ 5:

private JList BaseList,ToList;

6:

private JButton toRight;

7:

private String Name[]={"计算机网络","计算机组成原理","编译原理","数据结构"};

8:

public JListTest(){

9:

super("JListTest Testing!!!!");

10:

Container c=getContentPane();

11:

c.setLayout(new FlowLayout());

12:

BaseList=new JList(Name);

13:

BaseList.setVisibleRowCount(4);

14:

BaseList.setFixedCellWidth(80);

15:

BaseList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_

//创建 BaseList 列表框

SELECTION); 16:

ToList=new JList();

17:

ToList.setVisibleRowCount(4);

18:

ToList.setFixedCellWidth(80);

//创建 ToList 列表框


10.2

Swing 简介

139

19:

ToList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SEL

20:

toRight=new JButton(">>>");

21:

c.add(new JScrollPane(BaseList));

22:

c.add(toRight);

23:

c.add(new JScrollPane(ToList));

ECTION);

24:

//创建">>>"按钮

toRight.addActionListener(this);

25:

}

26:

public static void main(String[] args){

27:

JListTest J_L=new JListTest();

28:

J_L.addWindowListener(new WindowAdapter(){

29:

public void windowClosing(WindowEvent e){

30:

System.exit(0);

31:

}

32:

});

33:

J_L.setSize(300,150);

34:

J_L.show();

35:

}

36:

public void actionPerformed(ActionEvent e){

37:

ToList.setListData(BaseList.getSelectedValues());//依据选择设置 ToList 内容

38:

}

39:

}

【运行结果】 如图 10.7 所示。

图 10.7

【例 10.6】的运行结果

【程序说明】 (1)在这个范例中,我们产生了两个 JList 对象 BaseList 和 ToList,分别放在按钮对象的 左右两边,而在左边的 JList 对象我们设置了一些初始内容,右边的内容则设为空白;使用者可 以利用鼠标来选择左边 JList 对象内的数据项,接着通过按钮的动作把在 BaseList 对象内所选 择的数据内容传送到右边的 JList 对象内。


第 10 章

140

图形用户界面程序设计

(2)程序中第 13 行设置可见行数为 4;程序中第 14 行设置 JList 的固定宽度为 80。 (3)程序中第 37 行把左边 BaseList 对象内被选取的内容,设置给右边的 ToList 对象。 8.JOptionPane JOptionPane 类中的方法如表 10.7 所示。 表 10.7 方

JOptionPane 类中的方法

简 要 说 明

showConfirmDialog

提供对问题的确认

showInputDialog

提供输入数据的对话框

showMessageDialog

提供显示信息对话框

showOptionDialog

前面三个方法的结合

【例 10.7】JOptionPane_Test.java 1: import javax.swing.JOptionPane; 2: public class JOptionPane_Test{ 3:

public static void main(String[] args){

4: 5:

JOptionPane.showInputDialog("输入");

//显示输入对话框

JOptionPane.showMessageDialog(null,"alert","alert",JOptionPane. ERROR_MESSAGE); //显示警告对话框

6:

JOptionPane.showConfirmDialog(null,"choose

7:

Object[] options={"OK","CANCEL"};

one",JOption Pane.YES_NO_OPTION); 8:

JOptionPane.showOptionDialog(null,"Click

one","choose //对选择内容的确定对话框

OK

to

JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE, null,options,options[0]); 9:

System.exit(0);

10: 11:

} }

【运行结果】 如图 10.8 所示。

//显示提醒对话框

continue",

"Warning",


10.2

图 10.8

Swing 简介

141

【例 10.7】的运行结果

通过这几个范例的学习,对 Swing 有了基本的认识,Swing 的内容是非常庞大的,在本章中 只是介绍一些基本的组件,要想了解更多内容,请参阅相关内容。

10.3

事件处理机制

所谓事件,就是指系统中某种事情(如鼠标移动、按下某个键等)的发生。事件的概念一般 在窗口用户界面环境中采用,是必不可少的。在 Java 中,所有的事件都定义在类 Event (java.awt.Event)内。

10.3.1

KeyEvent

和鼠标比起来,键盘产生的事件就简单多了,一般说来,我们关心的就是用户按下了什么键, 再处理就行了,而不必像鼠标那样还具有移动的事件需要处理,它的主要事件有: KEY_PRESS,按下键时产生; KEY_RELEASE,放开键盘按键的时候产生。 处理它们的方法分别为: public boolean keyDown(Event evt,int x,int y){ …… } public boolean keyUp(Event evt,int x,int y){ …… } 参数中的 evt 还是表示事件本身的对象,而 key 就是那个被用户按下或放开的事件。Java 已 经在类 Event 中,定义好一些键的按键值,如表 10.8 所示。 表 10.8 类

代表的按键

键表事件键值 类

代表的按键


第 10 章

142

图形用户界面程序设计

Event.Up

方向键中的“↑”键

Event.F3

“F3”键

Event.DOWN

方向键中的“↓”键

Event.F4

“F4”键

Event.LEFT

方向键中的“←”键

Event.F5

“F5”键

Event.RIGHT

方向键中的“→”键

Event.F6

“F6”键

Event.HOME

“Home”键

Event.F7

“F7”键

例 10.8 演示了键盘事件,其主要功能是当按下键盘的特定功能键时,程序作出一定的响应。 【例 10.8】KeyStrike.Java //键盘事件例子。 1:

import java.awt.Event ;

2:

import java.awt.Graphics ;

3:

import java.applet.*;

4:

public class KeyStrike extends Applet{

5:

char PressKey=0;

6:

int Outx=5,Outy=15;

7:

public boolean keyDown(Event e, int key) {

8:

switch(key) {

9:

case Event.UP:Outy--;break;

10:

case Event.DOWN:Outy++;break;

11:

case Event.LEFT:Outx--;break;

//横坐标左移

12:

case Event.RIGHT :Outx++;break;

//横坐标右移

13:

case Event.HOME :Outx=5;Outy=15;break; //坐标返回初始状态

14:

default:PressKey=(char)key;break;

15:

}

16:

repaint();

17:

return true;

18:

}

19:

public void paint(Graphics g){

//纵坐标上移 //纵坐标下移

20:

if(PressKey!=0)

21:

g.drawString ("你好,你按下了: "+PressKey+" 键",Outx,Outy); //在(Outx,Outy)坐标处,显示对应的字符串内容

22:

}

23: }

HTML 的源代码与前面类似,把 codebase 参数改为 KeyStrike.class。 【运行结果】 如图 10.9 所示。


10.3

图 10.9

事件处理机制

143

【例 10.8】的运行结果

【程序说明】 (1)鼠标在 JavaApplet 区域中点一下后,就可以在键盘上面按键了,每一次按的什么键, 都可以在屏幕上面显示出来。 (2)第 6 行语句定义了程序初始的输出位置(5,15),第 7 句定义了键盘事件。 (3)第 8 到第 15 行,对按下的键值进行判断。如果按的是向上键则输出上移,如果按的是 向下键则输出下移,如果按的是向左键则输出左移,如果按的是向右键则输出右移,如果按的是 Home 键则移到初始的输出位置(5,15)。

10.3.2

TextEvent

TextListener 为监听文字事件的接口。当文本框内容发生改变时 TextEvent 事件发生,由 textValueChange()方法进行处理。 例 10.9 给出了 TextEvent 事件的应用举例,例子中定义了一个利息计算的界面,当输入本 金和利率后,系统会自动进行利息小计和利息合计的计算。 【例 10.9】Fund.java 1: import java.awt.*; 2: import java.awt.event.*; 3: public class Fund implements TextListener,ActionListener{ 4: Frame f; 5: Panel p1,p2,p3; 6: Label lb1[]; 7: TextField tf[]; 8: Button btn[]; 9: String lblcaption[]={"本金","年利率","利息小计","利息合计"}; 10: String ss; 11: public static void main(String args[]){ 12: Fund fund=new Fund(); 13: fund.go(); 14: } 15: public void go(){ //定义 go 方法,主要完成主界面的设置 16: f=new Frame(); 17: p1=new Panel(); 18: p2=new Panel(); 19: p3=new Panel(); 20: lb1=new Label[4]; 21: tf=new TextField[7];


第 10 章

144

图形用户界面程序设计

22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:

btn=new Button[2]; f.setSize(350,200); Font ft=new Font("Time",Font.PLAIN,24); //定义字体 p1.setLayout(new GridLayout(3,3,3,3)); for(int i=0;i<=3;i++){ //依据数组设立标签名称 lb1[i]=new Label(lblcaption[i]); } for(int i=0;i<=2;i++){ //向区域 p1 中添加对应的标签 p1.add(lb1[i]); } for(int i=0;i<=5;i++){ //创建、初始化文本框,并将其添加到区域 1 tf[i]=new TextField(5); tf[i].setText("0"); p1.add(tf[i]); } tf[1].addTextListener(this); //设置 TextField1 事件监听

38: 39: 40: 41: 42: 43: 44: 45: 46: 47:

tf[4].addTextListener(this); //设置 TextField4 事件监听 f.add(p1,BorderLayout.NORTH); p2.add(lb1[3]); tf[6]=new TextField(10); tf[6].setText("0"); p2.add(tf[6]); f.add(p2,BorderLayout.CENTER); btn[0]=new Button("清除"); btn[1]=new Button("结束"); btn[0].addActionListener(this); //设置 Button0 事件监听

48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71:

btn[1].addActionListener(this); //设置 Button1 事件监听 p3.add(btn[0]); p3.add(btn[1]); f.add(p3,BorderLayout.SOUTH); f.setVisible(true); } public void textValueChanged(TextEvent te){//定义 TextField 事件 String tf0,tf1,int1,int2,int3; double interest1,interest2,interest3,tf0value,tf1value; tf0=tf[0].getText(); tf1=tf[1].getText(); tf0value=Double.parseDouble(tf0); //把文本框中的值转换为双精度数 tf1value=Double.parseDouble(tf1); //把文本框中的值转换为双精度数 interest1=tf0value*tf1value; //对转换后的值进行计算 int1=Double.toString(interest1); //计算后的值转换为字符串 tf[2].setText(" "+int1); //设置 tf2 的值为转换后的字符串 tf0=tf[3].getText(); tf1=tf[4].getText(); tf0value=Double.parseDouble(tf0); tf1value=Double.parseDouble(tf1); interest2=tf0value*tf1value; int2=Double.toString(interest2); tf[5].setText(""+int2); interest3=interest1+interest2; //对利息合计进行汇总


10.3 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87:

事件处理机制

145

int3=Double.toString(interest3); tf[6].setText(""+int3); } public void actionPerformed(ActionEvent ae){// 定义鼠标事件 String ss; ss=ae.getActionCommand(); if(ss.equals("清除")){ //当按下清除按钮时,设置文本框为 0 for(int i=0;i<=6;i++){ tf[i].setText("0"); } } else{ System.exit(0); } } }

【运行结果】 如图 10.10 所示。

图 10.10

【例 10.9】的运行结果

【程序说明】 (1)第 54~74 行定义了文本框事件,当年利率发生改变时则激活 TextField 事件,对利息 小计和利息合计进行重新计算并把结果值填入到对应的文本框。 (2)第 75~87 行定义了鼠标事件,若鼠标点击按钮时,则取按钮对应的数值,并依据按钮 的取值进行相关动作的处理。

10.3.3

ItemEvent

ItemListener 是处理选项类事件的接口。当选择事件发生时,便会调用 itemStateChanged() 方法,并将 ItemEvent 作参数传递到此方法。 例 10.10 给出了 ItemEvent 的使用实例,其主要功能是选中对应的选项按钮,对字体进行设 置。 【例 10.10】FontChange.java 1: import java.awt.*; 2: import java.awt.event.*;


第 10 章

146 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:

图形用户界面程序设计

import javax.swing.*; public class FontChange implements ItemListener,ActionListener{ Frame f; Panel p1,p2; CheckboxGroup cbg; Checkbox cb1,cb2,cbg1,cbg2; JTextField jField; Font ftc; public static void main(String args[]){ FontChange fc=new FontChange(); fc.go(); } public void go(){ f=new Frame("字体变化"); f.setSize(220,220); p1=new Panel(); p2=new Panel(); jField=new JTextField("Text",10); ftc=new Font("Serif",Font.PLAIN,18); jField.setFont(ftc); jField.setHorizontalAlignment(JTextField.LEFT); p1.add(jField); cbg=new CheckboxGroup(); //创建单选按钮组 cbg1=new Checkbox("置左",true,cbg); cbg2=new Checkbox("置右",false,cbg); cbg1.addItemListener(this); //设置单选选项事件监听 cbg2.addItemListener(this); p2.add(cbg1); p2.add(cbg2); cb1=new Checkbox("斜体"); //创建复选框 cb1 cb2=new Checkbox("粗体"); //创建复选框 cb2 cb1.addItemListener(this); //设置复选框事件监听 cb2.addItemListener(this); p2.add(cb1); p2.add(cb2); f.add(p1,"North"); f.add(p2,"Center"); f.setVisible(true); } public void actionPerformed(ActionEvent ae){ } public void itemStateChanged(ItemEvent ie){ //当选项状态改变时激活该事件 if(cbg1.getState()==true){ jField.setHorizontalAlignment(JTextField.LEFT); //文字放置最左边 } if(cbg2.getState()==true){ jField.setHorizontalAlignment(JTextField.RIGHT); //文字放置最右边 } if(cb1.getState()==true){ if(cb2.getState()==true){ tc=new Font("Serif",Font.ITALIC+Font.BOLD,18);//设置粗体和斜体


10.3 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: }

事件处理机制

147

jField.setFont(ftc); } else{ ftc=new Font("Serif",Font.ITALIC,18);//设置粗体和斜体 jField.setFont(ftc); } } else{ if(cb2.getState()==true){ ftc=new Font("Serif",Font.BOLD,18);//设置粗体和斜体 jField.setFont(ftc); } else{ ftc=new Font("Serif",Font.PLAIN,18);//设置为初始字体 jField.setFont(ftc); } } }

【运行结果】 如图 10.11 所示。

图 10.11

【例 10.10】的运行结果

【运行结果】 (1)第 15~41 行用于对窗口界面的设置。添加文本框、复选框和单选框。 (2)第 42~71 行为选择事件,当选项发生改变时,则激活该方法。该方法用几个选项组合 的取值来对字体进行设置。

10.3.4

MouseEvent

鼠标的使用也会产生一系列事件,主要事件如下: mouseDown 事件,当鼠标按键被按下时产生。由方法 mouseDown 对该事件进行处理。 方法 mouseDown 的定义为: public boolean mouseDown(Event e,int x,int y){ …… } 方法 mouseDown 有三个参数 e、x、y。e 是事件本身,把“事件”看成一个对象实体,只不


第 10 章

148

图形用户界面程序设计

过它的类是 Event 而已;x、y 是这个时间发生时的鼠标的坐标值,方法返回值是 boolean 型的值。 mouseUp 事件,当鼠标按键放开时产生。由方法 mouseUp 对该事件进行处理。这个方法的使 用与 mouseDown 方法完全相同。 mouseMove 事件,是由鼠标移动产生的事件。由方法 mouseMove 对该事件进行处理。 方法 mouseMove 的定义为: public boolean mouseMove(Event evt,int x,int y){ …… } 所用到的参数与方法 mouseDown 和 mouseUp 类似。 mouseDrag,是由鼠标拖动产生的事件。由方法 mouseDrag 对该事件进行处理。 方法 mouseDrag 的定义为: public boolean mouseDrag(Event e,int x,int y){ …… } mouseEnter 事件:当鼠标进入到某个应用程序的窗口范围时发生。由方法 mouseEnter 对该 事件进行处理。 方法 mouseExit 定义为: public boolean mouseEnter (Event evt, int x,int y){ …… } mouseExit 事件:当鼠标从应用程序窗口移出来的时候产生。由方法 mouseExit 对该事件进 行处理。 方法 mouseExit 定义为: public boolean mouseExit (Event e,int x,int y){ …… } 例 10.11 给出了鼠标事件的应用举例,其主要功能是每当鼠标按下时,鼠标事件被激活并取 对应的坐标(x,y)值,然后字符串移到(x,y)位置。 【例 10.11】Amouse.class 1: import java.applet.*; 2: import java.awt.*; 3: public class Amouse extends Applet{ 4:

int cx=50;

5:

int cy=50;

6: public boolean mouseDown(Event e,int x,int y){ 7:

cx=x;

8:

cy=y;

9:

repaint();


10.3 10:

事件处理机制

149

return true;

11: } 12: public void paint(Graphics g){ 13:

g.drawString("在哪儿按鼠标,我就在哪出现!",cx,cy);

14: } 15: }

【运行结果】 如图 10.12 所示。 【程序说明】 (1)浏览器从 paint 函数处执行,cx、cy 值是程序初始化时的值。按下鼠标后 mouseDown 事 件发生,重新取得 cx、cy 的值。

图 10.12

【例 10.11】的运行结果

(2)第 4、5 行定义鼠标的初始位置,也就是当你还没有按下鼠标时的字符串将出现的位 置。 (3)第 6 行定义鼠标按下函数,这是一个布尔类型的函数,也就是只有真和假两种状况的函 数。参数 e 是鼠标按下状态参数,当鼠标按下时,e 值就为真,x、y 是鼠标的位置参数。 (4)第 7、8 行重新定义字符串的位置参数,使字符串的位置为鼠标按下的位置。 (5)第 9 行重新绘制屏幕,使字符串位置更新。 (6)第 12 行,paint 函数,绘制屏幕函数,浏览器从这里开始知道怎么绘制屏幕, 绘制些 什么。这里是在 cx、cy 位置绘制一个字符串,使其显示出来。 例 10.12 给出了鼠标 mouseEnter 和 mouseExit 事件的例子。 【例 10.12】Mouse.java。 1: import java.awt.*; 2: import java.applet.* ; 3: public class Mouse extends Applet { 4:

String msg="";

5: public boolean mouseEnter(Event e,int x,int y){ 6:

msg="请你把鼠标拿开吧!";

7:

repaint();


第 10 章

150 8:

图形用户界面程序设计

return true;

9: } 10: public boolean mouseExit(Event e,int x,int y){ 11:

msg="为什么不把鼠标移进来呀?";

12:

repaint();

13:

return true;

14: } 15: public void paint(Graphics g){ 16:

g.drawString (msg,4,20);

17: }

18: }

【运行结果】 如图 10.13 所示。

(a) 鼠标进入查看器窗口的显示

图 10.13

(b) 鼠标离开查看器窗口的显示

【例 10.12】的运行结果

【程序说明】 (1)第 4 行定义一条字符串变量,存储要输出的消息。 (2)第 5~9 行定义了鼠标移入事件,当鼠标进入查看器窗口时,则显示字符串 msg; (3)第 10~14 行定义了鼠标离开事件, 当鼠标离开查看器窗口时,则显示字符串 msg 并重 新绘制屏幕。

本 章 小 结 本章主要对图形界面设计的基础知识进行了介绍。AWT 是 Java 图形界面的基础,Swing 建立 在 AWT 基础之上,并对其进行了改进,对于一些方法进行了替代。事件处理对于图形界面是必不 可少的。

练习与思考 10.1

设计一个简易的计算器,能完成加、减、乘、除等功能。


10.3

事件处理机制

151

10.2 用 Swing 实作一提供华氏温度与摄氏温度间转换的程序(若 f 表示华氏温度,c 表示摄氏温度,则 f=32+(9/5)*c)。 10.3

编写一个程序,使其界面如图 10.14 所示:

图 10.14

题 10.3 图 1

而且,当扩大这个窗体的宽度时,OK 按钮并不会出现在文本框之后,而是产生如图 10.15 所示的输出结果:

图 10.15

题 10.3 图 2


第 11 章

152

第 11 章

Java 多媒体程序设计

Java 多媒体程序设计

学习目标  理解多媒体技术的基本理论  掌握 Java 图形处理、图像处理、声音播放和动画技术 Java 将各种媒体作为对象进行抽象,为各种媒体的集成提供了方便。本章主要介绍 Java 多 媒体程序设计中的图像处理、声音处理及动画处理等技术,并通过实例来具体说明 Java 多媒体 程序设计的基本方法。

11.1

多媒体技术概述

媒体是指信息传递和存储的最基本技术和手段。日常生活中常用的媒体有语言、文本、音乐、 图片、书籍、电视、广播及电话等。多媒体技术是指人类同计算机交互处理多媒体信息的方法。 多媒体技术有以下主要特征: (1)多维性 这是指多媒体技术具有的处理信息范围的空间扩展和放大能力,是指对信息的输入加以变 换、创作、加工并能够对输出信息增加表现能力,丰富效果。例如,用多媒体系统来辅助地理课 教学,学生不仅学到文本知识,观察到静止图景,而且通过多媒体技术还可以看到热带茂密的丛 林,听到鸟儿的歌唱,使学生有身临其境之感。这种信息空间的多维化,使信息的表达方式不再 局限于单调,狭隘的范围内。 (2)集成性 集成性不仅仅是各种媒体的集成,而且包含着多媒体信息的集成。这种集成性是信息系统层 次的一次飞跃。早期,各种媒体都只能单独地发挥作用,如声音,图像等,有的只有声音而无图 像,有的仅有静态图像而无动态图像等。多媒体系统将它们集成起来以后,经过多媒体技术处理, 使其发挥综合作用。 (3)交互性 没有交互性就不是多媒体系统。如,电视机有图像、声音和文字显示,但观众只能被动地观 看,没有交互能力,因此不是多媒体系统。多媒体技术为用户提供了更加有效地控制和使用信息 的手段,为多媒体系统应用开辟了更广阔的领域。其交互性可以增加对信息的注意力和理解,延 长信息的存储时间。

11.2

Java 图形处理

利用 Java 的绘图方法可绘制各式各样图形以及显示各种文本字体,并配以颜色,这是 Java


11.2

Java 图形处理

153

多媒体技术中的一项基本功能。 Java 语言的类库中提供了丰富的绘图方法(method) ,其中大部分对图形、文本、图像(image) 的操作方法都定义在 Graphics 类中。Graphics 类又是 java.awt 程序包的一部分,在进行图形、 文本、图像的处理时,要在 Java 中导入对应的类 java.awt.Graphics。 在这里要特别指出的是,当在屏幕上绘制图形、文本、图像时,并不需要直接使用 new 来产生 一个 Graphics 类的对象实例;在 java.awt.Applet 类的 paint( )方法中,我们已经得到了一个 Graphics 对象的引用, 这时系统将直接把生成好的 Graphics 对象通过参数形式传递给 paint( )方法。 因此,我们只要在这个对象上进行图形、文本及图像的绘制操作,就可以在屏幕上看到所显示的结果。

11.2.1

图形坐标系

为了将某一图形在屏幕上绘制出来,首先碰到的问题就是“画在哪个位置” ,为了解决这个 问题就必须有一个精确的图形坐标系统来将该图形定位。 与大多数其他计算机图形系统所采用的二维坐标系统一样,Java 的坐标原点(0,0)在屏幕的 左上角,水平向右为 X 轴的正方向,竖直向下为 Y 轴的正方向,每个坐标点的值表示屏幕上的一 个像素点的位置,因此,所有坐标点的值都取整数。

11.2.2

Graphics 类

Graphics 类提供了绘制基本图形的方法,包括画直线、矩形、椭圆弧和多边形。主要方法见表 11.1。 表 11.1 方

DrawLine(int x1,int y1,int x2,int y2) DrawRect(int x, int y, int width, int height) fillRect(int x, int y, int width, int height) drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) drawPolygon(int xPoints[],int yPoints[],int nPoints) fillPolygon(int xPoints[],int yPoints[],int nPoints) DrawOval(int x, int y, int width, int height) fillOval(int x, int y, int width, int height) drawArc(int x, int y, int width, int height,int startAngle, int arcAngle)

Graphics 类中的方法 功

画线,其中 x1,y1 表示线段的一个坐标点,x2,y2 表示线段的另 一个坐标点 画矩形, x 和 y 分别表示矩形左上角的横坐标和纵坐标,width 和 height 分别表示矩形的宽度和高度 画填充型矩形,与画矩形参数类似 画圆角矩形, x 和 y 分别表示矩形左上角的横坐标和纵坐标, width 和 height 分别表示矩形的宽度和高度,arcWidth 代表 了圆角弧的横向直径;arcHeight 代表了圆角弧的纵向直径 画填充型圆角矩形,与圆角矩形参数类似 画多边形, int Poly1_x[]参数是一个整数数组,用以存放多边形 坐标点的 X 坐标值,int Poly1_y[]参数存放相应的一组 Y 坐 标值,int Poly1_pts 则表示共有几个坐标点 画填充型多边形,与多边形参数类似 画椭圆, x 和 y 是椭圆外接矩形的左上角横坐标和纵坐标,width 和 height 为椭圆外接矩形的高和宽 画填充型椭圆,与椭圆参数类似 画弧形,前四个参数的含义与画椭圆一样, startAngle 参数表示该弧 从什么角度开始,arcAngle 参数表示从 startAngle 开始转了多


第 11 章

154

height,int startAngle, int arcAngle) fillArc(int x, int y, int width, int height,int startAngle, int arcAngle)

Java 多媒体程序设计 从什么角度开始,arcAngle 参数表示从 startAngle 开始转了多 少度

画填充型弧形,与弧形参数类似

例 11.1 用上面表格中的方法画出多边形、弧形、填充型弧形。 【例 11.1】drawPolygon.java 1:import java.awt.Graphics; 2:public class drawPolygon extends java.applet.Applet{ 3: public void paint(Graphics g){ 4: int Poly1_x[]={30,63,115,72,67}; 5: int Poly1_y[]={40,20,95,74,106}; 6: int Poly1_pts=Poly1_x.length; 7: g.drawPolygon(Poly1_x,Poly1_y, Poly1_pts); //画多边形 8: g.drawArc(110,20,100,60,35,-140); //画弧形 9: g.fillArc(210,20,100,60,35,-140); //画填充型弧形 10: } 11: }

【运行结果】 如图 11.1 所示。

图 11.1

【例 11.1】的运行结果

【程序说明】 (1)第 4、5 行 int Poly1_x[]参数是一个整数数组,用以存放多边形坐标点的 X 坐标值,int Poly1_y[]参数存放相应的一组 Y 坐标值,int Poly1_pts 则表示共有几个坐标点。 (2)边框型多边形并不自动关闭多边形的最后一条边,而仅是一段开放的折线。所以,若想 画封闭的边框型多边形,不要忘了在数组的尾部再添上一个起始点的坐标。 (3)第 8 行画一段弧,其中前四个参数的含义与画矩形一样,因此也必须用矩形的观点来确 定弧在坐标系统中的位置。后两个参数就是用来定义椭圆的部分:startAngle 参数表示该弧从什 么角度开始,arcAngle 参数表示从 startAngle 开始转了多少度。水平向右表示 0 度,逆时钟方 向为正角度值,顺时钟方向为负角度值。 (4)参数 startAngle 和 arcAngle 中有任一值大于 360 的话,都会被转换为 0 到 360 之间的 数值。因此若要画满整个椭圆,arcAngle 需设为 360 的整数倍,若设为 370 则相当于只画了 10 度。另外 fillArc( )方法的效果并不是填充弧的两个端点直接连线所围的区域,而是填充弧的两 端点与圆心连线所围的扇形区域,像一个饼图。


11.2

11.2.3

Java 图形处理

155

Color 类

设置颜色与设置字体信息相似,要设置新的颜色,必须先创建 Color 对象,然后再调用 Graphics 类中设置颜色的方法来将生成的 Color 对象设为当前所用的绘图颜色。 Java 中每一种颜色都看成是由红(R)、绿(G)、蓝(B)三基色组合而成的。因此 Color 类的构 造函数采用如下格式: Color(int r, int g, int b) 其中每个参数的值都在 0 到 255 之间,数值越大就表明这种颜色的成分越重。例如(0,0,0) 代表黑色,(255,0,0)代表红色。 为了能使用刚才生成好的 Color 对象来显示文本及绘制图形,还需调用 Graphics 类中的 setColor( )方法把这个对象设置为系统当前所用的绘画颜色,其调用格式为: setColor(Color c) 例如,想要用蓝色来显示文本,最简单的办法是直接引用标准色的类变量: setColor(Color.blue); 例 11.2 对 Color 类中的方法进行了演示,其主要功能是显示一排用随机定义的颜色所填充 的小方块。 【例 11.2】Colors.java: 1:import java.awt.Graphics; 2:import java.awt.Color; 3:public class Colors extends java.applet.Applet{ 4: public void paint(Graphics g){ 5: int red,green,blue; 6: for (int i=10;i<400;i+=40){ 7: red=(int)Math.floor(Math.random()*256); 8: green=(int)Math.floor(Math.random()*256); 9: blue=(int)Math.floor(Math.random()*256); 10: g.setColor(new Color(red,green,blue)); 11: g.fillRect(i,20,30,30); 12: } 13: } 14:}

【运行结果】 如图 11.2 所示。

图 11.2

【例 11.2】的运行结果

【程序说明】 (1)第 7~9 行,随机产生填充色,当然最终在屏幕上是否能显示所定义的颜色还取决于客


第 11 章

156

Java 多媒体程序设计

户端系统的调色板所支持的颜色种类的多少。若客户端系统的调色板并不支持当前所定义的颜色 值,就会在调色板中挑选最接近的颜色来代替。 (2)第 10、11 行,设置显示色彩,并依据设置的色彩画出对应矩形。

11.2.4

文本与字体

Graphics 类也提供了在屏幕上显示文本的方法,但若要使文本的显示更具特色,让它满足某 种字体、某种风格及尺寸大小的要求,就需要用字体类 Font 来定义。 1.设置文本信息 要在屏幕上输出文本信息,首先要确定的就是采用何种字体,这都由 Font 类来定义,其构 造函数的调用格式: Font(String name, int style, int size) 它的三个参数就是字体名、字体风格和尺寸大小,并且 Font 类中已预定义了一些类变量来 表示字体的 style 值,如 Font.BOLD(表示粗体)、Font.ITALIC(表示斜体)、Font.PLAIN(表示普 通体)。由于它们被定义为整数常量,因此可以进行相加运算来生成复合 style,例如定义 style 为既是粗体又是斜体: Font fn = new Font("TimesRoman", Font.BOLD+Font.ITALIC, 28) 2.显示文本 创建了 Font 对象以后,我们就可以利用 Graphics 类中提供的 drawString( )、drawChars( ) 等方法来显示字符串与字符。当然,首先还要用 setFont( )方法,将所创建的 Font 对象设为当 前所用的字体。下面就是 Graphics 类中这三个方法的调用格式: setFont(Font font); drawString(String str, int x, int y) drawChars(char data[], int offset, int length, int x, int y) setFont( )方法的参数就是一个创建好的 Font 对象,表明系统当前选用哪个 Font 对象所定 义的字体信息。drawString( )方法中的 str 即是要显示的字符串,x、y 指明字符串显示的起始 位置坐标,另外还有一种方法 drawChars( )方法则是用来显示多个字符的,就是从给定的字符数 组中抽取连续的一部分显示在屏幕上。其中 data 参数就是给定的原始字符数组,offset 表示从 第几个字符位置开始显示,length 表示共显示几个字符,x 与 y 参数的含义与 drawString( )方 法一样,代表显示在屏幕上的起始位置。 例 11.3 演示了文本设置和显示方法,程序的主要功能是显示了一些不同的文本字体。 【例 11.3】Fonts.java 1:import java.awt.Graphics; 2:import java.awt.Font; 3:public class Fonts extends java.applet.Applet{ 4: public void paint(Graphics g){ 5: Font ftp20 = new Font("TimesRoman",Font.PLAIN,20); 6: Font fai15 = new Font("Arial",Font.ITALIC,15); 7: Font fcb24 = new Font("Courier",Font.BOLD,24); 8: Font fsib30 = new Font("宋体",Font.ITALIC+Font.BOLD,30); 9: g.setFont(ftp20);


11.2 10: 11: 12: 13: 14: 15: 16: 17: 18:}

Java 图形处理

157

g.drawString("Font name TimesRoman , style plain , size 20",10,20); g.setFont(fai15); g.drawString("Font name Arial , style italic , size 15",10,50); g.setFont(fcb24); g.drawString("Font name Courier , style bold , size 24",10,80); g.setFont(fsib30); g.drawString("字体名 宋体,风格 斜体+粗体,尺寸 30",10,120); }

【运行结果】 如图 11.3 所示。

图 11.3

【例 11.3】的运行结果

【程序说明】 (1)第 5~8 行创建不同的字体对象。 (2)第 9~16 行,依据不同的字体设置显示对应的文字。首先设置显示文本字体格式,然后 显示该文本。

11.3

图像处理

与其他语言相比较,Java 对图像与声音媒体都具有较强的支持,显示图像与播放声音就如同 显示一行文本一样方便。正因为它在 Java 动画中灵活的运用了图像和声音媒体,才使得 Web 页 面更加生动和丰富。

11.3.1

加载图像

Applet 类中提供了 getImage( )方法用来将准备好的图像文件装载到 Applet 中,但我们必 须首先指明图像文件所存储的位置。由于 Java 语言是面向网络应用的,因此文件的存储位置并 不局限于本地机器的磁盘目录,而大部分情况是直接存取网络中 Web 服务器上的图像文件,因而, Java 采用 URL(Universal Resource Location,统一资源定位器)来定位图像文件的网络位置。 getImage 方法的格式是: Image getImage(URL,url) Image getImage(URL url,String name) 例 11.4 显示了加载图像的用法,其主要功能是加载图像 pic.jpg,然后按要求格式显示出来。 【例 11.4】getImage. java 源代码如下:


第 11 章

158

Java 多媒体程序设计

1:import java.awt.*; 2:import java.applet.*; 3:public class getImage extends Applet{ 4: Image labmag; 5:public void init(){ 6: labmag=getImage(getDocumentBase(),"pic.jpg"); 7:} 8:public void paint(Graphics g){ 9: g.drawImage (labmag,0,0,this); 10: g.drawImage (labmag,0,120,100,100,this); 11:} 12:}

【运行结果】 如图 11.4 所示。

图 11.4

【例 11.4】的运行结果

【程序说明】 (1)第 6 行中 getDocamentBase()方法是返回相对路径,也就是说,网页文件现在在哪儿, 那么它的返回值就是网页文件的位置。 (2)第 9、10 行中 g.drawImage()方法用于显示,将在后面一节中介绍,第一个方法是从(0,0) 开始显示整个图像,第二个方法是从(0,120)开始显示高度和宽度均为 100 的图像。

11.3.2

显示图像

getImage( )方法仅仅是将图像文件从网络上装载进来,交由 Image 对象管理。Graphics 类 提供了一个 drawImage( )方法,它来完成将 Image 对象中的图像显示在屏幕的特定位置上。 drawImage( )方法的调用格式如下: boolean drawImage(Image img, int x, int y, ImageObserver observer) boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) 其中 img 参数就是要显示的 Image 对象,x 和 y 参数是该图像左上角的坐标值,observer 参


11.3

图 像 处 理

159

数则是一个 ImageObserver 接口,用来跟踪图像文件装载情况,通常我们都将该参数置为 this。 第二种格式比第一种格式多了两个参数 width 和 height,表示图像显示的宽度和高度。若实际图 像的宽度和高度与这两个参数值不一样时,Java 系统会自动将它进行缩放。 Image 类中有两个方法可以分别得到原图的宽度和高度。其调用格式如下: int getWidth(ImageObserver observer) int getHeight(ImageObserver observer) 同 drawImage( )方法一样,通常用 this 作为 observer 的参数值。 例 11.5 演示了显示图像方法的用法。其主要功能是按比例对图像进行缩放,然后输出到屏 幕上。 【例 11.5】Images.java 1:import java.awt.Graphics; 2:import java.awt.Image; 3:public class Images extends java.applet.Applet{ 4: Image img; 5: public void init(){ 6: img=getImage(getCodeBase(),"image.gif"); 7: } 8: public void paint(Graphics g){ 9: int w=img.getWidth(this); 10: int h=img.getHeight(this); 11: g.drawImage(img,20,10,this); //原图 12: g.drawImage(img,200,10,w/2,h/2,this); //缩小一半 13: g.drawImage(img,20,200,w*2,h/3,this); //宽扁图 14: g.drawImage(img,350,10,w/2,h*2,this); //瘦高图 15: } 16:}

【运行结果】 如图 11.5 所示。

图 11.5

【例 11.5】的运行结果


第 11 章

160

11.3.3

Java 多媒体程序设计

图像生成

通过 ImageProducer 可进行图像生成,ImageProducer 是一个接口,凡是实现该接口的类都可 以 用 来 生 成 图 像 。 包 括 java.awt.image 中 提 供 的 两 个 类 FilteredImageSource 和 MemoryImageSource。类 MemoryImageSource 使用一个数组来得到图像中每个像素点的值。它的一 个构造方法如下: MemoryImageSource(int width,int height,int pixel[],int offset,int scanLineWidth); 其中 width 和 height 给出图像的大小,pixel 中包含每个像素点的值,scanLineWidth 给出 图像中每行的像素数。 FilteredImageSource 则通过一个已载入图像和图像过滤器来生成一幅新的图像,它的构造 方法为: FilteredImageSource(ImageProducer orig,ImgaeFilter imgF); 当生成了 ImageProducer 型对象后,就可以调用组件的方法 creatImage()来生成图像。其接 口为:image CreateImage(ImageProducer producer); 例 11.6 用 MemoryImageSourceSource 方法依据数组内容生成一幅图像并在屏幕上显示出来。 【例 11.6】memoryImage.java: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:

import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class memoryImage extends Applet{ Image img; public void init(){ Dimension d=size(); int w=d.width,h=d.height; int pix[]=new int[w*h]; int index=0; for(int y=0;y<h;y++) for(int x=0;x<w;x++){ int red=(x^y)&0xff; int green=(x*2^y*2)&0xff; int blue=(x*4^y*4)&0xff; pix[index++]=255<<24|(red<<16)|(green<<8)|blue; } img=createImage(new MemoryImageSource(w,h,pix,0,2)); //依据数组 pix 创建图像 19: } 20:public void paint(Graphics g){ 21: g.drawImage(img,0,0,this); //在 Applet 上输出图像 22: } 23: }

【运行结果】 如图 11.6 所示。


11.3

图 11.6

图 像 处 理

161

【例 11.6】的运行结果

(1)第 11~17 行利用多重循环对 pix 数组进行赋值。 (2)第 18 行依据数组内容创建图像。

11.3.4

图像的简单处理

AWT 提供图像过滤器(Image Filter)来进行图像处理。java.awt.image 中提供的图像过滤器 包括 ImageFilter 及其子类 CropImageFilter 和 RGBImageFilter。一幅图像的数据交给图像过滤 器,经处理之后可以得到新的图像。 ImageFilter 实现“空”处理(即不对图像数据进行处理),它是作为所有图像过滤器的父类而 存在的。 CropImageFilter 用于提取图像中指定矩形区域的图像。 RGBImageFilter 用于对图像的色彩进行处理,它是抽象类。 要使用图像过滤器来处理图像,首先需要得到原始图像以及原始图像的数据源,然后生成图 像过滤器,在作好这些准备之后,就可通过 FileredImageSource 类建立新的图像所需要的数据, 并通过 CreatImage()生成新的图像。如: ImageFilter filter=new CropImageFilter(x1,y1,w,h); ImageProducerproducer=new FilterImageSource(img.getSource(),filter); Image img=createImage(producer); 例 11.7 是使用 CropImageFilter 处理图像实例。 【例 11.7】CropImage.java: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:

import java.awt.*; import java.awt.image.*; import java.applet.Applet; public class CropImage extends Applet{ Image img; Image newImg; int x1,y1,w,h,count=1; public void init(){ img=getImage(getCodeBase(),"pic.jpg"); newImg=img; } public void paint(Graphics g){ int w=newImg.getWidth(this); int h=newImg.getHeight(this);


第 11 章

162

Java 多媒体程序设计

15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45:

Dimension d=size(); if(w>d.width)w=d.width; if(h>d.height)h=d.height; g.drawImage(newImg,(d.width-2)/2,(d.height-h)/2,this); } public boolean mouseDown(Event evt,int x,int y) { Graphics g=getGraphics(); if(count==1){ x1=x;y1=y;count=2; g.drawLine(x-5,y,x+5,y); g.drawLine(x,y-5,x,y+5); } else if(count==2){ count=3; if(x1>x)w=x1-x; else w=x-x1; if(y1>y)h=y1-y; else h=y-y1; newImg=crop(); repaint(); } else{ count=1; newImg=img; repaint(); } return true; } Image crop(){ ImageFilter filter=new CropImageFilter(x1,y1,w,h); ImageProducer FilteredImageSource(img.getSource(),filter); 46: return createImage(producer); 47: } 48: }

【运行结果】 如图 11.7 所示。

图 11.7

【程序说明】

【例 11.7】的运行结果

producer=new


11.3

图 像 处 理

163

(1)第 22~26 行,当鼠标第一次按下时,在鼠标位置画出一个小十字坐标轴,并取鼠标当 前位置为(x1,y1)。 (2)第 27~40 行,当鼠标第二次按下时,再取鼠标位置坐标(x,y),与(x1,y1)相减,求得 图形区域,调用图像处理 crop()方法。

11.4

声音播放

Java 利用 AudioClip 类来控制声音的播放过程。AutioClip 是一个接口类,可以通过 Applet 类的 getAudioClip()方法获取对象。读入声音文件和读入图形格式的原理是一样的。以下是 getAudioClip()方法的两种调入形式。 import java.applet.AudioClip; AudioClip bgsound=getAudioClip(URL url): AudioClip bgsound=getAudioClip(URL url,String name) 控制音频文件进行播放的方法有: bgsound.play() //启动声音文件的播放,每次均从起始播放,且不回放 bgsound.loop() //循环播放 bgsound.stop()//中断 play()方法 AudioClip 允许用户在音频文件装入前进行预装入,并将其数据保存于内存,这样就无须每 次运行时重载了。 例 11.8 演示了声音播放的用法,这段程序将播放两段声音,其中 space.au 为背景音乐, intro.au 为一段人说话的声音。 【例 11.8】Audios.java 1: import java.applet.AudioClip; 2: public class Audios extends java.applet.Applet{ 3: AudioClip bgmusic,speak; 4: public void init(){ 5: bgmusic=getAudioClip(getDocumentBase(),"space.au"); //加载背景音乐 6: speak=getAudioClip(getDocumentBase(),"intro.au"); //加载讲话声音 7: } 8: public void start(){ 9: if(bgmusic!=null) 10: bgmusic.loop(); //循环播放背景音乐 11: if(speak!=null) 12: speak.play(); //播放讲话 13: } 14: 15: public void stop(){ 16: if(bgmusic!=null) 17: bgmusic.stop(); //关闭背景音乐 18: } 19: }

【程序说明】


第 11 章

164

Java 多媒体程序设计

第 5、6 行装入两段声音,第 9、10 行对 bgmusic 作为背景音乐播放,并循环进行。第 11~ 12 行对 speak 进行播放。

11.5

动画技术

初步掌握了在 Java 中处理各种媒体的基本技能后,接下来要讨论 Java 多媒体中的 Java 动 画技术。这一节,制作几个简单的动画实例,并通过这些实例介绍一些动画技术,以便读者能真 正掌握 Java 动画技术。 1.移动型动画 由于动画的过程是一个循环过程,应该生成一个动画线程来完成图像的显示和更新。在 Applet 启动时,生成一个新的动画线程,在 Applet 停止时,终止该线程以释放它所占用的 CPU 资源。在终止动画线程时,不能调用线程的方法 stop(),而是设置动画线程为 null。这是因为 直接调用线程的方法 stop()会强制线程终止所有的执行工作,可能会带来不好的结果。如果设置 线程为 null,则会在方法 run()中,由于不满足循环条件,线程会自然退出。 2.控制显示频率 计算机实现动画显示的时候,通常每秒显示 10~20 帧图像。程序设计时考虑在定义缺省值的 同时,可以由用户指定显示频率。再根据显示频率确定延迟时间,再通过线程的方法 sleep()来 实现延迟。 3.消除闪烁 在 Java 中,动画发生闪烁的原因有两种: (1)在显示下一帧画面时,调用了方法 repaint(),在 repaint()调用 update()时,要清除 整个背景,然后才调用 paint()显示图画。但很多情况下,消除背景是不必要的。 (2)方法 paint()中可能要进行复杂的计算,图像中各个像素的值不能同时得到,使得动画 的生成频率低于显示器的刷新频率,从而造成闪烁。 针对上述原因,可以重写方法 update(),使之不清除背景;或采用双缓冲技术生成一帧后台 图像,然后把后台图像一次显示于屏幕等方法来消除闪烁。 4.跟踪图像的载入 在刚刚启动程序时,由于没有完全载入图像,屏幕上显示的是残缺不全的画面,这时可以使 用 MediaTracker 或 ImageObserver 来跟踪图像的载入,并进行相应的处理。 下面给出一个实现动画的实例,该动画通过连续播放 image1.gif~image5.gif 5 个彩色小球 的图片来产生一个运动的效果。 【例 11.9】Animator.java 1: 2: 3: 4: 5: 6: 7:

import java.awt.*; import java.applet.Applet; public class Animator extends Applet implements Runnable{ int frameNumber; int delay; int allFrame=5; Thread animatorThread;


11.5 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:

动 画 技 术

Dimension offDimension; Image offImage; Graphics offGraphics; Image images[]; MediaTracker tracker; public void init(){ String str; str=getParameter("fps"); int fps=(str!=null)?Integer.parseInt(str):10; delay=(fps>0)?(5000/fps):100; images=new Image[allFrame]; tracker=new MediaTracker(this); for(int i=1;i<=allFrame;i++){ images[i-1]=getImage(getCodeBase(),"image"+i+".gif");//加载图像 i tracker.addImage(images[i-1],0); } } public void start(){ if(animatorThread==null){ animatorThread=new Thread(this); animatorThread.start(); } } public void stop(){ animatorThread=null; offImage=null; offGraphics=null; } public boolean mouseDown(Event e,int x,int y){ //定义鼠标事件,启动和停止动画 if(animatorThread==null){ start(); } else{ animatorThread=null; } return false; } public void run(){ long startTime=System.currentTimeMillis(); while (Thread.currentThread()==animatorThread){ repaint(); try{ startTime+=delay; Thread.sleep(Math.max(0,startTime-System.currentTimeMillis())); }catch(InterruptedException e){ break; } frameNumber++; } }

165


第 11 章

166

Java 多媒体程序设计

58: public void paint(Graphics g){ 59: if(offImage!=null){ 60: g.drawImage(offImage,0,0,this); 61: } 62: } 63: public void update(Graphics g){ 64: Dimension d=size(); 65: if((offGraphics==null)||(d.width!=offDimension.width)||(d.height! =offDimension.height)){ 66: offDimension=d; 67: offImage=createImage(d.width,d.height); //创建后台缓冲区 68: offGraphics=offImage.getGraphics(); 69: } 70: offGraphics.setColor(getBackground()); //设置作图环境 71: offGraphics.fillRect(0,0,d.width,d.height); 72: offGraphics.setColor(Color.black); 73: if(tracker.statusID(0,true)==MediaTracker.COMPLETE){ 74: offGraphics.drawImage(images[frameNumber%allFrame],0,0,this); 75: } 76: g.drawImage(offImage,0,0,this); 77: } 78: }

【运行结果】 如图 11.8 所示。

图 11.8

【例 11.9】的运行结果

【程序说明】 第 15~20 行,跟踪图像的载入。第 36~44 行,重写方法 mouseDown(),使得用户在点击鼠 标时,可以启动和停止动画。第 49~54 行,控制显示的频率。 第 63~77 行,重写方法 update(),使其不再调用 paint(),但是方法 paint()依然要实现, 这是因为 AWT 绘图系统可直接调用 paint()来绘图。paint()的实现可以简单地调用 update()。 第 67 行,通过 creatImage()生成合适大小的后台缓冲区,然后获取在缓冲区作图的环境(即 Graphics 类对象),并通过它完成后台图像的绘制,最后再通过当前作图环境在屏幕上绘制已经 准备好的后台图像。

本 章 小 结 本章讨论了文本处理技术、图形处理技术、图像处理技术、声音处理技术及动画处理技术。


11.5

动 画 技 术

167

通过本章的学习,可以学会如何访问图像、绘图、声音、图片、影像、动画等,以及如何利用多 媒体技术使 Java Applet 在网页中生成丰富多彩的动态效果。

练习与思考 11.1

多媒体技术有哪些主要特征?

11.2

声音播放有哪两种不同的方式,它们的区别是什么?

11.3

试实现一模拟时钟的动画。

11.4

实现动画时需注意哪几个问题?如何改善动画的质量效果?


12.3

第 12 章

JDBC 驱动程序类型

167

Java 数据库程序设计

学习目标  理解 JDBC 的体系结构、ODBC 和 JDBC 的比较、JDBC 的主要特点、JDBC 两层结构和三层结 构  理解 JDBC 驱动程序类型和创建 ODBC 数据源  熟练掌握 JDBC 数据库访问流程和 JDBC 应用程序接口 Java 数据库连接 JDBC (Java Database Connectivity)技术,为程序开发者提供了独立于数 据库的统一的 API,是 Java 语言访问数据库的接口。当 Java 程序需要访问数据库时,可以使用 JDBC API,采用标准 SQL 进行有关数据库方面的程序设计。 本章主要介绍 JDBC 的体系结构及主要特点、JDBC 驱动程序分类、JDBC 数据库访问过程及 JDBC 应用程序接口,并给出具体的用法及相关例子。

12.1

JDBC 原理

JDBC 是一种用于执行 SQL 语句的 Java API,它由一组用 Java 编程语言编写的类和接口组成。 JDBC 为开发人员提供了一个标准的 API,使他们能够用纯 Java API 来编写数据库应用程序。 因为 JDBC API 可以使程序独立于具体的数据库,因此就不必为访问 Oracle 的数据库专门写 一个程序,为访问 MS SqlServer 数据库又专门写一个程序,或为访问 Access 数据库又写另一个 程序等。只需用 JDBC API 写一个程序就够了,它负责向相应数据库发送 SQL 语句。如果将 Java 和 JDBC 结合起来,则程序员只需写一遍程序就可让它在任何平台上运行。 Java 具有健壮、安全、易于使用、易于理解和可从网络上自动下载等特性,提供了 Java 应 用程序与各种不同数据库之间进行对话的方法,而 JDBC 正是作为此种用途的机制。

12.1.1

JDBC 的功能

JDBC 的功能,简单地说是做下面的三件事: (1)与数据库建立连接。 (2)发送 SQL 语句到数据库。 (3)存放 SQL 语句的结果。 下列代码段给出了以上三步的基本示例: Connection con=DriverManager.getConnection("jdbc:odbc:wombat","login","password");//与数据库建立连接 Statement stmt=con.createStatement();//发送 SQL 语句 ResultSet rs=stmt.executeQuery("select a,b,c from table"); //存放 SQL 语句的结果


第 12 章

168

Java 数据库程序设计

while(rs.next()) //对结果进行处理 { int x=rs.getInt("a"); String s=rs.getString("b"); float f=rs.getFloat("c"); }

12.1.2

JDBC 的驱动程序管理器

Java 应用程序只通过 JDBC API 与数据库保持连接,在 JDBC 内部,JDBC 驱动程序管理器 DriverManager 通过相应的驱动程序与具体的数据库系统进行连接。JDBC 驱动程序管理器 DriverManager 是 JDBC 的管理层,作用于应用程序和驱动程序之间,它跟踪可用的驱动程序,并 在数据库和相应的驱动程序之间建立连接。

12.1.3

ODBC 和 JDBC 的比较

到目前为止,ODBC 可能是用得最广泛的访问关系数据库的 API,它几乎提供了连接任何一种 平台、任何一种数据库的能力。在 JDBC 的协助下,可用 JDBC-ODBC 桥接器实现数据库连接。 (1)ODBC 并不适合在 Java 中使用。ODBC 是一个 C 语言实现的 API,从 Java 程序调用本地的 C 程序会带来一系列类似安全性、完整性、健壮性方面的缺点。 (2)完全精确地实现从 C 代码的 ODBC 到 Java API 翻译的 ODBC 也并不令人满意。如 Java 没 有指针,而 ODBC 中大量地使用了指针,包括极易出错的空指针“void *”。 (3)ODBC 不容易学习,它将简单特性和复杂特性混杂一起,甚至对非常简单的查询都有复杂 的选项。而 JDBC 刚好相反,它保持了简单事物的简单性,但又允许复杂的特性。 (4)JDBC API 对纯 Java 方案来说是必须的。当使用 ODBC 时,人们必须在每一客户机上安装 ODBC 驱动器和驱动管理器。如果 JDBC 驱动器是完全用 Java 语言实现的话,那么 JDBC 的代码就 可以自动地下载和安装,并保证其安全性,而且,这将适应任何 Java 平台,从网络计算机(NC) 到大型主机。 总之,JDBC API 是能体现 SQL 基本抽象的、最直接的 Java 接口。它构建在 ODBC 思想的基础 上,并保持了 ODBC 的基本设计特征。它们最大的不同是 JDBC 是基于 Java 的风格和优点,并强 化了 Java 的风格和优点。

12.2

JDBC 两层结构和三层结构

JDBC API 既支持数据库访问的两层模型,同时也支持三层模型。 在两层模型中(图 12.1) ,Java applet 或应用程序将直接与数据库进行对话。这将需要一 个 JDBC 驱动程序来与所访问的特定数据库管理系统进行通讯。用户的 SQL 语句被送往数据库 中,而其结果将被送回给用户。数据库可以位于另一台计算机上,用户通过网络连接到上面。这 就叫做客户机/服务器配置,其中用户的计算机为客户机,提供数据库的计算机为服务器。网络 可以是 Intranet(它可将公司职员连接起来),也可以是 Internet。


12.3

图 12.1

JDBC 驱动程序类型

169

JDBC 的两层应用结构

在三层模型中(图 12.2) ,命令先是被发送到服务的“中间层” ,然后由它将 SQL 语句发送给 数据库。数据库对 SQL 语句进行处理并将结果送回到中间层,中间层再将结果送回给用户。MIS 主管们都发现三层模型很吸引人,因为可用中间层来控制对公司数据的访问并可扩展更新的控制 功能。中间层的另一个好处是,用户可以利用易于使用的高级 API,而中间层将把它转换为相应 的低级调用。最后,许多情况下三层结构可提供一些性能上的好处。

图 12.2

JDBC 的三层应用结构

到目前为止,中间层通常都用 C 或 C++这类语言来编写,这些语言执行速度较快。然而,随 着最优化编译器(它把 Java 字节代码转换为高效的特定于机器的代码)的引入,用 Java 来实现 中间层将变得越来越实际。这将是一个很大的进步,它使人们可以充分利用 Java 的诸多优点(如 坚固、多线程和安全等特征)。JDBC 对于从 Java 的中间层来访问数据库非常重要。

12.3

JDBC 驱动程序类型

目前的 JDBC 驱动程序可分为四个类,主要适应不同的情况。其中 Pure JDBC Driver 是 JDBC 访问数据库的首选方法,因为它不会增加任何额外的负担,而且是由纯 Java 语言开发面成的。 Net Protocol All-Java 驱动程序也是纯 Java 语言开发而成的,而且中间件软件仅需在服务器上 安装,因此它是一个比较好的方案。Java to Native API 类 JDBC 驱动程序由于事先必须在客户 机上安装其他附加软件,将会使 Java 程序兼容性大降低。JDBC-ODBC 桥 JDBC 驱动程序最简单, 但只能在 Windows 平台上用。 本书为简单起见,所用的 JDBC 驱动程序为 JDBC-ODBC 桥。

12.3.1

JDBC-ODBC 桥

JDBC-ODBC 桥利用 ODBC 驱动程序提供了 JDBC 访问功能,如图 12.3 所示。JDBC-ODBC 桥是 JDK 的一部分,即是 sun.jdbc.odbc 包的一部分。 JDBC-ODBC 桥虽然使用简单,但由于需要本地的 ODBC 方法,所以限制了它的使用。


第 12 章

170

Java 数据库程序设计

图 12.3

12.3.2

JDBC-ODBC 桥

Java to Native API

把 JDBC 调用转换为 Oracle、Sybase、Informix、DB2 或其他 DBMS 本身所带驱动程序的调用, 如图所示。

图 12.4

12.3.3

本地 API-部分用 Java 来编写的驱动程序

Net Protocol All-Java

纯 Java 驱动程序,如图 12.5 所示。它可以用在一个第三方的解决方案中,而且可以在 Internet 上使用。通过驱动程序厂商所创建的专有网络协议来和某种中间件来通信,这个中间件 通常位于 Web 服务器或者数据库服务器上,并且可以和数据库进行通信。

图 12.5

12.3.4

本地协议纯 Java 驱动程序

Pure JDBC Driver

纯 Java 驱动程序,将 JDBC 调用直接转换为 DBMS 所使用的网络协议(图 12.6) 。这将允许 从客户机机器上直接调用 DBMS 服务器,是 Intranet 访问的一个很实用的解决方法。与其他类型


12.3

JDBC 驱动程序类型

171

的驱动程序相比,这种驱动程序的优点在于它的性能,在客户和数据库引擎之间没有任何本地代 码或者中间件。

图 12.6

12.3.5

纯 Java 网络驱动程序

建立 Book_Shop 数据库和 ODBC 数据源

为了使用 JDBC-ODBC Bridge, 我们建立一个 ODBC 数据源, 通过这个数据源可以访问Microsoft SQL Server 上一个简单的网上购书 Book_Shop 数据库。在建数据源之前,必须安装好 SQL Server7.0/2000,如果是在 PC 机上安装的话,需安装 SQL Server 桌面版。然后在 SQL Server 上 创建 Book_Shop 数据库(具体方法参见有关书籍),在 Book_Shop 数据库内创建以下 3 个表: (1)book 书表,存放图书有关信息。book 表主要字段有书号(book_no,char(5))、书名 (book_name,char(20)) 、 单 价 (price,decimal(8,2)) ; 编 者 (editor,char(10)) ; 出 版 社 (publish_company, char(16)) ; 出 版 日 期 (publish_date,datetime(8)) ; 库 存 数 量 (quantity,numeric(6,0))。 (2)customer 客户表,存放客户有关信息。customer 客户表主要字段有客户号(customer_ id,char(10));客户名(customer_name,char(10));客户地址(customer_address,char(40));客户密 码(customer_password, char(8));身份证号(id_card,char(18));邮件地址(e_mail,char(20));联 系电话(phone,char(12))。 (3) purchase 订单表, 存放图书销售有关信息。 purchase 订单表主要字段有订单号(purchase_ id,char(15));客户号(customer_id,char(10));书号(book_id,char(5));数量(quantity, numeric(6,0));单价(price, decimal(8,2);折扣率(discount_rate, smallint(2))。 下面介绍如何建立与 Book_Shop 数据库相对应的 ODBC 数据源 book_shop。 (1)从控制面板中选择数据源——ODBC(图 12.7)。


172

第 12 章

图 12.7

Java 数据库程序设计

ODBC 系统 DSN 界面

(2)单击添加,选择 SQL Server 为数据源驱动程序类型(图 12.8)。

图 12.8

创建新数据源

(3)输入要使用的数据源的名称(图 12.9)。


12.3

图 12.9

JDBC 驱动程序类型

173

添加 SQL Server DSN 配置界面

(4)单击下拉按钮选择已经创建好的数据库。 (5)可以在高级选项里面加上用户名和密码。

12.4

JDBC 数据库访问流程

所有的 JDBC 应用程序都具有下面的基本流程: (1)加载 JDBC 驱动程序。 (2)建立到数据库的连接。 (3)执行 SQL 语句。 (4)存放处理结果。 (5)与数据库断开连接。 在和某一特定数据库建立连接前,必须首先加载驱动程序 Driver 类,加载成功的驱动程序 类会在 DriverManager 中注册。 加载驱动程序的方式主要有两种: (1)通过调用方法 Class.forName,显式地加载驱动程序类。由于这与外部设置无关,因此 推荐使用这种加载驱动程序的方法。 使用以下代码加载 JDBC-ODBC Bridge 驱动程序: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); (2) 通过将驱动程序添加到 java.lang.System 的属性 jdbc.drivers 中, 这是一个由 Driver Manager 类加载的驱动程序类名的列表,由冒号分隔。初始化 DriverManager 类时,它搜索系 统属性 jdbc.drivers,如果用户已输入了一个或多个驱动程序,则 DriverManager 类将试图加 载它们。 上面两种方式中,新加载的 Driver 类都要通过调用 DriverManager.registerDriver 类进行 自我注册,调用方法 Class.forName 显式地加载每个驱动程序显得更为安全。


第 12 章

174

12.4.1

Java 数据库程序设计

创建数据库连接

加载 Driver 类并在 DriverManager 类中注册后,即可建立与数据库的连接。当调用 DriverManager.getConnection 方法发出连接请求时,DriverManager 将检查每个驱动程序,查 看它是否可以建立连接。 下面是用 JDBC-ODBC 桥驱动程序,建立数据库连接的例子。 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //加载驱动程序 String url = "jdbc:odbc:databasename"; //数据库连接字符串 DriverManager.getConnection(url, "userID", "passwd"); //进行数据库连接

12.4.2

执行 SQL 语句

在数据库连接成功之后,就可以执行那些完成实际工作的 SQL 语句了。在执行 SQL 语句之前, 我们必须首先创建一个语句对象,这个对象提供了到特定数据库 SQL 引擎的接口。 有下列三种不同类型的语句对象: (1)Statement,基本的语句对象,它提供了直接在数据库中执行 SQL 语句的方法。对于那 些只执行一次的查询以及 DDL 语句如 CREATE TABLE,DROP TABLE 等等来说,statement 对象就足 够了。 (2)Prepared statement,这种语句对象用于那些需要执行多次,每次仅仅是数据取值不同 的 SQL 语句,它还提供了一些方法,以便指出语句所使用的输入参数。 (3)Callable statement,这种语句对象被用来访问数据库中的存储过程。它提供了一些方 法来指定语句所使用的输入输出参数。 下面是一个用语句类来执行 SQL SELECT 语句的一个例子: Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM MyTable");

12.4.3

接收并处理 SQL 的返回结果

在执行了一个 SQL 语句之后,必须处理得到的结果。有些语句仅仅返回一个整型数,指出受 到影响的行数(比如 UPDATE 和 DELETE 语句)。SQL 查询(SELECT)语句返回一个含有查询结果的 结果集,结果集由行和列组成,各列数据可以通过相应数据库类型的一系列 get 方法(如 getString,getInt,getDate 等等)来取得。在取得了一行数据的所有数据之后,我们可以调用 next()方法来移到结果集中的下一条记录。JDBC 规范的 1.1 版只允许 forward-only(只向前) 型的游标,而在 JDBC2.0 中有更健壮的游标控制功能,我们可以向后移动游标而且可以将游标移 动到指定行。

12.4.4

关闭创建的各个对象

在结果集、语句和连接对象用完以后,必须正确地关闭它们。连接对象、结果集对象以及 所有的语句对象都有 close()方法,通过调用这个方法,可以确保正确释放与特定数据库系统 相关的所有资源。这样可以尽可能的减少由于挂起的对象残留在数据库系统中而造成的内存


12.4

JDBC 数据库访问流程

175

泄漏。

12.5

JDBC 应用程序接口

JDBC API 定义了若干 Java 中的类,如表示数据库连接、SQL 指令结果集,它允许用户发送 SQL 指令并处理结果。通过驱动程序管理器,JDBC API 可以利用不同的驱动程序连接不同的数据 库系统。JDBC API 提供了应用程序调用驱动程序的 Java 接口,而驱动程序负责标准的 JDBC 调 用向数据库所要求的具体调用的转变。

12.5.1

JDBC API

JDBC API 是通过 java.sql 包实现的,这个包中包含了所有的 JDBC 类和接口。JDBC API 中 比较重要的接口如下: (1)java.sql.DriverManager,用来装载驱动程序并为创建新的数据库连接提供支持。 (2)java.sql.Connection,完成对某一指定数据库的连接功能。 (3)java.sql.Statement,在一个给定的连接中作为 SQL 执行声明的容器。 (4)java.sql.ResultSet,用来控制对一个特定记录集数据的存取。 JDBC API 具有以下特点,JDBC API 是 SQL 基础之上的 API,并与 SQL 保持一致性;JDBC API 可在现有数据库接口之上实现;JDBC API 提供与其他 Java 系统一致的界面;JDBC 的基本 API 尽 可能地简单化。 JDBC 是一种“低级”的接口,因为它直接调用 SQL 命令,但它又可作为构造高级接口与工具 的基础。

12.5.2

DriverManager 类

DriverManager 类是 JDBC 的管理层,主要用来装载驱动程序并为创建新的数据库连接提供 支持。 例 12.1 演示了加载数据库的方法,其功能是建立数据库连接并进行连接结果测试。 【例 12.1】ConnectSQL.java: 1: import java.sql.*; 2: public class ConnectSQL { 3: public static void main(String agrs[]) { 4: String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; 5: String url = "jdbc:odbc:book_shop"; 6: String user = ""; 7: String password = ""; 8: try { 9: Class.forName(driver); 10: } 11: catch(Exception e) { 12: System.out.println("无法加载驱动程序:" + driver); 13: e.printStackTrace(); 14: }


第 12 章

176 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:

Java 数据库程序设计

try { Connection con = DriverManager.getConnection(url,user,password); if(!con.isClosed()) System.out.println("数据库连接成功!"); con.close(); } catch(SQLException ee) { System.out.println("数据库连接失败!"); ee.printStackTrace(); } } }

【运行结果】 如图 12.10 所示。

图 12.10

12.5.3

【例 12.1】的运行结果

Connection 接口

Connection 接口用于应用程序与数据库之间的静态连接。它提供了进行事务处理的方法、创 建执行 SQL 语句和存储过程所用对象的方法,同时它还提供了一些基本的错误处理方法。 Connection 接口提供的常用方法如表 12.1。 表 12.1

Connection 接口常用方法

方 法 名 称

方 法 说 明

Statement createStatement()

创建用于执行 SQL 语句的 Statement 对象

DatabaseMetaData getMetalData()

返回用于确定数据库特性的 DatabaseMetaData 对象

prepareStatement(String sql)

创建 PrepareStatement 对象

boolean getAutoCommit()

返回 Connection 对象的 AutoCommit 状态

void setAutoCommit(boolean AutoCommit)

设定 Connection 对象的 AutoCommit 状态

void commit()

确定执行对数据库的插入、删除与修改记录的操作

void rollback()

取消执行对数据库的插入、删除与修改记录的操作

void close()

结束 Connection 对象对数据库的连接

boolean isClose()

测试是否已经关闭 Connection 对象对数据库的连接


12.5

12.5.4

JDBC 应用程序接口

177

Statement 接口

Statement 接口用于将 SQL 语句发送到数据库中。实际上有三种 Statement 对象,Statement、 PreparedStatement(它从 Statement 继承而来)和 CallableStatement(它从 PreparedStatement 继承而来),它们都专用于发送特定类型的 SQL 语句。Statement 对象用于执行不带参数的简单 SQL 语 句 ; PreparedStatement 对 象 用 于 执 行 带 或 不 带 IN 参 数 的 预 编 译 SQL 语 句 ; CallableStatement 对象用于执行对数据库已存储过程的调用。 Statement 接口提供了执行语句和获取结果的基本方法。PreparedStatement 接口添加了处 理 IN 参数的方法;而 CallableStatement 添加了处理 OUT 参数的方法。 1.创建 Statement 对象 建立了到特定数据库的连接之后,就可用该连接发送 SQL 语句。Statement 对象用 Connection 的方法 createStatement 创建,如下列代码段中所示: Connection con = DriverManager.getConnection(url, "sunny", ""); Statement stmt = con.createStatement(); 2.使用 Statement 对象执行语句 Statement 接口提供了三种执行 SQL 语句的方法: executeQuery、 executeUpdate 和 execute。使用哪一个方法由 SQL 语句所产生的内容决定。 方法 executeQuery 用于产生单个结果集的语句,例如 SELECT 语句。 方法 executeUpdate 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言) 语句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE,语句的效果是修改表中 零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新 计数) 。对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。 方法 execute 用于执行返回多个结果集、多个更新计数或二者组合的语句。 3.语句完成 当连接处于自动提交模式时,其中所执行的语句在完成时将自动提交或还原。语句在已执行 且所有结果返回时,即认为已完成。对于返回一个结果集的 executeQuery 方法,在检索完 ResultSet 对象的所有行时该语句完成。对于方法 executeUpdate,当它执行时语句即完成。但 在少数调用方法 execute 的情况中, 可能在检索所有结果集或它生成的更新计数之后语句才完成。 4.关闭 Statement 对象 Statement 对象将由 Java 垃圾收集程序自动关闭。而作为一种好的编程风格,应在不需要 Statement 对象时显式地关闭它们。这将立即释放 DBMS 资源,有助于避免潜在的内存问题。 例 12.2 演示了执行 SQL 语句的方法。其功能是在数据库中插入一条记录。 【例 12.2】InsSQL.java 1: import java.sql.*; 2: public class InsSQL { 3: public static void main(String agrs[]) { 4: String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; 5: String url = "jdbc:odbc:book_shop"; 6: String user = "";


第 12 章

178

Java 数据库程序设计

7: 8: 9: 10: 11: 12: 13: 14: 15:

String password = ""; try { Class.forName(driver); } catch(Exception e) { System.out.println("无法加载驱动程序:" + driver); } try { Connection conn = DriverManager.getConnection(url,user,password); //建立数据库连接 16: if(!conn.isClosed()) 17: System.out.println("数据库连接成功!"); 18: Statement smt = conn.createStatement(); //创建一条 SQL 语句 19: smt.executeUpdate("INSERT into book(book_no,book_ name,price, publish_date,editor, quantity)" + "values('00001','计算机文化基础 ',25,'2002-06-06','李梁',1000)"); //向数据库中插入一条记录 20: System.out.println("记录插入完毕!"); 21: smt.close(); //关闭 smt 22: conn.close(); //关闭连接 23: } 24: catch(SQLException ee) { 25: System.out.println("数据库连接失败!"); 26: } 27: } 28: }

【运行结果】 如图 12.11~图 12.13 所示。

图 12.11

程序运行前数据库 book 表中内容


12.5

JDBC 应用程序接口

图 12.12

图 12.13

179

程序运行结果显示

程序运行后数据库 book 表中内容

【程序说明】 (1)第 8~13 行加载数据库驱动程序。 (2)第 14~27 行创建数据库连接。 (3)第 18~20 行执行一条 SQL 语句。

12.5.5

PreparedStatement 接口

PreparedStatement 接口继承自 Statement,但与之在某些方面有所不同。 PreparedStatement 实例中包含已经过预编译处理的 SQL 语句,这些 SQL 语句可以具有一个 或多个 IN 参数。在创建 SQL 语句时 IN 参数的值并未被指定,而是用问号“?”为每个 IN 参数 保留一个占位符。在执行该语句之前,必须通过适当的 setXXX 方法为每个问号提供具体的值。 由于 PreparedStatement 对象已经过预编译处理,因此其执行速度要快于 Statement 对象。 所以,通常把需要多次执行的 SQL 语句创建为 PreparedStatement 对象,以便提高该语句的执 行效率。 作为 Statement 的子类,PreparedStatement 在继承了 Statement 的所有功能同时,还添加 了一系列与参数相关的方法,这些方法主要用于设置发送给数据库以取代 IN 参数占位符的具体 值。 1.创建 PreparedStatement 对象 下面的代码段实例(其中 con 是 Connection 对象)创建了包含两个 IN 参数占位符的 SQL 语 句的 PreparedStatement 对象: PreparedStatement pstmt = con.prepareStatement("UPDATE table4 SET m = ? WHERE x = ?"); pstmt 对象包含的语句 "UPDATE table4 SET m = ? WHERE x = ?",在对象创建完毕后,它 已经被发送到 DBMS,并为执行作好了准备。 2.传递 IN 参数


第 12 章

180

Java 数据库程序设计

在执行 PreparedStatement 对象之前,通常需要调用 setXXX 方法来完成对每个“?”参数 值的设置。其中 XXX 是与该参数相应的类型。例如,如果“?”参数具有 Java 类型 long,则使 用的方法就是 setLong。setXXX 方法有两个参数,其中第一个参数是要设置的“?”的序数位置, 第二个参数是设置给“?”的值。以下代码段演示了如何设置两个“?”参数占位符的值并执行 pstmt 10 次。为做到这一点,数据库不能关闭 pstmt。在该示例中,第一个“?”参数被设置为“Hi” 并保持为常数。在 for 循环中,每次都将第二个“?”参数设置为不同的值:从 0 开始,到 9 结束。 pstmt.setString(1, "Hi"); for (int i = 0; i < 10; i++){ pstmt.setInt(2, i); int rowCount = pstmt.executeUpdate(); } 3.参数中数据类型的一致性 setXXX 方法中的 XXX 是 Java 的类型。在向 DBMS 发送参数时,JDBC 驱动程序将把 Java 的 XXX 类型映射为 JDBC 对应的数据类型,并将该 JDBC 类型发送给数据库。例如,下面一条语 句将 PreparedStatement 对象 pstmt 的第二个参数设置为 44,Java 类型为 short: pstmt.setShort(2, 44); 这条语句中 44 是 Java short 类型,JDBC 驱动程序将 44 映射为 JDBC 的 SMALLINT 类型并 发送给数据库。 程序员的责任是确保将每个 IN 参数的 Java 数据类型映射为数据库所需的或兼容的 JDBC 数 据类型。JDBC 驱动程序承诺一定的兼容性,例如数据库需要 JDBC SMALLINT 的数据类型,如果 使用 setByte 方法,则 JDBC 驱动程序会将 JDBC TINYINT 发送给数据库。上述这种方法是可行的, 因为许多数据库可从一种相关的类型转换为另一种类型,并且通常 TINYINT 可用于 SMALLINT 适 用的任何地方。通常,对于要适用于尽可能多的数据库的应用程序,最好使用与数据库所需的确 切的 JDBC 数据类型相应的 Java 数据类型。比如,所需的 JDBC 类型是 SMALLINT,则使用 setShort 代替 setByte 将使应用程序的可移植性更好。 例 12.3 给出了一个 PreparedStatement 接口实用例子,其主要功能是对相应的记录值进行 修改。 【例 12.3】UsePreStatement.java 1: import java.sql.*; 2: public class UsePreStatement { 3: public static void main(String agrs[]) { 4: String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; 5: String url = "jdbc:odbc:book_shop"; 6: String user = ""; 7: String password = ""; 8: try { 9: Class.forName(driver); 10: } 11: catch(Exception e) {


12.5

JDBC 应用程序接口

12: System.out.println("不能加载驱动程序:" + driver); 13: } 14: String cno[]={"01001","01002"}; 15: String cadd[]={"长江路 10 号","黄河路 11 号"}; 16: try { 17: Connection con = DriverManager.getConnection(url,user,password); 18: PreparedStatement pmst=con.prepareStatement("UPDATE customer SET customer_address=? WHERE customer_id=?"); //创建一个 PreparedStatement 对象 19: for(int i=0;i<cno.length;i++) { 20: pmst.setString(1,cadd[i]); 21: pmst.setString(2,cno[i]); 22: pmst.executeUpdate(); 23: } 24: pmst.close(); 25: Statement smt = con.createStatement(); 26: ResultSet rst = smt.executeQuery("SELECT * FROM customer"); 27: System.out.println("修改后客户表中的记录:"); 28: while(rst.next()){ //移动记录指针到下一笔记录 29: System.out.println(rst.getString("customer_id") + " " + rst. getString ("customer_name") + " "+rst.getString("customer_ address")); 30: } 31: smt.close(); 32: con.close(); 33: } 34: catch(SQLException e) { 35: System.out.println("数据库连接失败!"); 36: } 37: } 38:}

【输出结果】 运行前数据库中 customer 表中的内容如图 12.14 所示,运行结果如图 12.15 所示。

图 12.14

【例 12.3】运行前数据库中 customer 表内容

181


第 12 章

182

图 12.15

Java 数据库程序设计

【例 12.3】的运行结果

【程序说明】 (1)第 8~13 行加载数据驱动程序。 (2)第 18~22 行采用参数传递的方式对数据库中对应记录的值进行修改。 (3)第 25~32 行对数据库中的修改后将数据输出到屏幕上。

12.5.6

ResultSet 接口

ResultSet 是结果集,用于保存 SQL 语句执行的结果。 ResultSet 包含 SQL 语句执行结果中的所有行,并且它通过一套 get 方法对这些行中不同列 数据进行访问。ResultSet.next()方法用于移动到 ResultSet 中的下一行,使下一行成为当前行。 1.行和光标 ResultSet 对指向其当前数据行的游标进行维护通过调用 next 方法。 每调用一次 next 方法, 游标向下移动一行。最初它位于第一行之前,因此第一次调用 next 将把游标置于第一行上,使 它成为当前行。随着每次调用 next 导致游标向下移动一行,按照从上至下的次序获取 ResultSet 中行。 在 ResultSet 对象或其父辈 Statement 对象关闭之前,游标一直保持有效。 2.列 getXXX 方法提供了获取当前行中某列值的途径。在每一行内,可按任何次序获取列值。但为 了保证可移植性,应该从左至右获取列值,并且一次性地读取列值。列名或列号可用于标识要从 中获取数据的列。例如,如果 ResultSet 对象 rs 的第二列名为“title”,并将值存储为字符 串,则下列任一代码都可获取存储在该列中的值: String s = rs.getString("title"); String s = rs.getString(2); 注意列是从左至右编号的,并且从列 1 开始。同时,用作 getXXX 方法的输入的列名不区 分大小写。 3.数据类型和转换 对于 getXXX 方法,JDBC 驱动程序试图将基本数据转换成指定 Java 类型,然后返回适合的 Java 值。例如,如果 getXXX 方法为 getString,而基本数据库中数据类型为 VARCHAR,则 JDBC 驱动程序将把 VARCHAR 转换成 Java String。getString 的返回值将为 Java String 对象。


12.5

JDBC 应用程序接口

183

4.流的使用 ResultSet 可以获取任意大的 LONGVARBINARY 或 LONGVARCHAR 数据。方法 getBytes 和 getString 将数据返回为较大的数据块(最大为 Statement.getMaxFieldSize 的返回值) 。但是, 以固定块获取较多的数据可能会更方便、安全,可以通过返回 ResultSet 类的 java.io.Input 流 来完成。从该流中可分块读取数据。注意:必须立即访问这些流,因为在下一次对 ResultSet 调 用 getXXX 时它们将自动关闭。 JDBC API 具有三个获取流的方法,分别具有不同的返回值: getBinaryStream 返回只提供数据库原字节而不进行任何转换的流。 getAsciiStream 返回提供单字节 ASCII 字符的流。 getUnicodeStream 返回提供双字节 Unicode 字符的流。 5.NULL 结果值 要确定给定结果值是否是 JDBC NULL,必须先读取该列,然后使用 ResultSet.wasNull 方法 检查该次读取是否返回 JDBC NULL。 当使用 ResultSet.getXXX 方法读取 JDBC NULL 时,方法 wasNull 将返回不同的 NULL 值。 Java null 值,对应于返回 Java 对象的 getXXX 方法(例如 getString、getBigDecimal、 getBytes 、 getDate 、 getTime 、 getTimestamp 、 getAsciiStream 、 getUnicodeStream 、 getBinaryStream、getObject 等)。 零值,对应于 getByte、getShort、getInt、getLong、getFloat 和 getDouble 等方法。 false 值,对应于 getBoolean 等方法。

本 章 小 结 本章从 JDBC 体系结构出发,作为对比,先介绍了 ODBC 及与 JDBC 的区别,随后介绍了 JDBC 的概念、应用结构、API 及 JDBC 驱动程序的分类,并且通过实例介绍了如何使用 JDBC API 建立 与数据库的连接和在 JDBC 中处理 SQL 查询及查询结果。

练习与思考 12.1

什么是 JDBC? JDBC 提供了哪些主要功能?

12.2

JDBC 有哪两种应用结构?每种应用结构有哪些特点?

12.3

JDBC 驱动程序有哪 4 种类型?每种类型驱动程序的主要功能是什么?

12.4

JDBC 具有哪些主要特点?

12.5

JDBC 数据库访问流程是什么?

12.6

JDBC URL 一般有哪两种格式?

12.7

JDBC API 提供了哪些常用的类与接口?它们有什么主要功能?

12.8 针对表 book_shop 数据库中的表 book,使用 JDBC API 设计一个程序,该程序能够完成对 book 表的 插入、删除、修改等操作。 12.9

编写一个程序,以书号、书名、作者为顺序输出 book_shop 中 book 表的内容。


第 13 章

184

第 13 章

Java Servlet 程序设计

Java Servlet 程序设计

学习目标 理解和掌握 Servlet 的基本概念、Servlet 的运行环境、Servlet 的生命周期和基本方法、 Servlet API  能够对 Servlet 数据库程序设计和 Servlet 会话程序设计等知识点达到综合运用的程度 Java Servlet 是 Java Web 服务器端的应用程序,它是 Web 服务器软件的扩展,主要用于产 生动态网页输出。同时,Servlet 结合 JDBC 可以进行 Web 数据库应用,即在客户端浏览器通过调 用 Servlet 可以浏览和操纵服务器上数据库中的数据。Servlet 的运行方式仍可看成是客户机/ 服务器模式,主要还是用来响应和处理用户的请求。 本章首先对 Servlet 的基本概念、运行环境、生命周期及 Servlet API 进行介绍,然后探讨 Servlet 结合 JDBC 进行综合应用。

13.1

Java Servlet 工作原理

Servlet 是 Java 编写的服务器端应用程序,Servlet 可以被编译成类,由 Web 服务器动态装 入并运行。Servlet 通过扩展服务器的功能,以提供客户请求和响应的服务。 当客户机发送请求至服务器时,服务器将请求信息转发给 Servlet,由 Servlet 进行响应和 处理,并最后将结果通过服务器将返回给客户机。因此 Servlet 的工作原理与 CGI 类似,都是运 行在服务器端的程序。

13.1.1

Servlet 主要功能

Servlet 的功能涉及范围很广,具体来说明 Servlet 可完成如下功能: (1)创建并返回一个包含基于客户请求性质的动态内容的完整的 HTML 页面。 (2)创建可嵌入到现有 HTML 页面中的部分 HTML 页面(HTML 片段)。 (3)与其他服务器资源(包括数据库和基于 Java 的应用程序)进行通信。 (4)用多个客户机处理连接,接收多个客户机的输入,并将结果广播到多个客户机上。 (5)为服务器提供定制的处理服务。

13.1.2

Servlet 的执行过程

在 Web 服务器与客户机交互过程时,Servlet 应用程序的执行过程是: ( 1 ) 客 户 机 浏 览 器 向 服 务 器 提 交 了 一 个 指 定 servlet 的 HTTP 请 求 , 例 如 http://MyServer/servlet/ MyServlet。

(2)Web 服务器接收到来自客户请求后,分析这个请求,将其转发给对应 servlet。如果该 Servlet


13.1

Java Servlet 工作原理

185

未被加载,则由 Web 服务器将其载入 Java 虚拟机并执行该程序。 (3)Servlet 对收到的客户机请求进行处理,然后将处理的结果返回给服务器。 (4)服务器把 Servlet 的处理结果发送到客户机的浏览器。 整个处理过程参见图 13.1,注意处理的大部分过程对客户 web 浏览器是完全不可见的,从 一般用户的角度看,Servlet 应用程序生成的页面与其他 Web 页面没有任何区别。

图 13.1

13.1.3

Servlet 的执行过程

Java Servlet 与 CGI 的比较

Servlet 继承了 Java 的所有优点,并且它与任何协议无关,同时也具有平台无关性,与 CGI 比较起来它有如下的优点: (1)便于信息共享数据,Java Servlet 之间能共享数据。而每个 CGI 程序的调用都开始一个 新的进程,调用间通讯通常要通过文件进行。 (2)方便快捷,Java Servlet 能自动的粘贴和解码 HTML 表单数据,读和设置 HTTP 头等其他 大量功能。CGI 程序是作为单独过程运行的,通常调用时间较长,在每次调用的时候都要发生。 而内存中的 servlet 可以非常迅速地加载。 (3)高效率,传统 CGI 对每一个 HTTP 请求都要产生一个新的进程。而 Java Servlet 在 Java 虚拟机上,每个请求由一个 Java 线程(thread)响应,而不是一个操作系统进程。 (4)功能强大,Java Servlet 可以很容易的实现对 CGI 来说是不可能或很困难的事务。如 Java Servlet 能直接和服务器进行通讯。

13.1.4

Servlet 的运行环境

第 9 章介绍过 Tomcat 服务器的安装及配置方法,下面以 Tomcat 为例说明 Servlet 运行环境 的构建方法。 如果访问或调用 Tomcat Web 服务器上的 Servlet 程序, 需要在 Tomcat 的发布目录的 WEB-INF 目录下建立一个目录 classes,将编译成 class 文件的 Servlet 文件放在该目录下。后的按照包 路径 下例中,Tomcat 服务器上 Servlet 程序的发布目录结构如下: D:\Tomcat 4.1\webapps\root\WEB-INF\classes 如在 D:\Tomcat 4.1\webapps\root\WEB-INF\classes 目录下, 有一个名为 HelloWorld.class 的 Servlet 程序,那么客户机访问的 URL 为: http://sureness:8080/serlvet/HelloWorld


第 13 章

186

13.1.5

Java Servlet 程序设计

Servlet 程序的两种基本结构

Servlet 程序通常的结构是,先导入必须的包,java.io 包(要用到 PrintWriter 等类) 、 javax.servlet 包 ( 要 用 到 HttpServlet 等 类 ) 以 及 javax.servlet.http 包 ( 要 用 到 HttpServletRequest 类和 HttpServletResponse 类) ;然后进行 Servlet 加载时的初始化;对用 户的请求进行处理;使用 out 把处理的结果发送到客户浏览器;最后是 Servlet 执行完毕的卸载 善后处理。 Servlet 程序的实现有两种基本结构。一种是从 GenericServlet 继承,处理一般的请求;另 外一种是从 HttpServlet 继承而来,处理 GET 请求,所谓的 GET 请求,可以把它看成是当用户在 浏览器地址栏输入 URL、点击 Web 页面中的链接、提交没有指定处理方法的表单时的 HTTP 请求。 GenericServlet 类用 service 方法来处理用户的请求, GenericServlet 类 servlet 对任何 浏览器端的请求都做同样处理。 HttpServlet 类用 doGet 和 doPost 方法处理用户的请求。 在用 HttpServlet 类时可以用 doGet 和 doPost 分别处理浏览器端的 Get 请求和 Post 请求。实际上在 HttpServlet 类中也可以重载 service 方法来处理任何请求。目前一般的 Servlet 程序通常从 HttpServlet 类继承,因为 HttpServlet 类增强了的 HTTP 请求的处理能力。 doGet 和 doPost 方法都有两个参数,这两个参数的类型分别为 HttpServletRequest 类型和 HttpServletResponse 类型 (在 service 方法中参数类型为 ServletRequest 和 ServletResponse) 。 HttpServletRequest 提供访问有关请求的信息的方法,例如表单数据、HTTP 请求头等等。 HttpServletResponse 除了提供用于指定 HTTP 应答状态(200,404 等) 、应答头(Content-Type, Set-Cookie 等) 的方法之外, 最重要的是它提供了一个用于向客户端发送数据的 PrintWriter 类 。 对于简单的 Servlet 来说,它的大部分工作是通过 PrintWriter 类的 println 方法生成向客户端 发送的页面。 下面的代码显示了一个从 GenericServlet 继承的 Servlet 程序,它可以处理从浏览器端发 来的请求并向浏览器发送字符串“ HelloWorld!”,它用 service 方法来处理请求。 1: import javax.servlet.*; 2: import javax.servlet.http.*; 3: import java.io.*; 4: import java.util.*; 5: public class HelloWorld extends GenericServlet { 6: public void init() {}//初始化函数在 Servlet 加载时执行 7: public void service(ServletRequest req,ServletResponse res)throws ServletException, IOException{// 使用"req"读取和请求有关的信息(比如 Cookies)和表单 数据使用"res"指定 HTTP 应答状态代码和应答(比如指定内容类型,设置 Cookie) 8: PrintWriter out=res.getWriter();// 使用 "out"把应答内容发送到浏览器 9: out.println("HelloWorld!"); 10: } 11: public void destroy(){}//在 Servlet 卸载时执行 12: }

下面的代码显示了一另一个 Servlet 程序,它和上面的程序有同样的功能,不同的是它从 HttpServlet 继承而来,它用 doGet 方法来处理请求。


13.1 1: 2: 3: 4: 5: 6:

7: 8: 9: 10: 11: 12:

Java Servlet 工作原理

187

import java.io.*; // 导入所需的包 import javax.servlet.*; import javax.servlet.http.*; public class HelloWorld extends HttpServlet { public void init(){} //初始化函数在 Servlet 加载时执行 public void doGet(HttpServletRequestrequest, HttpServletResponse response) // 使 用 "request"读取和请求有关的信息(比如 Cookies) 和表单数据 使用"response"指定 HTTP 应答状态 代码和应答(比如指定内容类型,设置 Cookie) throws ServletException, IOException { PrintWriter out = response.getWriter(); // 使用 "out"把应答内容发送到浏览器 out.println("HelloWorld! ") } public void destroy(){} //在 Servlet 卸载时执行 }

使用 doGet 和 doPost 方法时为提高程序的可靠性,通常要抛出异常。

13.1.6

Servlet 的生命周期与基本方法

Servlet 程序一般是动态被装载和执行,Web 服务器有一个管理器负责装载和初始化特定的 Servlet 程序,Servlet 工作完毕后要卸载释放所占用的系统资源。因此 Servlet 程序在服务器 中的运行是一个动态过程,表现为一定的生命周期。 Servlet 程序的生命周期如图 13.2 所示,分为三个阶段。初始化时期,服务器装载并开始运 行 Servlet 程序;执行时期,Servlet 程序接受来自客户端的请求并进行处理;结束时期,处理 完请求后,卸载 Servlet 程序。 下面对 Servlet 生命周期各主要阶段的具体说明。 (1)初始化时期 在初始化时期,服务器装载 Servlet 程序。服务器在如下情况时装入 Servlet,已配置自动 装入选项,则在启动服务器时自动装入;客户机首次向 Servlet 发出请求时;重新装入 Servlet 时。 当一个服务器装载 Servlet 时,运行 Servlet 的 init() 方法进行初试化。 Servlet 的 init() 方法声明为: public void init(ServletConfig config) throws ServletException { super.init(); //一些初始化的操作,如数据库的连接 } 需要说明的是,要在 init()结束时调用 super.init()。init()方法不能反复调用,一旦调 用就是重新装载 Servlet,否则只能到调用 destroy 方法卸载 Servlet 后才能再调用。


第 13 章

188

Java Servlet 程序设计

图 13.2

Servlet 的生命周期

(2)Servlet 的执行时期 在服务器装载并初始化 Servlet 后,Servlet 就能够处理客户端的请求了,在 GenericServlet 中用 service 方法(在 Httpservlet 中通过 doGetdoGet 或 doPost 方法)做到这一点。Servlet

能同时运行多个 service(或 doGet,doPost)方法。 (3)Servlet 结束时期 Servlet 运行结束时被服务器卸载。Servlet 运行结束的时,系统需要收回 Servlet 程序在 init()方法中分配、使用的资源。Servlet 的 destory()方法可用来完成相应的工作。同 init() 方 法一样,destory()方法也只能执行一次。 Servlet 的 destroy()方法声明为: public void destroy() { //回收在 init()中启用的资源,如关闭数据库的连接等。 } 在 Servlet 的整个生命周期内,init()和 destroy()方法只能被调用一次,而 service()(或 doPost()/ doGet())方法可以反复调用。所以 Servlet 的生命周期又可看成是 service()方法或 doPost()/doGet()方法的执行过程。 如果 Servlet 程序是从 GenericServlet 类继承的,程序运行的过程可看成是如下方法的执行 过程: init(), service(), service(), ..., service(), destroy(); 如果 Servlet 程序是从 HttpServlet 类继承的,程序运行的过程又可看成是如下方法的执行过 程: init(),doPost()/doGet, doPost()/doGet), ..., doPost()/doGet, destroy(); 例 13.1 程序演示了 Servlet 生命周期的 init()和 destroy()方法。程序结果见图 13.3。 【例 13.1】TestServlet.java。 1: 2: 3: 4: 5: 6: 7: 8: 9:

import import import import

javax.servlet.*; javax.servlet.http.*; java.io.*; java.util.*;

public class TestServlet extends HttpServlet { String test; static final private String CONTENT_TYPE = "text/html; charset=GBK"; //Initialize global variables


13.1

Java Servlet 工作原理

189

10: 11: 12: 13: 14:

public void init() throws ServletException { test="我已被装载"; } //处理 HTTP Get 请求 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 15: response.setContentType(CONTENT_TYPE); 16: PrintWriter out = response.getWriter(); 17: out.println("<html>"); 18: out.println("<head><title>testservlet</title></head>"); 19: out.println("<body>"); 20: out.println("<p>"+test+"</p>"); 21: out.println("</body></html>"); 22: } 23: public void destroy() { //回收资源 24: } 25: }

【运行结果】 如图 13.3 所示。

图 13.3

【例 13.1】的运行结果

【程序说明】 该程序采用的 doGet()方法。对于从 HttpServlet 继承来的 servlet 来说,它会根据客户端 发出的请求方式,调用 doGet()或 doPost()方法,用户一般至少重载 doGet 或 doPost 中的一个 方法。

13.2

Servlet API

Servlet API 由 javax.servlet 包和 javax.servlet.http 包的组成。javax.servlet 包有与 通信协议无关的一般性类 GenericServlet,它被 javax.servlet.http 包的 HttpServlet 类继承, 并增加了与 HTTP 相关的功能。


第 13 章

190

13.2.1

Java Servlet 程序设计

与 Servlet 有关类、接口的关系

javax.servlet 包提供了“请求-响应”通信方式的一般性服务器端程序类 GenericServlet。 javax.servlet.http 包中的 HttpServlet 类由 GenericServlet 继承而来,主要实现了 HTTP 有关 的功能,可用于处理 HTTP 请求。 javax.servlet 包中有七个接口和五个类,它们分别是 Servlet 接口、ServletRequest 接口、 ServletResponse 接口、ServletConfig 接口、ServletContext 接口、RequesDispatchert 接口 和 SingleThreadModel 接口,GenericServlet 类、ServletInputStream 类、ServletOutputStream 类、ServletException 类、UnavailableException 类。 javax.servlet 包 中 比 较 重 要 的 接 口 与 类 有 Servlet 接 口 、 ServletRequest 接 口 、 ServletResponse 接口和 GenericServlet 类。 javax.servlet.http 包有无个接口和四个类,它们分别是它们分别是 HttpServletRequest 接 口 、 HttpServletResponse 接 口 、 HttpSession 接 口 、 HttpSessionContext 接 口 和 HttpSessionBingingEvent 接口,Cookie 类、HttpServlet 类、HttpSessionBingingEvent 类和 HttpUtils 类。 javax.servlet.http 包 中 比 较 重 要 的 接 口 与 类 有 HttpServletRequest 接 口 、 HttpServletResponse 接口、HttpSession 接口和 HttpServlet 类。 javax.servlet 包、javax.servlet.http 包与它们所包含的类和接口的关系示意,如图 13.4 所示。

图 13.4

13.2.2

与 Servlet 有关类、接口的关系

Servlet 接口

Servlet 接口包含了所有的 Servlet 最基本的方法。由 13.2.1 可知,实际在使用 Servlet 时, 一般使用 GenericServlet 类或 HttpServlet 类,这两种类都实现了 Servlet 接口。 Servlet 接口中的主要方法和功能如下: Interface Servlet(){ public abstract void init(ServletConfig); //初始化 Servlet,在收到任何请求前,必须确保已初始化完毕


13.2

Servlet API

191

public abstract ServletConfig getServletConfig(); //返回一个 ServletConfig 对象,包含初始化参数和 Servlet 的启动设置。 public abstract String getServletInfo(); //返回一个包含 Servlet 信息的字符串,如作者、版本等 public abstract void service (ServletRequest,ServletResponse); //执行一次来自客户端的请求 public abstract void destroy();

//释放 Servlet 所占用的全部资源

}

13.2.3

GenericServlet 类

GenericServlet 类实现了 Servlet 接口,是一个实用的 servlet 类,它的主要方法和功能如 下: Class GenericServlet Implements Servlet{ public void init(ServletConfig); //初始化 Servlet,在收到任何请求前,必须确保已初始化完毕 public ServletConfig getServletConfig(); //返回一个 ServletConfig 对象,包含初始化参数和 Servlet 的启动设置。 public String getServletInfo(); //返回一个包含 Servlet 信息的字符串,如作者、版本等 public void services(ServletRequest,ServletResponse); //执行一次来自客户端的请求 public void destroy(); //释放 Servlet 所占用的全部资源 }

13.2.4

HttpServlet 类

HttpServlet 类是一个抽象类,它继承了 GenericServlet 类,简化了 HTTP1.1 协议的 Servlet 程序的过程。它用 doGet 或 doPost 方法代替了 service 方法,在使用时必须至少重载 doGet 或 doPost 方法的一个。如果重载 doGet 方法还要考虑用 getLastModified()方法来缓存 HTTP 的应答数据 下面是 HttpServlet 类中一些主要的方法: Class HttpServlet extends GenericServlet{ protected void doGet(HttpServletRequest req,HttpServletResponse resp); //完成一次 HTTP GET 操作 protected long getLastModified(HttpServletRequest req); //返回请求对象的最后修改时间 protected void doPost(HttpServlet req,HttpServletResponse resp); //完成一次 HTTP POST 操作 protected void doPut(HttpServlet req, HttpServletResponse resp); //完成一次 HTTP PUT 操作 protected void doDelete(HttpServlet req,HttpServletResponse resp);


第 13 章

192

Java Servlet 程序设计

//完成一次 HTTP DELETE 操作 HttpServlet(); // HttpServlet 类的构造方法 }

doGet 方法用来处理 HTTP 的 GET 请求。GET 请求仅仅允许客户从 HTTP 服务器上 “取得” (GET) 资源。GET 请求被认为是可靠的,不会产生副作用。方法 doGet 的缺省执行将返回一个 HTTP 的 BAD_REQUEST 错误。 doGet 方法的声明格式为: protected void doGet(HttpServletResquest request, HttpServletResponse response) throws ServletException, IOException; doPost 方法用来处理 HTTP 的 POST 请求。POST 操作包含了在必须通过此 servlet 执行的请 求中的数据。由于它不能立即取得资源,POST 请求有可能产生一些副作用。DoPost 方法的缺省 执行将返回一个 HTTP 的 BAD_REQUEST 错误。当编写 Servlet 程序时,为了支持 POST 操作必须在 子类 HttpServlet 中实现(implement)此方法。 doPost 方法的声明格式为: protected void doPost(HttpServletResquest response) throws ServletException, IOException;

request,

HttpServletResponse

例 13.2 采用了 doGet 方法,使服务器接收带输入参数的请求,并向浏览器返回一段带参数 字符串及请求处理次数。 【例 13.2】HelloServlet.java 1: 2: 3: 4: 5: 6: 7: 8: 9:

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HelloServlet extends HttpServlet{ public void init(ServletConfig conf) throws ServletException{ super.init(conf); connections=0; //初始化请求处理次数为 0 } public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException{ 10: String name=req.getParameter("name"); 11: ServletOutputStream out=res.getOutputStream();// 获得一个与浏览器连接的 链路,用于发送输出结果 12: res.setContentType("text/html"); //设置为 HTTP 协议 13: out.println("Servlet 应用"); 14: out.print("向 Servlet 发请求,"); 15: out.println(name); 16: out.println(""); 17: out.print("你是第"); 18: connections++; 19: out.println(Integer.toString(connections)); 20: out.print("个请求者!"); 21: out.close(); 22: }


13.2

Servlet API

193

23: public String getServletInfo(){ //返回一个描述 Servlet 的字符串 24: return "hello world Servlet"; 25: } 26: int connections; //用于记录调用次数 27: }

【运行结果】 如图 13.5 所示。

图 13.5

【例 13.2】的运行结果

【程序说明】 ( 1 ) 通 过 浏 览 器 测 试 Servlet 程 序 , 在 浏 览 器 中 输 入 下 列 URL 地 址 : http://sureness:8080/servlet/ HelloServlet?&name=小刘 Servlet 程序 HelloServlet 接收到输入参数小刘后,进行处理返回到客户端浏览器的结果如 图 13.5。 (2)程序第 5~8 行进行 Servlet 初始化,设置调用次数为 0。第 9~21 行为 Servlet 执行阶 段,先从浏览器获得参数 name 的值,然后建立一个输出端口向浏览器发送字符串。

13.2.5

ServletRequest 接口

当客户向 Servlet 发出请求时,Servlet 使用 ServletRequest 接口获取客户端的请求信息, 该接口是由用户来实现的。 ServletRequest 对象作为一个参数传递给 service()方法。在使用 HttpServlet 类时,一般 使用它的子接口 HttpServletRequest。 下面是 ServletRequest 接口中定义的主要方法: Interface ServletRequest{ public abstract int getContentLength(); //返回请求数据的长度 public abstract String getContentType(); //返回请求数据的类型 public abstract String getProtocol(); //返回请求数据的协议和版本 public abstract String getScheme(); //返回请求中所使用的 URL 类型 public abstract String getServerName(); //返回收到请求主机的服务器名字 public abstract String getServerPort(); //返回发出请求代理的端口号 public abstract String getRemoteAddr(); //返回发出请求代理的 IP 地址


第 13 章

194

Java Servlet 程序设计

public abstract String getServerHost(); //返回发出请求代理的完整主机名 public abstract String getParameter(String name); // 返回请求中指定的名字的参数 public String getParameterValues(String name); // 返回请求中指定的名字的参数值 }

13.2.6

ServletResponse 接口

ServletResponse 接口一般在 Servlet 的 service()方法中调用。在 service()方法中可以使 用 ServletResponse 中的方法,把数据返回给客户端,ServletResponse 对象是作为一个参数传 递给 service()方法的。 下面是 ServletResponse 接口中定义的主要方法: Interface ServletResponse{ public abstract void setContentLength(int len); //设置应答内容的长度 public abstract void setContentType(String type);//返回请求的指定名称属性 public abstract ServletOutputStream getOutPutStream(); //返回写应答数据的一个输 出流 } 例 13.3 程 序 使 用 ServletRequest 接 口 中 的 方 法 , 获 取 请 求 协 议 、 文 件 名 称 并 用 ServletResponse 接口中的方法把结果发送到客户端,程序运行结果见图 13.6。 【例 13.3】ReqHead.java。 1: 2: 3: 4: 5: 6: 7: 8: 9:

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ReqHead extends GenericServlet{ public void init(ServletConfig conf) throws ServletException{ super.init(conf); } public void service(ServletRequest req, ServletResponse res) throws IOException{ String name=req.getParameter("name"); //获得一个与浏览器连接的链路,用于发送输出结果 10: ServletOutputStream out=res.getOutputStream(); 11: res.setContentType("text/html");//设置为 HTTP 协议 12: out.println("协议: "+req.getProtocol()); 13: out.println("文件名: "+req.getServerName( )); 14: out.close(); 15: } 16: public String getServletInfo(){//返回一个描述 Servlet 的字符串 17: return(""); 18: } 19: }

【运行结果】 如图 13.6 所示。


13.2

图 13.6

13.2.7

Servlet API

195

【例 13.3】的运行结果

HttpServletRequest 接口

HttpServletRequest 是 ServletRequest 接口的子接口,它代表了一个 Http Servlet 请求, 它一般作为一个参数传递给 doGet 或 doPost 方法,除了完成和 ServletRequest 接口相同的功能 外,它还允许在 doGet 或 doPost 中防卫 HTTP 协议特定的头部域信息。 下面是 HttpServletRequest 接口定义的主要方法: Interface HttpServletRequest{ public abstract String getMethod(); //返回请求所用的方法名 public abstract String getServletPath(); //返回被调用的 Servlet 路径 public abstract String getRemoteUser(); //返回发出请求的用户名 }

13.2.8

HttpServletResponse 接口

HttpServletResponse 是 ServletResponse 接口的子接口, 它代表了一个 Http Servlet 响应, 它一般作为一个参数传递给 doGet 或 doPost 方法,除了完成和 ServletResponse 接口相同的功 能外,它还允许 doGet 或 doPost 操作 HTTP 协议的头部域信息,此接口定义了很多常量来知识服 务器返回的状态。 下面是 HttpServletResponse 接口定义的主要方法: Interface HttpServletResponse{ public abstract void addCookie(Cookie cookie);//给头部域加上特定的 cookie 信息 public abstract void setStatus(int sc); //设置状态编码 public abstract void addDateHeader(java.lang.String name, long date); //给头部域加上名字和日期信息 }

13.3

Servlet 程序设计实例

本小节给出一个 Servlet 程序实例, 其主要功能是利用一个 HTML 表单, 向服务器端的 Servlet


第 13 章

196

Java Servlet 程序设计

程序提交相关的信息,用户添完表单后,进行提交。Servlet 接收到提交的信息后,发出响应信 息传送到客户端的浏览器上。HTML 表单 ask.html 和 Servlet 程序 answer.java 源代码见例 13.4, 程序运行的输入和输出见图 13.7A 和 13.7B。 【例 13.4】Servlet 程序接收表单提交数据并进行响应。 (1)表单 ask.html 代码 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <title>询问</title> </head> <body> <form method="POST" action="--WEBBOT-SELF--"> <p>

</p>

<p align="center">姓名:<input type="text" name="T1" size="20"></p> <p align="center"><textarea rows="9" name="S1" cols="32"></textarea></p> <p align="center"><input type="submit" value="提交" name="B1"><input type="reset" value= "全部重写" name="B2"></p> </form> </body> </html>

(2)Servlet 程序 answer.java 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class answer extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { res.setContentType("text/html;charset=GB2312"); PrintWriter out = new PrintWriter(res.getOutputStream()); out.println("<html><body><title>anwser</title> "); String name=req.getParameter("T1"); String nameGb=Gb2312ToUnicode(name); if(nameGb.equals("小刘")){ String content=req.getParameter("S1"); String contentGb=Gb2312ToUnicode(content); out.println("<p>你好,小刘!</p>"); out.println("<p>你发给我的信息是:"+contentGb+"</p>"); }else{ out.println("<p>对不起,我不认识你!</p>") } out.flush(); out.println("</body></html>"); } public static String Gb2312ToUnicode(String s) throws IOException {


13.3 24: 25: 26: 27: 28: 29:

Servlet 程序设计实例

byte ba[]=new byte[s.length()]; for(int i=0;i<s.length();i++) ba[i]=(byte)s.charAt(i); return new String(ba,"GB2312"); } }

【运行结果】 如图 13.7 所示。

(a) 客户端 HTML 输入表单

(b) 客户端接收到的 Servlet 响应信息 图 13.7

【例 13.4】的运行结果

197


第 13 章

198

13.4

Java Servlet 程序设计

Servlet 会话

HTTP 是一个无状态协议,因此必须有一种机制来保存某个用户一系列请求的当前状态,这就 是所谓的 Session,又称为会话。当一个用户用浏览器发出一个 HTTP 请求时便产生了一个会话, 一般来说只要浏览器浏览是这个网站的页面,那么这个会话就始终存在。 在 Servlet 网络应用编程时,大量用到 Session 会话。如用户在网络应用中登录时,经常把 用户的基本信息(用户名,部门…)保存在 Servlet 会话中。 javax.servlet.http 包提供了 HttpSession 接口,使得用户可以在访问一个 Web 服务器的多 个页面时能够保存用户本身的会话信息。具体来说可以用此接口保存一些信息,即使用户的浏览 器页面切换,仍然可以提取、修改或者删除这些信息。 HttpSession 接口中主要的会话跟踪的方法如下: Interface HttpSession(){ public void putValue(String name,Object value); //该方法绑定一对名字/值数据,把它存储到当前会话中。如果会话中已经存在该名 //字,则将替换它 public Object getValue(String name); //该方法用于读取存储在会话中的对象,也就是读取该对象所捆绑的名字 public String getValueNames(); //该方法将返回存储在会话中的一组当前捆绑的名字 pubic void removeValue(String name); //该方法将从会话中删除指定的捆绑信息,它把与捆绑关联的名字作 为字符串参数 } 例 13.5 给出了一个 Servlet 会话程序,它主要对 HttpSession 接口中的方法的用法进行了 演示。此程序用一个 HttpSession 保存用户的初始输入,每次访问这个值加 1 并显示该值。程序 【例 13.5】count.html <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> <meta name="ProgId" content="FrontPage.Editor.Document"> <title>New Page 1</title> </head> <body> <form method="POST" action="http://sureness:8080/servlet/tf1.count"> <p><input type="text" name="T1" size="20"><input type="submit" value="提交" name="B1"><input type="reset" value="全部重写" name="B2"></p>


13.4

Servlet 会话

199

</form> </body> </html> count.java 1: public class count extends HttpServlet { 2: static final private String CONTENT_TYPE = "text/html; charset=GBK"; 3: 4: public void init() throws ServletException { 5: } 6: 7: public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 8: response.setContentType(CONTENT_TYPE); 9: PrintWriter out = response.getWriter(); 10: String value=request.getParameter("T1"); 11: HttpSession ses=request.getSession(true); 12: ses.putValue("i",value); 13: out.println("<html>"); 14: out.println("<head><title>testservlet</title></head>"); 15: out.println("<body>"); 16: out.println("<p>i="+value+"</p>"); 17: out.println("<a href='http://sureness:8080/servlet/tf1.count'>再看 看</a>"); 18: out.println("</body></html>"); 19: } 20: 21: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 22: response.setContentType(CONTENT_TYPE); 23: PrintWriter out = response.getWriter(); 24: HttpSession ses=request.getSession(true); 25: String value=(String)ses.getValue("i"); 26: int intvalue=new Integer(value).intValue(); 27: ses.putValue("i",new Integer(intvalue).toString()); 28: out.println("<html>"); 29: out.println("<head><title>testservlet</title></head>"); 30: out.println("<body>"); 31: out.println("<p>i="+new Integer(intvalue).toString()+"</p>"); 32: out.println("<a href='http://sureness:8080/servlet/tf1.count'>再看 看</a>"); 33: out.println("</body></html>"); 34: } 35: 36: public void destroy() { 37: } 38: }

【运行结果】


200

第 13 章

Java Servlet 程序设计

如图 13.8 所示。

(a) 客户端 HTML 输入表单

(b) 点击提交按纽后 Servlet 的处理结果


13.4

Servlet 会话

201

(c) 点击“再看看”后 Servlet 的处理结果 图 13.8

13.5

【例 13.5】的运行结果

Servlet 数据库程序设计举例

Servlet 程序可以产生动态网页,这种动态性真正体现在与数据库在一起的综合应用上。编 写一个使用 JDBC 的 Servlet 依然要进行数据库的连接、执行、处理和关闭过程,不同之处在于 对数据的显示,通常的方法是将数据库数据转成 HTML 发送到客户端。 例 13.6 给出一个 Servlet 的数据库程序,它利用 JDBC-ODBC 桥访问第 12 章中的 Sqlsever 数据库 Book_Shop,从 Book_Shop 的 book 表中查询的高等教育出版社的出版的教材信息,得到的 结果集被转成为 HTML 表格并返回给客户端。该程序分为分为两部分,Query.htm 表单输入要查询 的出版社名,看见 Servlet 程序 BookQuery.java 完成处理并返回结果。Query.htm 表单输入见图 13.9,查询的结果见图 13.10。 【例 13.6】Query.htm <html> <head> <meta http-equiv="Content-Language" content="zh-cn"> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> <meta name="ProgId" content="FrontPage.Editor.Document"> <title>按出版社查询</title> </head> <body bgcolor="#C0C0C0"> <p align="center"><font size="5">按出版社查询</font></p> <p align="center">

</p>

<form method="POST" action="http://srueness:8080/servlet/tf1.BookQuery"> <p align="center">请输入你要查询的出版社<input type="text" name="T1" size="20"></p> <p align="center"><input type="submit" value="提交" name="B1"><input type="reset" value="全部重写" name="B2"></p> </form> </body> </html> BookQuery.java 1: 2: 3: 4: 5: 6:

import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class BookQuery extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res)


202 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40:

第 13 章

Java Servlet 程序设计

throws IOException, ServletException { String company=req.getParameter("T1"); res.setContentType("text/html;charset=GB2312"); PrintWriter outh = res.getWriter(); outh.println("<HTML><BODY>"); outh.println("<CENTER><H2>"); outh.println("计算机专业图书的书目:<BR><BR></H2>"); String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; String url = "jdbc:odbc:book_shop"; String user = ""; String password = ""; try { Class.forName(driver); } catch(Exception e) { outh.println("不能加载驱动程序:" + driver); } try { Connection con = DriverManager.getConnection(url,user,password); Statement smt = con.createStatement(); ResultSet rst = smt.executeQuery("select * from book where company='"+company+"'"); outh.println("<H3>"); while(rst.next()) { outh.println(rst.getString(1)+" "+rst.getString(2)+" "+rst.getString(4)+" "+rst.getString(5)+"<BR>"); } outh.println("</CENTER></H3>"); outh.println("</BODY></HTML>"); } catch(SQLException e) { outh.println("数据库连接失败!"); } } }

publish_


13.5

Servlet 数据库程序设计举例

图 13.9

图 13.10

203

输入查询条件表单

数据库查询结果

【程序说明】 程序第 8 行获得用户输入的出版社名,程序第 14~28 行建立数据库连接并进行查询,第 29~ 34 行输出查询结果。 BookQuery servlet 存在下列一些问题: (1)它只在小数据量时才能正常工作。如果你要面对的是有成百上千条记录的表,那么在一 个 HTML 表格中显示所有的数据显然是不适当的。这不但会花费许多时间,而且从用户的角度讲 意义也不大。 (2)表中所有列中的数据在放到 HTML 表格里之前都被转化为字符串。对于图像之类的二进 制数据来说,这也是不恰当的。 (3)对于每一个 GET 请求,servlet 都要建立一个新的数据库连接。执行数据库连接是非常 耗时的操作,效率很低。实际上,为每一个新请求创建新的数据库连接会使一个 Web 服务器很快 陷入阻塞。 下面我们就来看一看如何解决这些限制。 将输出分成多个页面 如果要返回给用户大量数据,不一定非要将所有数据放在一个页面上。这不但使用户难于浏 览这些数据,而且生成和下载 HTML 页面也会浪费大量的时间。 解决这个问题的一个办法就是先输出部分数据分别,用户通过继续点击来查看下一部分数 据。INTERNET 上的搜索引擎在输出查询结果就采用的是上述方式。 例 13.7 的 Servlet 列出了书库中满足用户查询条件的所有的书籍。基本的代码和 BookQuery Servlet 相似,所以这里只指出主要的不同。首先,限制处理结果集显示的最大行数,在 HTML 中 加入一个 NEXT 按钮,见图 13.11。这个按钮被按下时,查询请求再次提交。当显示表中数据的最


第 13 章

204

Java Servlet 程序设计

后一页的时候,Next 按钮就不会被生成了,见图 13.12。 【例 13.7】BookPage.java 1: import java.io.*; 2: import java.sql.*; 3: import javax.servlet.*; 4: import javax.servlet.http.*; 5: public class BookPage extends HttpServlet { 6: int count=0; 7: int pagecount=3; 8: String company; 9: String[] label={"编号","书名","价格","作者","出版社","出版日期","数量"} 10: public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { 11: if(company==null){ 12: company=req.getParameter("T1"); 13: } 14: int rowcount=0; 15: boolean hasmore=false; 16: String uri=req.getRequestURI(); 17: res.setContentType("text/html;charset=GB2312"); 18: PrintWriter outh = res.getWriter(); 19: outh.println("<HTML><BODY>"); 20: outh.println("<CENTER><H1>"); 21: outh.println("书库中的目录信息:</H1>"); 22: String driver = "sun.jdbc.odbc.JdbcOdbcDriver"; 23: String url = "jdbc:odbc:book_shop"; 24: String user = ""; 25: String password = ""; 26: try { 27: Class.forName(driver); 28 } 29: catch(Exception e) { 30: outh.println("不能加载驱动程序:" + driver); 31: } 32: try { 33: Connection con = DriverManager.getConnection(url,user,password); 34: Statement smt = con.createStatement(); 35: ResultSet rst = smt.executeQuery("select * from book where publish_company"); 36: ResultSetMetaData rsmd=rst.getMetaData(); 37: int columns=rsmd.getColumnCount(); 38: outh.println("<TABLE ALIGN=CENTER BORDER=1 WIDTH="100%">"); 39: outh.println("<TR>"); 40: for(int i=1;i<=columns;i++) { 41: outh.println("<TD>"+label[i]); 42: outh.println("</TD>"); 43: } 44: outh.println("</TR>\n"); 45: while(rst.next()) {


13.5 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: }

Servlet 数据库程序设计举例

rowcount++; if(rowcount>count*pagecount && rowcount<=(count+1)*pagecount) { outh.println("<TR>"); for(int i=1;i<=columns;i++) { outh.println("<TD>"); outh.println(rst.getString(i)); outh.println("</TD>"); } outh.println("</TR>"); } if(rowcount>(count+1)*pagecount) { hasmore=rst.next(); break; } } outh.println("</TABLE>"); outh.println("</CENTER>"); outh.println("</BODY></HTML>"); } catch(SQLException e) { outh.println("数据库连接失败!"); } if(hasmore) { outh.println("<BR>"); outh.println("<CENTER>"); outh.println("<FORM METHOD=GET"+"ACTION\""+uri+"\">"); outh.println("<INPUT TYPE=\"Submit\" VALUE=\"Next\">"); outh.println("</FORM>"); outh.println("</Center>"); } else outh.println("\n<CENTER><H2>数据显示完毕</H2></CENTER>"); outh.println("</BODY></HTML>"); outh.flush(); count++; }

205


练习与思考

图 13.11

图 13.12

205

分页查询第一页

分页查询最后一页

本 章 小 结 本章介绍了 Java Servlet 程序的工作原理和两种基本结构。Servlet 是在服务器端运行的 Java 程序,主要是对客户端的请求进行响应和处理。在 Servlet 编程时要用到 Servlet API。 Servlet 程序的最大的用处是与 HTML 表单结合起来,进行 Web 应用,这也是 Java 的生命力所在。

练习与思考 13.1

什么是 Servlet? Servlet 具有哪些主要功能?

13.2

Servlet 执行的基本流程是什么?

13.3

简述一般用途的 Servlet 处理客户请求的过程。

13.4

简述 HTTP 的 Servlet 处理客户端 GET 或 POST 的请求过程,它们有什么区别。


206

第 13 章

Java Servlet 程序设计

13.5

编写一个 HTTP 的 Servlet,在浏览器上显示“这是我的第一个 HTTP Servlet”。

13.6

指出 Java Servlet 与 Java Applet 的生命周期的相同点与不同点。

13.7

编写一个 Servlet 程序,由数据库载入一个 GIF 文件。

13.8

编写一个 Servlet 程序,以表格方式输出 book_shop 中的 customer 表。

13.9

编写一个 HTML 表单,用户可以在其中输入想要显示信息的条数,当用户按提交按钮时所指定的

Servlet 完成以下工作:它可以取得用户的显示信息的条数并在 book_shop 中的 customer 表中取出相应条数据并 输出,要求按照数据分页的方式输出记录。


14.2

第 14 章

JavaBeans 的属性

207

Java 组件程序设计

学习目标  理解 JavaBean 的基本概念、JavaBean 的设计特点、JavaBeans 技术和 EJB  掌握 JavaBean 的属性、JavaBean 事件的设置和构造  熟练掌握 BDK 的安装和配置、BeanBox 的启动  熟练掌握 Bean 的创建和 Applet 的生成 软件组件是指具有特殊的功能、相对独立的软件模块。软件组件遵照一定的接口规范可以实 现互操作,进而完成软件系统的集成。软件组件技术是一种把软件功能作为一个组件来组装成应 用程序的技术,它包括开放式组件库,以及把库中组件组合起来的应用开发工具、标准和环境。 真正的组件是可以通过应用程序生成器工具来管理,并且独立于开发语言功能的模块。Java 利用 JavaBeans 技术构建软件组件。 本章介绍了 JavaBeans 的基本概念、JavaBeans 开发工具箱(BDK)及用来测试 JavaBeans 的 BeanBox,并通过实例说明如何构建组件及生成对应的 Applet 程序。

14.1

JavaBeans 概述

软件组件技术来自硬件的即插即用的思想,即使用者不必过问系统的内部细节,只需根据外 部特性进行维护、升级和重构,以此提高系统的性能,减少升级、维护费用。 JavaBeans 就是一种软件组件技术,相类似于 Visual Basic 中的 ActiveX 控件,或 Dephi 的 Visual components,是专门用于可视化开发环境下使用的组件。有了这样的组件后,软件开发 人员可在应用程序窗口上,直接采用“拖”或“放”的方式进行使用,而不必关心组件实现的内 部细节,因此能大大提高软件开发效率和质量。

14.1.1

JavaBeans 的基本概念

JavaBeans 是 Java 的软件组件模型。在该模型中,JavaBeans 组件可以被修改或与其他组件 结合生成新组件或完整的程序。 JavaBeans 组件是可在应用程序构造器中进行可视化操作的可重用的软件。有些 Bean 具有可 视化外观(如按钮、滚动条或数据库视图),而有些却不具有可视化外观,但它们都可以使用应用 程序构造器可视化地进行组合。 JavaBeans 组件也只是一个 Java 类,与一般的类最大的差异是,这种类必须符合 JavaBeans 的规范。JavaBean 的最重要特征是可视化地操作与定制功能,而类主要是为编程提供某种功能, 它并不具有可视化操作的特点。


第 14 章

208

Java 组件程序设计

每个 JavaBeans 组件的功能可能不一样,但它们都必须支持自检、定制、事件、属性及持 久性。

14.1.2

JavaBeans 的特点

JavaBeans 技术使得 Java 平台可以通过简单地链接相互独立的组件,生成完整的应用程序, 其主要特点是: (1)JavaBeans 易于创建、使用和可移植 JavaBeans 易于创建,尤其是结构简单的组件非常容易使用,也可用 Java 来创建复杂的组件。 JavaBeans 组件是完全可移植的,不管它们的开发平台如何。 (2)JavaBeans 利用了 Java 平台的许多优点 JavaBeans 使用了已内置到 Java 平台中的类显示机制。这个机制使用了 Java 的独一无二的 内部检查和映射技术。这意味着,Java 不必在运行时调用其他复杂的登记机制来支持接口的出版 和显示。除此之外,JavaBeans 不需要程序序员另外编程,这与 Java 使用轻量级且易于理解的设 计思想一致。如 AWT 组件能自动地成为 JavaBeans 组件。JavaBeans 库还会为简单组件提供缺省 的组件行为。另外,JavaBeans 构造管理器还能通过检查组件的 get 和 set 方法,自动产生属性 编辑器。 (3)JavaBeans 使用了健壮的分布式计算机制 Java 开发人员可以同时使用几种分布式计算方式,而不会使 JavaBeans 变得更加复杂或加重 Java 平台的负担。如开发人员能够通过使用 Java 的远程方法调用(RMI)、工业标准 CORBA IDL 接 口进行远程对象访问,或通过其他的分布式计算机制在 Java 应用中增加分布式组件交互功能。 开发人员能够根据移植、性能和遗传集成等方面的要求选择最适合它们的机制。 (4)灵活的组件编辑器 JavaBeans 允许开发人员在创建时确定组件的各种属性、检查器和编辑器,这样,开发人员 可以提供最有效的方法,让组件用户使用分配给自己的全部功能。如数据库连接组件提供商不能 仅简单地提供一个很长且复杂的属性,单供开发人员在创建组件时使用,而是需要将各种属性分 类组织起来,以便让组件用户可以可视化地对这些属性进行装配。 JavaBean 开发工具 API 将有 助于让组件开发人员为它们的组件创建最好的属性编辑器。

14.1.3

JavaBeans 和 EJB

JavaBeans 是 Java 的组件模型,在 JavaBeans 规范中定义了组件的事件和属性等特征。EJB 也定义了一个 Java 组件模型,但是 EJB 组件模型和 JavaBeans 组件模型是不同的。JavaBeans 的 重点是允许开发者在开发工具中可视化操纵组件。JavaBeans 规范详细地解释了组件间事件登记、 传递、识别和属性使用、定制和持久化的应用编程接口和语意。EJB 的侧重点是详细地定义了一 个可以便携地部署 Java 组件的服务框架模型。因此,其中并没提及事件,因为 Enterprise bean 通常不发送和接受事件。同样也没有提及属性——属性定制并不是在开发时进行,而是在运行时 (实际上是在部署时)通过一个部署描述文件来描述。 JavaBeans 和 EJB 都是组件模型规范,但是前者侧重于开发工具中应用程序组装的问题,而 后者则侧重于部署组件的服务框架的细节。但不要错误地认为 JavaBeans 是用于客户端的开发,


14.2

JavaBeans 的属性

209

EJB 是用于服务器端的开发。JavaBeans 也可作为进行非图形化服务器端 Java 应用程序开发的 组件模型。区别是当使用 JavaBeans 创建服务器应用时,还得设计整个的服务框架。而用 EJB 框架是现成的,只需遵守它的 APIs。对于复杂的服务器端应用程序,显然使用 EJB 比重新开发 更简单。

14.2

JavaBeans 的属性

JavaBeans 组件的属性与一般 Java 程序中所指的属性,或者说与所有面向对象的程序设计 语言中对象的属性是一个概念,它确定 JavaBeans 组件的外观和行为,具体表现为就是类中的 变量。 JavaBeans 组件的属性的,按照不同的功能可分为 Simple, Index, Bound 与 Constrained 属性 四类。 1.属性访问的方法 JavaBeans 组件属性可以被访问, set 和 get 方法专门用来设置和得到它们的值。 设置属性的值,采用如下格式: public void set 属性名(属性类型

要设置的新值)

得到属性的值,采用如下格式: public 属性类型 get 属性名() 假设某个 bean 有一个名为 count、类型为 int 的属性,那么设置和得到该性质值的方法可以 是: public void setCount( int newValue ) public int getCount() 2.Simple 属性 简单属性可理解为一般的属性,对它进行访问用 get/set 方法。属性名与和该属性相关的 get/set 方法名对应,即如果有 setXXX()和 getXXX()方法,则暗指有一个名为"XXX"的属性。如 果有一个方法名为 isXXX,则通常暗指"XXX"是一个布尔属性(即 X 的值为 true 或 false)。 下面的这个例子中给出了 Simple 属性的使用方法。 1: public class alden1 extends Canvas { 2: String ourString= "Hello"; //属性名为 ourString,类型为字符串 3: public alden1(){ //alden1()是 alden1 的构造函数, 4: setBackground(Color.red); 5: setForeground(Color.blue); 6: } 7: public void setourString(String newString) {// set 方法 8: ourString=newString; 9: } 10: public String getourString() {// get 方法 11: return ourString; 12: } 13: }

3.Indexed 属性


210

第 14 章

Java 组件程序设计

一个 Indexed 属性表示一个数组值。 使用与该属性对应的 set/get 方法可取得数组中的数值。 也可一次设置或取得整个数组的值。 下面的这个例子中用 Indexed 属性访问数组。 1: 2: 3: 4: 5: 6: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:

public class alden2 extends Canvas { int[] dataSet={1,2,3,4,5,6}; // dataSet 是一个 indexed 属性 public alden2() { setBackground(Color.red); setForeground(Color.blue); } public void setDataSet(int[] x){ // 设置整个数组 dataSet=x; } public void setDataSet(int index, int x){ // 设置数组中的单个元素值 dataSet[index]=x; } public int[] getDataSet(){// 取得整个数组值 return dataSet; } public int getDataSet(int x){ // 取得数组中的指定元素值 return dataSet[x]; } }

4.Bound 属性 Bound 属性是指当属性的值发生改变时,要通知其他的对象,以使它们作出适当的反应。每 次属性值改变时,就激活一个 PropertyChange 事件(在 Java 程序中,事件也是对象) 。事件封 装了属性名、属性的原值、属性变化后的新值。这种事件传递到其他的 Bean,至于接收事件的 Bean 应做什么反应由它自己决定。 下面的这个例子给出了由属性的值发生改变时,引起的事件激活。 1: Public class alden3 extend Canvas{ 2: Sting outString=Hello;//ourString 是一个 bound 属性 3: Private propertyChanges Support = new PropertyChangesSupport(this); 4: Public void setString(string newString){ 5: String oldString = outString; 6: outString=newString; /*outString 的属性值已发生变化,于是接着激活属性改变事件*/ 7: changes.friePropertyChange("outString",oldString,newString); 8: } 9: public String getString(){ 10: return outString; 11: } 12: public void addPropertyChangesListener(PropertyChangesListener 1){ 13: changes.addPropertyChangesListener (1); 14: } 15: public void removePropertyChangesListener(PropertyChangesListener 1){ 16: changes.removePropertyChangesListener(1); 17: }


14.2

JavaBeans 的属性

211

在上面的例子中,调用 Changes 的 addPropertyChangesListener 方法把其他 JavaBeans 注册入 outString 属性的监听者队列 1 中, 队列 1 中可存储在任何 Java 对象中。也可使用 alden3 的 removePropertyChangesListener 方法,从监听者队列 1 中注销指定的对象,使 alden3 的 outString 属性的改变不再与这个对象有关。当然,编制程序时,也可以直接调用这两个方法, 将其他 Java 对象与 alden3 挂接。 5.Constrained 属性 某些时候,JavaBean 的一些属性是不希望被改变的。或者,是否修改需要经过其他 JavaBean 或容纳该 JavaBean 的容器来判断,这些属性称为受限属性。受限属性实现也很简单,即在修改 属性之前激发一个受限属性的改变事件,如果不发生异常,则继续其修改工作,改变属性值;若 由于其他 JavaBean 的干预而产生了异常,就不进行修改。

14.3

JavaBean 的事件

事件处理是 JavaBeans 体系结构的核心之一。通过事件处理机制,可让一些组件作为事件源, 产生可由其他组件接收的事件。这样,不同的组件就可组合在一起,组件之间通过事件的传递进 行通信。 JavaBeans 事件的实现需要进行两个步骤。首先需要定义 java.util.EventObject 类的一个 导出类,来封装关于事件的信息;然后需要定义接口,它继承自 java.util.EventListener 类; 下面进一步说明每个步骤,增加读者对事件处理的理解。 (1)定义 java.util.EventObject 类的一个导出类,来封装关于事件的信息。按照惯例, 这个导出类的名字应该是“事件的名字 Event”的形式。 下面的程序创建了一个鼠标移动事件,注意事件的名字。 18:public class MouseMovedExampleEvent extends java.util.EventObject{ 19: protected int x, y; 20: MouseMovedExampleEvent(java.awt.Component source, Point location) { //创建鼠标移动事件,注意事件名 21: super(source); 22: x = location.x; 23: y = location.y; 24: } 25: public Point getLocation() { //获取鼠标位置 26: return new Point(x, y); 27: } 28:}

(2)定义接口,它继承 java.util.EventListener 类。按照惯例,接口名字应该为“事件 的名字 Listener”。该接口必须定义一个方法,用来接收字符串参数。 例如: public class MouseMovedExampleEvent extends EventObject { //定义鼠标移动事件对象 ... // 在此类中包含了与鼠标移动事件有关的状态信息


第 14 章

212

Java 组件程序设计

} interface MouseMovedExampleListener extends EventListener { //定义鼠标移动事件监听者接口 void mouseMoved(MouseMovedExampleEvent mme){ //定义鼠标移动事件监听者所支持的方法 }

14.4

BDK 安装与配置

BDK(Bean Development Kit)提供了对 JavaBeans API 的支持,还提供了一个环境 BeanBox 来对 Bean 组件进行操作。BDK 是一个纯 Java 程序,它依赖于 JDK(Java Development Kit),因 此 BDK 必须在安装 JDK 之后后装。

14.4.1

BeanBox 的启动

安装好 BDK 后,只要执行了\JDK\beans\beanbox 目录下的一个批处理文件 run.bat,就可开 始使用 BeanBox 工具了。BeanBox 提供了一个可以使用户可视化地设计、编辑和测试 Bean 的环 境。 运行 run.bat 批处理文件即可启动 BeanBox,并显示 ToolBox(即工具框) 、BeanBox 主窗口、 属性单(PropertySheet)三个窗口,如图 14.1 所示。每个窗口都完成 BeanBox 中的某个功能。

图 14.1

BDK 界面

BeanBox 是一种使用 JDK 解释器的独立的应用程序。在 C:\JDK\beans\beanbox 目录中有一个 批处理文件 run.bat, 负责在执行 BeanBox 前设置自己的 CLASSPATH 环境变量。 不能直接使用 Java 解释器运行 BeanBox,而是应该使用该批处理文件来运行 BeanBox,这样才能正确设置其专用的 CLASSPATH 值。BeanBox 的批处理文件 run.bat 内容如下: rem if "%OS%" == "Windows_NT" setlocal set CLASSPATH=classes;..\lib\methodtracer.jar;..\infobus.jar java sun.beanbox.BeanBoxFrame 利用 BeanBox 可以将不同的 bean 下载到一个组合窗口中;调整和移动 bean;编辑一个 bean 的 外在属性;运行一个定制器配置一个 bean;将一个 bean 的事件源连接到一个事件句柄方法;连接不 同 bean 的关联属性; 存储和重载 bean 的集合; 得到一个 bean 的自检报告; 从 jar 文件添加新的 bean。


14.3

14.4.2

JavaBean 的事件

213

在 BeanBox 中应用已建好的 Bean 组件

BDK 工具包提供了许多示例 beans 用于演示 JavaBeans 体系结构的各个方面。这些演示 bean 的 源代码存在 BDK 的\demo\sunw\demo\*中,BeanBox 本身的源代码则在 beanbox\sun\beanbox\*中。这 些示例 bean 对于 JavaBean 的学习非常有用,因为它们有助于对 JavaBean 标准规范的理解和使用。 下面以 BDK 工具包提供的 JDBC SELECT bean 为例,说明如何具体使用一个 bean。 JDBC SELECT bean 是一个可运行 SQL SELECT 语句的组件。由于 JDBC SELECT bean 配置比较 复杂,所以它带有自己的定制器以便配置这个 bean。 本例除了使用 JDBC SELECT bean 外,还要使用按钮组件 OurButton,通过这两个组件的应用, 在前面 11 章中设置的 MS Sqlserver 的数据库 Book_Shop 中,查询 book 表的所有内容。具体步 骤如下: (1)在 BeanBox 窗口中添加一个 JDBC SELECT bean 和一个 OurButton 组件实例,在属性窗 口中将 OurButton 按钮的 label 属性设为“执行 SQL 语句”。 (2)选择 JDBC SELECT bean,选择 Edit|Customizer…,下拉菜单项,出现 sunw.demo. select.SelectCustomizer 定制器对话框,输入 JDBC- ODBC 数据源名称、user 和 password 后确 定。 (3)在 JDBC SELECT bean 属性对话框中输入要执行的 SQL 语句,如: SELECT * FROM book (4)选择“执行 SQL 语句”按钮。 (5)选择 Edit|Event|action|actionPerformed 下拉菜单项。BeanBox 窗口中在鼠标下会 出现一条直线箭头,可以用它连接“执行 SQL 语句”和“JDBC SELECT bean”。 (6)用鼠标将出现的直线连接到 JDBC SELECT bean 并单击鼠标按钮。在 BeanBox 窗口中会 出现 Event Target Dialog 对话框。JDBC SELECT bean 的方法都列在对话框中。 选择 update 方法单击“OK” ,这时,BeanBox 会产生一个适配类。当 BeanBox 产生这些代码 后,单击 BeanBox 窗口中的“执行 SQL 语句”按钮,就可以触发 JDBC SELECT bean 开始工作,执 行结果如图 14.2。

图 14.2

JDBC SELECT bean 和 OurButton 构成的 SQL 查询


第 14 章

214

14.5

Java 组件程序设计

Bean 组件的创建

本节通过程序示例说明如何去创建一个 JavaBeans 组件。例子组件的功能是在一块区域内 (150×150 像素)显示指定多个椭圆,椭圆的位置和大小参数随机产生。椭圆有个数和颜色两个 属性,还提供了增加一个椭圆和减少一个椭圆方法。为了简单起见,先不讨论由父类继承来的其 他属性和事件。这个 JavaBeasns 组件只由一个类构成,并提供了登记和注销 PropertyChange Listener 的方法。 1.OvalDemo 组件的创建步骤 利用 BeanBox 可以创建一个简单的 bean,创建的基本过程如下: (1)首先编写程序创建一个 OvalDemo 组件,并编译 bean; (2)使用 makefile 将 OvalDemo 组件存储在 Java 档案文件(jar)中; (3)将这个 bean 载入到 BeanBox 的 ToolBox 中; (4)使用 BeanBox 装入这个 bean 的一个实例,对其进行测试。 2.OvalDemo 组件的创建过程 (1)OvalDemo.java 文件存放在 JDK 安装目录中的 jdk/beans/demo/sunw/demo/OvalDemo 子 目录下,与这个目录处于一级的其他目录中存储了其他 BDK 示例 bean。 (2)编写 OvalDemo.java 的源程序代码,并保存在上述指定的目录中。 【例 14.1】OvalDemo.java 程序代码如下: 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:

package sunw.demo.ovaldemo; import java.awt.*; import java.awt.event.*; import java.util.Vector; import java.util.Random; import java.beans.*; public class OvalDemo extends Canvas{ //变量定义 private int ovalNumber=5; //椭圆个数 private Random rd=new Random(); //用于产生随机数的变量 private Color foreGroundColor = Color.blue; //椭圆颜色 private PropertyChangeSupport changes=new PropertyChangeSupport (this); //存储监听者对象 12: public OvalDemo(){ 13: super(); 14: setSize(150,150); 15: } 16: public void paint(Graphics g){ 17: g.setColor(foreGroundColor); 18: int hx,hy,tx,ty; 19: for(int i=0;i<ovalNumber;i++){ //产生随机数绘制椭圆 20: hx=Math.abs(rd.nextInt())%75; 21: hy=Math.abs(rd.nextInt())%75; 22: while((tx=Math.abs(rd.nextInt())%75)==hx); 23: while((ty=Math.abs(rd.nextInt())%75)==hy); 24: g.drawOval(hx,hy,tx,ty);


14.5

Bean 组件的创建

215

25: } 26: } 27: public void oneMore(ActionEvent e){ //增加一个椭圆 28: ovalNumber++; 29: repaint(); 30: } 31: public void oneLess(ActionEvent e){ //减少一个椭圆 32: ovalNumber--; 33: repaint(); 34: } 35: public void addPropertyChangeListener(PropertyChangeListener l){ //登记监听者 36: changes.addPropertyChangeListener(l); 37: } 38:public void removePropertyChangeListener( PropertyChangeListener l){ //注销监听者 39: changes.removePropertyChangeListener(l); 40: } 41: public int getOvalNumber(){ //读属性 OvalNumber 42: return ovalNumber; 43: } 44: public void setOvalNumber(int num){ //写属性 OvalNumber 45: ovalNumber=num; 46: repaint(); 47: } 48: public Color getColor(){ //读属性 Color 49: return foreGroundColor; 50: } 51: public void setColor(Color newColor){ //写属性 Color 52: Color oldColor = foreGroundColor; 53: foreGroundColor = newColor; 54: changes.firePropertyChange("color",oldColor,newColor); 55: repaint(); 56: } 57: }

【程序说明】 注意上面程序的第 35~40 行,是登录和注销监听者的方法。第 41~47 行是对 ovalNumber 属性的读写方法。48~56 行是对 color 属性的读写方法,写方法中产生了 PropertyChange 事件, 使得其他 JavaBeans 组件可以体察到椭圆颜色的变化。 (3)编写 makefile 文件。这个 makefile 文件会编译 OvalDemo 组件并在 beans/jars 目录下 (此目录中存放了所有 BeanBox 示例的 jar 文件)产生一个 jar 文件。将编辑好的 makefile 文件 在 beans/demo 目录中保存为 OvalDemo.mk(Windows)或 OvalDemo.gmk(UNIX)。 【例 14.2】OvalDemo.mk 文件 1:#nmake 文件 2:CLASSFILES=\sunw\demo\ovaldemo\OvalDemo.class 3:JARFILE=..\jars\ovaldemo.jar 4:.SUFFIXES:.java.class


第 14 章

216

Java 组件程序设计

5:all:$(JARFILE) 6:#创建一个带有适当说明文件(manifest)的 JAR 文件 7:$(JARFILE):$(CLASSFILES) 8:jar cfm $ (JARFILE)<<manifest.tmp sunw\demo\ovaldemo\*.class 9:name:sunw/demo/ovaldemo/OvalDemo.class 10:Java –Bean:True 11:#编译一个普通的.java 文件的规划 12:{sunw\demo\ovaldemo}.java{.sunw\demo\ovaldemo}.class: 13:set CLASSPATH=. 14:Javac $< 15:Clean: 16: -del sunw\demo\ovaldemo\*.class 17: -del $(JARFILE)

【程序说明】 程序第 2 行说明 JavaBean 中引用了哪些类。 程序第 3 行说明了生成的 jar 文件的存放目录和文件名。 程序第 5~14 行是生成“.jar”文件的过程。在生成的“.jar”文件的同时,要生成与其内 容相对应的“.mf”文件。 应注意程序第 9 行,在指明 name 时,采用了“/”来标识路径,不能采用“\” ,否则会导致 装入 JavaBeans 失败。另外,各个文件名是区分大小写,必须与原文件名的大小写完全相符。 (4)运行 beans/demo 目录下的 makefile 文件。在 Windows 系统下运行: nmake –f

OvalDemo.mk

JavaBeans 中的各个类将被打包成 jar 文件,以备提供出去使用。jar 文件包含几个 zip 格 式的文件(.class 文件,gif 文件等),以及一个 manifest.mf 文件,描述 jar 文件的内容。为了 把 JavaBean 的若干个文件打包并放在 BDK 的合适目录下,通常使用 nmake 工具来辅助进行。 nmake.exe 可以从 ftp://ftp.microsoft.com/Softlib/MSLFILES/nmake15.exe 下载 nmake15.exe 文件,这是一个自解压文件,其中包括三个文件:nmake.exe,nmake.doc,nmake.txt。这里所

讲的适用于 win98/2000 平台,在 Unix 系统下,要用 gnumake 来代替 nmake,相应地.mk 文件 也要用.gmk 文件代替。 运行 makefile 文件过程中,会编译 OvalDemo 组件,然后在 beans/jars 目录下创建一个 jar 文件。BeanBox 就会在这个目录中寻找 OvalDemo.jar 文件。 (5)将新创建的 jar 文件载入 ToolBox 中。选择 File|LoadJar…菜单项,这时会产生一个 文件浏览器,选择 beans/jars 目录下的 OvalDemo.jar。然后,OvalDemo 组件就会出现在 ToolBox 窗口中,即图中的 ToolBox 窗口中的 OvalDemo 组件。 (6)在 BeanBox 窗口中放置一个 OvalDemo 实例。先单击 ToolBox 窗口中 OvalDemo 组件,然 后将鼠标移至 BeanBox 窗口中某处并单击,OvalDemo 组件显示在窗体中,如图 14.3 所示。


14.5

Bean 组件的创建

图 14.3

OvalDemo 组件

217


本 章 小 结

14.6

217

在 Applet 中使用 JavaBeans 组件

在 BeanBox 中可以创建 Applet,使用 BeanBox 中的 File|MakeApple…菜单从 BeanBox 的上 下文环境中产生 Applet。使用 BeanBox 产生 Applet 的步骤如下: (1)打开或创建“演示示例 bean”中的 OvalDemo.jar 例子; (2)选择 File|Make Applet 菜单项后出现 MakeApplet 对话框,如图 14.4 所示。 (3)使用默认的 JAR 文件和 applet 名,单击“OK”按钮,在 jdk/beans/beanbox/tmp/ myApplet 目录下产生 applet 需要的文件。

图 14.4

MakeApplet 对话框

用 appletviewer 来运行 applet: c:\JDK\beans\beanbox\tmp\myApplet>appletviewer mySpplet.html 运行结果如图 14.5 所示。

图 14.5

生成 MyApplet 的运行结果

本 章 小 结 本章介绍了 JavaBeans 的概念、特点和开发的基本方法,并给出了示例。有兴趣的读者还可 以参考有关 JavaBeans 的书籍和资料,以期有更深的认识和了解。JavaBeans 相对于一般的 Java 程序有其特殊性,要注意安排好组件事件互动关系的处理和对属性访问的控制。为了能在创建工 具中对它进行操纵,还需要考虑它的定制方法以及与创建工具之间的协调。JavaBeans 作为一种


第 14 章

218

Java 组件程序设计

可复用的软件组件模型,仍在不断发展变化之中,必然会日趋完善并在软件生产中发挥更大的作 用。

练习与思考 14.1

简述 JavaBeans。

14.2

说明使用 JavaBeans 的特点和优点。

14.3

对 JavaBeans 的 demo 下的其他组件进行演示。

14.4

试用 JavaBeans 技术编写一个时钟。


参 考 文 献

219

参 考 文 献 1

宋波.Java 应用开发教程.北京:电子工业出版社,2002

2

王胜捷.Java 程序设计.北京:中国铁道出版社,2002

3 4

汪学明,钟诚.Java 程序设计指导.重庆:重庆大学出版社,2001 Harvey M.Deitel, Paul J.Deitel.Java 程序设计教程.袁兆山等译.北京:机械工业出版

社,2002 5 6

Bruce Eckel.Thinking in Java.北京:机械工业出版社,2002 王克宏.最新 Java 2 核心类库详解(上、下).北京:清华大学出版社,1999

7

王克宏.Java 2 程序设计.北京:清华大学出版社,1999


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.