零基础学编程

零基础学Java

陈洁　著

ISBN：978-7-111-28624-0

本书纸版由机械工业出版社于2010年出版，电子版由华章分社（北京华章图文信息有限公司）全球范围内制作与发行。

版权所有，侵权必究

客服热线：+ 86-10-68995265

客服信箱：service@bbbvip.com

官方网址：www.bbbvip.com

新浪微博 @研发书局

腾讯微博 @yanfabook

目　录

出版说明

前言

第一篇　Java语言了解必备篇

第1章　Java技术概览

1.1　Java技术

1.1.1　Java编程语言

1.1.2　Java语言平台

1.1.3　Java语言的功能

1.1.4　Java语言的优点

1.2　Windows下的“HelloWorld”应用程序

1.2.1　创建应用程序的软件环境

1.2.2　创建“HelloWorld”应用程序的步骤

1.3　深入探讨“HelloWorld”应用程序

1.3.1　注释源代码

1.3.2　实现类定义

1.3.3　详解main方法

1.4　常见疑难解答

1.4.1　环境变量设置错误

1.4.2　语法错误

1.4.3　语义错误

1.4.4　运行时错误

1.5　小结

1.6　习题

第2章　Java开发工具及环境设置

2.1　Java开发工具简介

2.2　如何下载并安装JDK

2.3　JDK内置工具

2.3.1　JDK常用工具

2.3.2　JDK常用工具实例

2.3.3　应用程序发布工具（jar）

2.4　jar文件的创建与查看

2.4.1　创建jar文件

2.4.2　查看jar文件的内容

2.4.3　提取jar文件的内容

2.4.4　更新jar文件

2.4.5　运行打包的jar软件

2.5　JDK环境设置

2.5.1　设置Path环境变量

2.5.2　设置classpath环境变量

2.6　一个简单的Java应用程序

2.7　一个简单的Java Applet应用程序

2.7.1　编写一个Applet

2.7.2　编写一个用来调用Applet Java程序的HTML文件

2.7.3　Applet的运行过程

2.8　注释及内嵌式文档

2.9　常见疑难解答

2.9.1　JDK和J2SDK的区别

2.9.2　Java文件扩展名是否区分大小写

2.10　小结

2.11　习题

第3章　Java语言中的数据与运算符

3.1　数制

3.1.1　基本概念

3.1.2　Java语言中的数制表现形式

3.2　数据类型

3.2.1　整型

3.2.2　字符型

3.2.3　浮点型

3.2.4　布尔型

3.3　变量

3.3.1　变量的声明

3.3.2　变量的含义

3.3.3　变量的分类

3.4　变量如何初始化

3.5　常量

3.6　运算符

3.6.1　算术运算符

3.6.2　关系运算符

3.6.3　逻辑运算符

3.6.4　位运算符

3.6.5　移位运算符

3.6.6　赋值运算符

3.6.7　三元运算符

3.6.8　逗号运算符

3.6.9　转型运算符

3.6.10　运算符的优先级别

3.7　常见疑难解答

3.7.1　如何将十进制转换成二进制

3.7.2　转型运算符会引起精度问题，为什么还要使用它

3.8　小结

3.9　习题

第4章　程序设计中的流程控制

4.1　编程风格

4.2　条件语句

4.2.1　条件语句的种类

4.2.2　如何使用好条件语句

4.3　循环语句

4.3.1　for语句

4.3.2　while循环

4.3.3　do……while语句

4.4　中断与继续语句

4.4.1　中断控制语句

4.4.2　继续语句

4.5　分支语句

4.6　返回语句

4.7　常见疑难解答

4.7.1　普通循环是使用for语句还是while语句

4.7.2　一般的程序可否用分支语句来代替条件语句

4.8　小结

4.9　习题

第5章　数组

5.1　数组概念的引入

5.1.1　实例的引入

5.1.2　数组的概念

5.1.3　用实例说明数组的用处

5.2　基本数据类型的数组

5.2.1　基本类型数组的声明

5.2.2　基本类型数组的初始化

5.3　由实例引出不同数组种类及其使用

5.3.1　由实例引出一维数组及其使用

5.3.2　由实例引出二维数组及其使用

5.4　数组的综合实例

5.5　常见疑难解答

5.5.1　声明数组需要注意什么

5.5.2　数组在平时的程序代码中使用是否频繁

5.6　小结

5.7　习题

第6章　字符串的处理

6.1　字符串的基本概念

6.2　字符串处理的类库种类

6.2.1　字符串的赋值

6.2.2　字符串的处理类—String

6.2.3　字符串处理的方法

6.2.4　缓冲字符串处理类—StringBuffer

6.2.5　缓冲字符串StringBuffer类的构造器

6.2.6　缓冲字符串处理的方法

6.2.7　缓冲字符串类的特点

6.3　用实例演示如何处理字符串

6.4　如何格式化输出

6.5　常见疑难解答

6.5.1　“equals”和“==”的区别

6.5.2　String类为何被定义成final约束

6.5.3　char类型如何转换成int类型，int类型如何转换成字符串

6.6　小结

6.7　习题

第二篇　Java语言特色篇

第7章　类

7.1　JDK 5和JDK 6特性

7.1.1　什么是JDK

7.1.2　JDK 5的特点

7.1.3　JDK 6的特点

7.1.4　JDK 5与JDK 6的比较

7.2　用比较法阐述类的概念

7.3　Java中已有的类

7.3.1　Java的数学运算处理类Math

7.3.2　测试时间和日期的类Date

7.3.3　测试日历的类GregorianCalendar

7.3.4　日历处理的实例解析

7.4　用实例分析设计一个类的流程

7.4.1　如何在现实程序设计中提取一个类

7.4.2　设置器和访问器

7.4.3　总结

7.5　常见疑难解答

7.5.1　类在程序语言中起到了什么作用

7.5.2　设置器和访问器的作用

7.6　小结

7.7　习题

第8章　对象和包

8.1　对象

8.1.1　什么是对象

8.1.2　操作对象

8.1.3　初始化对象

8.2　对象家族中的成员

8.2.1　对象的成员方法

8.2.2　对象的成员变量

8.3　对象中访问控制符的重要性

8.3.1　什么是访问控制符

8.3.2　如何使用访问控制符及其重要性

8.4　重载

8.4.1　什么是重载

8.4.2　用实例来说明重载的意义

8.5　包

8.5.1　什么是Java中的包

8.5.2　如何实现包

8.5.3　什么是类路径和默认包

8.5.4　包的作用域

8.6　注释及嵌入文档

8.6.1　如何添加注释

8.6.2　类、方法等注释的方法

8.7　常见疑难解答

8.7.1　包在实际编程中究竟有什么作用

8.7.2　访问控制符在实际开发中有什么作用

8.7.3　一个文件中定义了两个class类是否生成一个.class文件

8.8　小结

8.9　习题

第9章　继承

9.1　什么是继承

9.1.1　继承的引出

9.1.2　继承的概念

9.2　如何实现继承

9.3　构造器的使用

9.3.1　什么是构造器

9.3.2　继承中构造器的初始化

9.3.3　替代父类和本身的方式

9.3.4　Java中的单继承性

9.4　继承中的覆盖现象

9.5　类之间的关系

9.5.1　依赖

9.5.2　聚合

9.5.3　继承

9.6　继承层次图

9.7　如何设计好继承

9.8　常见疑难解答

9.8.1　Java不支持多继承，如何处理一个类继承多个父类的情况

9.8.2　如果出现了带参数的构造器，可否不在代码中写出来

9.9　小结

9.10　习题

第10章　多态

10.1　多态的产生

10.2　多态的概念

10.2.1　使用多态编写程序

10.2.2　覆盖的应用

10.2.3　重载与覆盖的实例对比

10.2.4　覆盖的多态性

10.2.5　传值引用和传址引用

10.3　通过实例熟悉多态用法

10.4　绑定

10.4.1　静态绑定

10.4.2　动态绑定

10.5　超类

10.5.1　什么是超类

10.5.2　equals方法的使用

10.5.3　通用编程

10.6　常见疑难解答

10.6.1　动态和静态编译是什么

10.6.2　绑定与多态的联系是什么

10.6.3　多态与重载的区别是什么

10.7　小结

10.8　习题

第11章　接口与内部类

11.1　接口

11.1.1　接口概念的引入

11.1.2　接口的概念

11.1.3　接口的声明

11.1.4　接口的实现

11.1.5　接口的多重实现

11.1.6　接口的属性

11.1.7　接口的继承

11.1.8　接口的意义

11.2　内部类

11.2.1　使用内部类来访问对象

11.2.2　局部内部类

11.2.3　静态内部类

11.2.4　匿名内部类

11.3　常见疑难解答

11.3.1　匿名类如何在程序中使用

11.3.2　接口与继承有什么区别

11.4　小结

11.5　习题

第12章　抽象

12.1　抽象的概念

12.1.1　什么是抽象

12.1.2　抽象的综合实例

12.2　抽象类

12.2.1　什么是抽象类

12.2.2　抽象类的实例

12.3　抽象与接口的区别

12.4　常见疑难解答

12.4.1　抽象类和接口在概念上有什么区别

12.4.2　如何从设计理念上看待抽象类和接口

12.5　小结

12.6　习题

第13章　线程

13.1　线程的概念

13.1.1　进程及其使用环境

13.1.2　线程及其使用环境

13.2　线程的创建

13.2.1　如何创建线程

13.2.2　通过实例熟悉如何创建线程

13.3　线程的使用

13.3.1　线程的优先级

13.3.2　线程的休眠与唤醒

13.3.3　线程让步

13.3.4　线程同步

13.4　实例分析

13.4.1　生产者与消费者的模拟程序设计及分析

13.4.2　多消费者的模拟程序设计及分析

13.4.3　多生产者的模拟程序设计及分析

13.5　常见疑难解答

13.5.1　Java中线程与线程之间怎么通信

13.5.2　什么是进程的死锁和饥饿

13.5.3　什么时候会涉及线程程序

13.6　小结

13.7　习题

第14章　封装

14.1　封装简介

14.2　在程序设计中为什么要使用封装

14.3　结合实例讲述如何设计封装

14.4　在程序设计中设计封装的注意点

14.5　常见疑难解答

14.5.1　封装在现实开发中给程序员带来什么启发

14.5.2　封装在实际开发中的应用有哪些

14.6　小结

14.7　习题

第三篇　Java程序设计数据处理篇

第15章　Java输入与输出（I/O）

15.1　输入与输出的重要性

15.2　Java有哪些重要的输入/输出类

15.3　文件或目录信息的处理—File类

15.3.1　File常用的操作方法

15.3.2　文件处理方法的应用

15.3.3　文件和目录的完全处理

15.4　读取数据的媒介之一—流（stream）

15.4.1　什么是流

15.4.2　什么是输入流和输出流

15.4.3　字节输入流及输出流

15.4.4　使用文件字节输入流读取文件

15.4.5　使用文件字节输出流输出文件

15.5　多字节数据读取类—Filter类

15.5.1　Filter流的概念

15.5.2　处理字节类型外的文件流

15.5.3　字节类型外的文件输出

15.5.4　增强的多字节输出流DataOutput

15.5.5　增强的多字节输入流DataInput

15.6　读取数据的媒介之二—字符流（character stream）

15.6.1　字符流的概念

15.6.2　抽象字符输入流Reader类的使用

15.6.3　抽象字符输出流Writer类的使用

15.6.4　读取带缓存的BufferedReader字符流

15.6.5　带缓存的字符输出流BufferedWriter类

15.6.6　字符输入流FileReader类和输出流FileWriter类的使用

15.6.7　如何用标准输入流System.in来获取数据

15.6.8　打印输入流PrintWriter类与PrintStream类的区别

15.6.9　随机文件访问RandomAccessFile类的使用

15.7　利用对象序列化控制输入/输出

15.7.1　什么是对象序列化

15.7.2　基本数据和对象数据读写ObjectInput接口与ObjectOutput接口

15.7.3　对象序列化处理ObjectOutputStream类的使用

15.7.4　对象序列化处理ObjectInputStream类的使用

15.8　常见疑难解答

15.8.1　字节流与字符流主要的区别

15.8.2　输入流与输出流如何区分，各有什么作用

15.8.3　什么是管道流

15.9　小结

15.10　习题

第16章　异常与处理及内存管理

16.1　异常的概念

16.1.1　异常的分类

16.1.2　异常的声明

16.1.3　异常的抛出

16.2　异常的捕获

16.2.1　捕获多个异常

16.2.2　自定义异常

16.3　内存的管理和回收

16.4　常见疑难解答

16.4.1　为什么要声明方法抛出异常

16.4.2　为什么抛出的异常一定是已检查异常

16.5　小结

16.6　习题

第17章　Java中对数据的处理

17.1　如何将基本数据类型转换为对象

17.1.1　为什么要将基本数据转换为对象

17.1.2　Wrapper类的构造函数

17.1.3　Wrapper类的常用函数

17.2　如何处理随机性的数据

17.2.1　Random类的使用场合

17.2.2　通过实例熟悉Random类的常用方法

17.3　如何对数据进行排列、整理

17.3.1　Arrays类的常用方法

17.3.2　实例分析

17.4　常见疑难解答

17.4.1　Random类函数在现实生活中有什么用处

17.4.2　数组的排列和整理数据在实际应用中有什么用处

17.5　小结

17.6　习题

第四篇　Java数据结构

第18章　数据结构的接口

18.1　数据结构接口及实现

18.1.1　数据结构接口的实质

18.1.2　用实例来熟悉数据结构接口的实现

18.2　Collection集合接口与Iterator迭代器接口

18.2.1　熟悉Collection集合接口

18.2.2　List链表接口和Set接口

18.2.3　Map映射接口

18.2.4　Iterator迭代器接口

18.2.5　通过实例来认识迭代器的实现

18.3　常见疑难解答

18.3.1　Collection集合接口和Collections集合类的区别

18.3.2　ArrayList数组列表类和Vector存储类的区别

18.3.3　HashMap哈希映射和Hashtable哈希表的区别

18.3.4　数据结构的种类有哪些

18.3.5　List链表接口和Set接口的区别

18.4　小结

18.5　习题

第19章　Java中的数据结构

19.1　链表

19.1.1　什么是Java中的链表

19.1.2　用程序代码段实现对链表的添加

19.1.3　用程序代码段实现对链表的删除

19.2　数组列表类

19.2.1　什么是数组列表类

19.2.2　通过实例熟悉数组列表如何存储数据

19.3　散列表

19.3.1　什么是散列表

19.3.2　通过实例熟悉散列表如何存储数据

19.4　散列集

19.4.1　什么是散列集

19.4.2　通过实例熟悉散列集如何存储数据

19.5　树集

19.5.1　什么是树集

19.5.2　通过实例熟悉树集如何存储数据

19.6　映像

19.6.1　什么是映像

19.6.2　通过实例熟悉映像如何存储数据

19.7　枚举

19.8　属性集

19.9　常见疑难解答

19.9.1　哪些是线程安全的数据结构

19.9.2　Vector是什么样的数据结构

19.10　小结

19.11　习题

第20章　XML基础

20.1　XML的产生及与HTML的比较

20.2　XML的编辑工具

20.3　XML文档

20.3.1　XML的声明

20.3.2　文档类型的声明

20.3.3　元素

20.3.4　注释

20.3.5　处理指令

20.3.6　空白处理

20.3.7　行尾处理

20.3.8　语言标识

20.3.9　一个简单的有关XML的实例

20.4　格式良好的XML

20.5　什么是DTD

20.5.1　在XML文档中加入DTD

20.5.2　DTD的结构

20.5.3　几个简单有关DTD的实例

20.6　有效的XML

20.7　XML处理器/解析器

20.8　常见疑难解答

20.8.1　XML与HTML的区别

20.8.2　XML有哪些显示数据的方式

20.9　小结

20.10　习题

第五篇　Java高级应用篇

第21章　JDBC及其应用

21.1　数据库基础知识

21.1.1　什么是数据库

21.1.2　数据库的分类及功能

21.1.3　关系数据库的设计

21.1.4　数据库设计技巧

21.2　JDBC的概念

21.2.1　什么是JDBC

21.2.2　JDBC驱动程序的分类

21.2.3　JDBC的用处

21.3　SQL数据库技术

21.3.1　什么是SQL数据库

21.3.2　如何操作SQL数据库

21.3.3　安装SQL Server 2005

21.3.4　使用SQL创建数据库

21.4　JDBC基本编程

21.4.1　如何建立数据库

21.4.2　如何操作数据库

21.4.3　高级SQL类型

21.5　事务处理

21.5.1　什么是事务处理

21.5.2　事务处理的过程演练

21.6　预查询

21.7　常见疑难解答

21.7.1　操作数据库的具体步骤是什么

21.7.2　数据库中的视图、图表、默认值、规则、触发器、存储过程的意义

21.8　小结

21.9　习题

第22章　Swing组件与布局管理器

22.1　什么是Swing编程

22.2　MVC设计模式基础

22.3　简单框架设计及实例

22.3.1　创建一个空白的框架

22.3.2　创建框架的其他工作

22.4　简单图形按钮控件的设计及实例

22.4.1　Swing中的按钮控件

22.4.2　按钮的模型

22.4.3　添加按钮

22.4.4　如何添加单选按钮

22.4.5　复选按钮

22.4.6　ToggleButton按钮

22.5　简单文本输入组件的设计及实例

22.5.1　文本域的设计

22.5.2　密码域的设计

22.5.3　如何设计文本区

22.6　标签组件的设计及实例

22.7　选择组件的设计及实例

22.8　菜单的组件设计

22.9　对话框的种类及用法

22.10　复杂布局管理器的种类及用法

22.10.1　箱式布局的设计

22.10.2　网格组布局的设计

22.11　通过实例熟悉Swing编程

22.12　常见疑难解答

22.12.1　如何处理菜单的启用和禁用功能

22.12.2　如何编写快捷键的程序

22.13　小结

22.14　习题

第23章　Eclipse的使用

23.1　Eclipse简介

23.2　Eclipse界面简介

23.3　如何使用Eclipse进行开发

23.3.1　如何新建一个Java工程

23.3.2　如何新建一个Java的类创建类的步骤如下所示。

23.3.3　编写代码

23.4　常见疑难解答

23.4.1　Eclipse和UltraEdit两个开发工具的优缺点

23.4.2　什么是IDE环境

23.4.3　Eclipse与JBuilder两个开发工具哪个好

23.4.4　有关Eclipse的编译报错的原因

23.5　小结

23.6　习题

第六篇　Java程序设计网络篇

第24章　Applet设计

24.1　Applet的基本概念及生命周期

24.2　Applet的多媒体处理

24.2.1　声音处理

24.2.2　图像处理

24.3　浏览器与Applet的设计

24.3.1　如何将Applet嵌入浏览器

24.3.2　如何使用Applet类编程

24.3.3　如何用Applet控制浏览器

24.4　Applet的安全

24.5　一个Applet的综合实例分析

24.6　常见疑难解答

24.6.1　浏览器如何运行Applet

24.6.2　有关Applet无法在IE中显示的问题

24.7　小结

24.8　习题

第25章　网络编程基础

25.1　网络基础知识

25.2　TCP与UDP

25.3　端口与套接字

25.4　TCP程序设计基础

25.4.1　如何设计TCP的程序

25.4.2　一个简单的例子

25.5　UDP程序设计基础

25.5.1　如何设计UDP的程序

25.5.2　一个简单的例子

25.6　如何设计网络程序

25.6.1　单向通信综合实例

25.6.2　双向通信综合实例

25.7　常见疑难解答

25.7.1　TCP和UDP的区别

25.7.2　什么是TCP/IP协议，分为几层，有什么功能

25.7.3　什么是IP协议，IP地址如何表示，分为几类，各有什么特点

25.8　小结

25.9　习题

第七篇　Java程序设计综合实例

第26章　学校管理系统

26.1　模块划分

26.2　需求分析

26.3　登录界面的设计

26.3.1　登录界面的分析

26.3.2　登录界面的代码实现

26.3.3　登录界面的测试

26.4　主界面的设计

26.4.1　主界面类的分析

26.4.2　主界面类的代码

26.4.3　主界面类的实现

26.5　数据库系统的设计

26.5.1　数据库的分析

26.5.2　数据库中表的建立

26.5.3　数据库中代码段

26.6　学生信息系统界面的设计

26.6.1　学生类的设计

26.6.2　存储类的设计

26.6.3　学生信息界面的设计

26.7　教师信息系统界面的设计

26.7.1　教师类的设计

26.7.2　存储类的设计

26.7.3　教师信息界面的设计

26.8　领导信息系统界面的设计

26.8.1　领导类的设计

26.8.2　存储类的设计

26.8.3　领导信息界面的设计

26.9　小结

第27章　电话单辅助分析工具

27.1　系统设计

27.2　系统目标

27.2.1　文件操作用例

27.2.2　话单操作用例

27.2.3　话单维护用例

27.3　系统功能

27.3.1　文件操作功能

27.3.2　话单维护功能

27.3.3　话单操作功能

27.3.4　系统维护功能

27.4　业务流程

27.4.1　文件操作流程

27.4.2　话单维护流程

27.4.3　话单操作流程

27.5　软件模块设计

27.5.1　主界面模块

27.5.2　文件操作模块

27.5.3　话单操作模块

27.5.4　话单维护模块

27.6　软件打包发行

27.7　数据库配置

27.8　小结

出版说明

“零基础学编程”系列图书自2008年上市以来就受到了广大读者的青睐。本系列中的很多图书一上市就登上了编程类图书销售排行榜的前列。目前本系列中的很多图书已经多次印刷。之所以有如此好的市场表现，是与本系列书的品质和在读者中的口碑是分不开的。这套书上市后受到了广大读者的好评，很多学校也作为教材使用。

为了使本系列图书能紧跟技术趋势，更加适合读者学习和学校教学，我们结合最新技术和读者的建议，对本系列图书中的一些图书进行了改版（即第2版）。另外，还增加了一些新的品种，也一并放入本系列，以使本系列图书更加完善。

第2版图书所做的改进

第2版图书在第1版图书的基础上主要有以下改进：

❑增加了数小时的多媒体教学视频，使得学习更加直观和高效；

❑增加了课后习题，使得本系列书更加适合读者自我检测和学校教学使用；

❑专门制作了教学PPT，以方便相关专业老师教学使用；

❑增加了更多的项目实践内容，以增强实用性，提高读者的动手能力；

❑对图书的编排体例进行了梳理，以增强条理性和可读性；

❑对第1版图书的内容和结构有所调整，使得其更加合理和科学；

❑补充完善了一些新的内容，使其内容更加完善；

❑更正了第1版图书中出现的一些疏漏。

包括的书籍

本系列书本次推出11个品种。其中，标注了第2版的为第1版的改版，未标注的为本次新增加品种。具体如下：

《零基础学Java第2版》

《零基础学SQL》

《零基础学Java Web开发第2版》

《零基础学Linux C程序设计》

《零基础学Visual C++第2版》

《零基础学数据结构》

《零基础学Visual Basic第2版》

《零基础学算法》

《零基础学JavaScript第2版》

《零基础学计算机英语》

《零基础学SQL Server 2008》

光盘内容

本系列书有配套光盘。光盘的主要内容如下：

❑书中所涉及的程序源代码；

❑多媒体语音教学视频；

❑教学PPT；

❑免费赠送的大量编程电子图书和编程视频。

技术支持

本系列书有专门的技术论坛（http：//www.rzchina.net）和QQ群（群号：21948169）。读者学习过程中若有疑问，可以在论坛上或者QQ群里提问交流。另外，论坛上还有一些技术教程、视频动画，读者可以免费下载使用。

前言

随着软件的普及，开发技术的发展也日新月异，软件开发语言也不断地触及生活中的各个领域。而在这些众多的开发语言中，Java语言如异军突起，让程序员们从开始接受它到热衷于它。

为了帮助更多的Java语言爱好者，作者编写了本书，旨在帮助Java语言初学者更好、更快地掌握Java语言。

本书在内容编排和目录组织上都十分讲究，争取让读者能够快速掌握Java的开发技巧及应用方法。本书通过从最基本的知识开始，以循序渐进的方式，让读者能够拥有稳固的基础。另外，本书的一大特色就是，对于书中的绝大多数的知识点，会给出相应的实例程序。其目的是让读者能够理论联系实际。读者可以根据书中的一些实例，来检查自己掌握的程度。同时在讲解具体知识的时候，语言简单易懂，告诉读者如何实现特定功能，让读者在实际操作中熟悉Java开发技术。

本书特点

1.分解知识点，逐个掌握

本书涉及Java语言入门者所需掌握的所有知识点，并将这些知识点按照七个不同的侧重点分成七篇。

2.采用了大量丰富的实例

本书拥有大量的实例，能够让读者根据实例来进一步清楚地理解所介绍的知识点。

3.步骤清晰，说明详细

在介绍具体知识点的时候，为了让读者从开始就能知道这个知识点的原理，本书提供了详细的图例，说明这些知识是如何设计、实现以及应用的。而且，在稍显复杂的地方提供了详细的标注，让读者一看就明白整个知识点的设计原理和实现手段。

4.讲解通俗，步骤详细

在介绍大型实例时，每个实例的制作步骤都以通俗易懂的语言阐述，并穿插讲解各种技巧，在阅读时就像听课一样详细而贴切。读者只需要按照步骤操作，就可以轻松地完成一个实例的制作，不但掌握了开发的步骤，还掌握了开发的技巧。

本书内容

第1章主要介绍了一些Java的基本概念和特色。另外还介绍了有关Java的版本情况。

第2章主要介绍了Java的编程环境，包括如何设置这些编程环境。除了以上内容，本章还介绍了如何编写一个Java程序，并且还举了一个最简单的Java实例。

第3章主要介绍了Java中的所有基本数据类型以及运算符。

第4章主要介绍了如何使用流程控制语句来编写程序。

第5章主要介绍了数组的知识。

第6章主要介绍了字符串方面的知识，以及如何处理字符串。

第7章主要介绍Java语言的第一个特点：类的概念。

第8章主要介绍Java语言的第二个特点：对象和包。

第9章主要介绍Java语言的第三个特点：继承的概念及其用法。

第10章主要介绍Java语言的第四个特点：多态的概念及其用法。

第11章主要介绍Java语言的第五个特点：接口和内部类的概念及其用法。

第12章主要介绍Java语言的第六个特点：抽象的概念及其用法。

第13章主要介绍Java语言的第七个特点：线程的概念及其用法。

第14章主要介绍Java语言的第八个特点：封装的概念及其用法。

第15章主要介绍了如何使用Java中的类库处理数据的输入和输出。

第16章主要介绍了Java语言中的异常机制和内存回收机制。

第17章主要介绍了Java语言中如何对数据进行处理。

第18章主要介绍了数据结构接口的基本知识。

第19章主要是介绍了数据结构接口的种类及其用法。

第20章主要介绍了XML方面的知识。

第21章主要介绍了数据库基础、SQL语言的应用、JDBC的概念和用法。

第22章主要介绍了使用Swing类库进行图形化编程。

第23章主要介绍了Eclipse开发工具的使用。

第24章主要介绍了Applet小程序的知识。

第25章主要介绍了如何编写网络程序。

第26～27章列举了两个综合实例：学校管理系统和电话单辅助分析程序。

本书具有知识全面、实例精彩、指导性强的特点，力求以全面的知识性及丰富的实例来指导读者掌握Java编程技术。

本书适用的对象

❑Java程序语言的初学者。

❑有一定的C语言编程经验的初级编程人员。

❑有一定的计算机基础的编程爱好者。

❑大、中专院校的学生。

❑社会培训类学校的学生。

❑其他语言向Java语言转移的编程人员。

本书作者

本书由陈浩主笔编写，同时参与编写和资料整理的有刘亮亮、丁士锋、何涛发、陈杰、黄曦、罗嘉、段春江、韩红宇、李嵩峰、莫光胜、王天国、李蓉、吴荣、宋祥亮、刘宇、吕晓鹏、王大伟、吴小平、张卫忠、施佳鹏、王嘉、吴雪、阳婷、张秀妍、王江、王志永、杨红、郑维龙、王松、张文。

编者

2009年11月

第一篇　Java语言了解必备篇

第1章　Java技术概览

Java是一种出色的面向对象跨平台编程语言。在当今的软件领域几乎无处不见Java语言活跃的身影，然而Java语言究竟是什么，本章将从两个方面解释Java的“身世”。Java既是一种编程语言，也是一个软件平台。本章还将从多个方面分析Java到底能为我们做什么。

本章重点：

❑Java语言的功能和特点。

❑学习创建简单的Java程序。

❑学会处理常见的Java编译错误。

1.1　Java技术

Java既是编程语言也是软件平台，作为编程语言的Java有自己的一套机制，实现源程序到处理器可执行程序间的过渡与转换，作为软件平台的Java为程序员提供了编程接口和Java虚拟机（JVM），下面将依次介绍。

1.1.1　Java编程语言

Java作为一种高级语言具有许多优良特性，如跨平台、面向对象、分布式、多线程、安全性、健壮性、简单性等。这些优良的特性在读者学习完本书后会有很好的体会。

在Java语言中的所有源代码都可以通过无格式的记事本编写，并保存为.Java文件，因为通过该扩展名，Java编译器会知道该文件是自己可以处理的文件。经过编译后的文件为.class文件，该文件是字节码文件，此时.class文件不能被处理器直接读取执行，必须通过虚拟机转换成二进制文件后，才可以被处理器执行。图1.1是Java程序的开发过程，整个过程说明了上面叙述的内容。

Java是跨平台的高级编程语言，这里的平台是指操作系统平台，如Windows、UNIX、Mac、Linux等。使用Java语言编写的程序一次编译就可以在所有上述平台上运行，就是因为有Java虚拟机的存在。Sun提供了在各种操作系统平台上运行安装的JVM，而该虚拟机都可以执行.class文件。这样也就实现了众所周知的“一次编译，随处运行”的理想。图1.2演示了不同平台上通过JVM执行Java程序的过程，从中可以清楚地理解JVM的作用。

图　1.2　不同平台上通过JVM执行Java程序

注意

图1.2中的“HelloWorld.class”也代表一个应用程序，应用程序本身就是经过打包的.class文件的集合。并且在不同的平台上安装的JVM是不同的，需要到Sun的网站下载适合自己机器操作系统平台的虚拟机。

1.1.2　Java语言平台

Java语言也是一个软件平台。平台可以理解为软件运行所需的软件或硬件环境，如操作系统可以看作是软件平台，它提供了应用程序运行的环境，如在Window XP操作系统上运行Word字处理程序。当然如果从用户的角度看，操作系统其实是软件和硬件相结合的综合平台，因为操作系统对硬件的操作用户是看不见的，但是操作系统又确实管理并运行在硬件平台上。

而Java语言作为软件平台，为Java源程序编写和运行提供了完善的环境。但是该Java提供的软件平台不和硬件发生任何关系，该平台是纯软件平台，运行在操作系统上。Sun为不同的操作系统编写了相应的JVM。作为软件平台的Java由两部分组成：

❑Java虚拟机：Java虚拟机是Java软件平台的基础，基于不同操作系统的平台对应不同的虚拟机。

❑Java应用编程接口（Java API）：API是一套编写好的软件组件的集合，这些API按照功能通过包（package）来提供，包就是一系列具有相似功能的类和接口的集合。图1.3是Java虚拟机和应用编程接口与各种操作系统的软件层次关系。

图　1.3　Java API和虚拟机

说明

在操作系统上加一层Java虚拟机，使得Java实现了平台的独立性，但是这也使得Java程序比C或C++语言编写的程序要慢一点，但是随着编译器的改进以及虚拟机技术的成熟，二者之间的差距会越来越小。

1.1.3　Java语言的功能

Java语言作为完善而强大的软件平台，每个版本的Java平台都会提供如下的功能或组件。

1.开发工具

开发工具提供了诸如编译、执行、调试、文档化等功能，使得开发人员可以使用这些工具来编译调试自己的程序。初学者经常使用的Java工具有Javac源程序编译工具、Java程序执行工具和Javadoc源程序的文档化工具等。具体的功能将在本章后面介绍。读者将会了解如何使用Javac和其他Java工具。

说明

Javac编译工具其实就是在DOS下运行的一个命令，而DOS命令不区分大小写，所以本章有时候在DOS窗口中输入的时候，也直接说是javac命令。

2.应用编程接口（API）

Java API提供了Java语言的核心功能，开发人员可以直接调用这些方法或实现其中的接口来满足自己程序设计的需要，如需要对数据进行排序或搜索，Java提供了具体的类来实现，开发人员只要了解这些类的基本功能和类中的方法就可以调用这些工具。读者可以翻阅Java JDK的HTML文档来搜索自己需要的功能。

3.用户接口工具集

其实这个工具集就是提供如何编写用户接口程序（即GUI程序），为应用程序提供一个友好的用户界面，任何复杂的程序界面都可以通过该工具集中的类或接口实现，具体请参看本书的第22章。

4.集成类库

该类库提供了集成工具来完成复杂的行为，如Java为访问数据库提供了JDBC API；为实现远程方法提供的Java RMI，该方法实现了对网络远端的Java程序的调用，是Java实现分布式计算的重要基础。

1.1.4　Java语言的优点

Java语言与其他语言（如C、C++、Delphi等语言）相比，有自己的优势，无论你学习Java的初衷为何，Java语言确实使得你的程序更友好，并且需要较少的工作。下面将介绍Java语言的优点以证实上述的说法。

1.入门更快

因为Java语言是一门强大的面向对象的语言，所以只要理解面向对象技术（符合人类的处理事情的思维方式），就很容易学习这门语言。如果读者已经学习过C++语言或C语言，将更容易进入Java的世界。

2.代码量少

同C++语言比较，同样的程序使用Java语言编写只需要较少的类和方法。

3.编码更容易

Java语言提供良好的编码规范，使得Java语言编写的程序具有统一的“外表”，并且Java支持垃圾回收机制（GC），所以不会像C++中那样出现内存泄漏的问题，这样开发人员就可以集中精力编写程序所需的类，而不用考虑何时、如何回收这些对象了，只要在需要时创建对象即可，其他都由GC处理。Java的面向对象特性、JavaBeans组件架构和功能强大的API，都使开发人员可以容易地重用经过严格测试的代码，这些API的使用也减少了程序中Bug的数量。

4.程序开发更快

Java语言同C++语言相比更简单，相同的程序使用Java可能比使用C++快些，同时只需要更少的代码行。

5.避免平台依赖

Java语言编写的类库可以在其他平台上的Java应用程序中使用，而不像C++语言必须依赖于Windows平台。

6.程序的平台独立性

Java源程序被编译成字节码，字节码通过运行操作系统平台上的JVM来解释而获得执行，这样使用Java语言编写的程序不依赖于特定的平台，实现一次编写，随处运行。

1.2　Windows下的“HelloWorld”应用程序

到本节为止，笔者还没有介绍如何编写Java程序。当然程序的编写、编译和执行都需要一定的软件环境或编辑工具，所以在写Java程序前需要做些准备工作，最基本的需要两个软件，一个是Java的JDK，一个是文本编辑工具。1.2.1节将介绍这两个软件，在1.2.2节将介绍创建一个Java应用程序的步骤。

1.2.1　创建应用程序的软件环境

JDK即Java开发工具集，就是为开发Java程序提供了一套工具，包括源程序的编译、调试、运行等工具，并提供了一些API接口工具。读者可以到Sun的网站下载JDK并安装使用，这部分内容在本章后面会更详细介绍，这里读者只需要知道开发Java程序需要安装JDK即可。

对于初学者而言，编写源程序的工具最好选择无格式的文本编辑器，Windows自带的记事本就是很好的Java源程序编写工具。可以打开Windows平台的记事本，如图1.4所示。单击“开始”|“运行”命令，在弹出的“运行”对话框内输入“notepad”命令。按Enter键会打开记事本。

图　1.4　打开Windows平台的记事本工具

1.2.2　创建“HelloWorld”应用程序的步骤

【实例1.1】本节通过一个实例介绍开发Java应用程序的步骤，该实例程序为HelloWorld.java，它的功能很简单，就是在标准输出端打印字符串“HelloWorld！”。下面演示创建步骤。

说明

本例只是介绍Java程序创建、编译、运行的整个流程，并不需要读者亲自动手，读者可以在看完第2章的JDK安装和设置后再创建本例练习。

1.创建源程序文件

该文件包含使用Java语言编写的代码，当然这些代码要符合Java规范。可以使用任意的文本编辑器来创建Java源程序文件。实例程序如下所示。

01 /**

02 HelloWorld类的功能是在标准输出端

03 打印一行输出"HelloWorld！"

04 */

05 class HelloWorld{

06 public static void main（String[]args）{

07 System.out.println（"HelloWorld！"）；//调用标准输出打印字符串

08 }

09 }

【代码说明】

❑第1～4行是Java的注释语句，这里实现了多行注释的效果。

❑第5行是类的标识class。

❑Java的主运行方法，所有程序从“main（）”中开始执行。

❑第7行是输出语句。语句后面又使用了“//”表示Java的单行注释。

把在文本编辑器（笔者使用Windows的记事本）中编写的程序另存为“HelloWorld.java”文件，保存源文件为.java文件的过程如图1.5所示。

注意

保存的文件名一定为“HelloWorld.java”，保存类型选择“文本文档（*.txt）”，而编码选择ANSI，单击“保存”按钮，然后退出记事本编辑器。记住保存目录，在编译时需要该目录来指定源文件的位置。

图　1.5　保存源文件为.Java文件

2.编译源程序

首先选择“开始”|“运行”命令，然后在打开的“运行”对话框内输入cmd命令，则打开一个DOS窗口。该窗口的目录提示为当前目录，该目录通常是Windows XP的主目录，如图1.6所示。

为了编译文件，需要改变当前目录到源文件存放的目录下，如源文件在C盘的javacode目录下，则需要在当前DOS提示符下输入cd C：\javacode，再按Enter键，则当前的DOS提示符会变为C：\javacode。但是如果源文件目录不和当前DOS目录在同一个磁盘上，如在D盘，则需要首先输入“D：”，再按Enter键，则DOS提示符会进入D盘，再输入源文件的路径，按Enter键则进入源文件目录。图1.7是切换到源文件目录的状态。

为了检验是否进入了源文件所在目录，在图1.7所示的目录下输入dir指令，发现了刚才保存的“HelloWorld.java”文件。图1.8是查看当前目录下的文件。

现在，可以编译源程序了，在图1.7所示的当前目录下输入命令Javac HelloWorld.java来编译源文件，一旦编译成功，则在当前目录下会生成HelloWorld.class文件，在DOS指示符号下输入dir命令查看会发现有新生成的.class文件。图1.9是查看.class文件的效果。

图　1.6　DOS窗口

图　1.7　切换到源文件目录

图　1.8　查看当前目录下的文件

图　1.9　查看.class文件

javac HelloWorld.java命令执行成功后，则又回到当前的DOS提示符下，此时输入dir命令查看当前目录下的文件，发现了新增加的HelloWorld.class文件，该文件是Java虚拟机可以执行的字节码文件。

说明

调用javac命令的前提是已经安装了JDK并且设置了环境变量，将JDK提供的工具命令告知Windows系统。直到Windows系统知道到哪里寻找用户输入的javac命令。JDK环境变量的设置请参考第2章中的设置，为了节约篇幅这里不再介绍。

3.运行源程序

在编译完程序后需要运行程序。运行程序很简单，在当前目录下输入Java HelloWorld，这里的HelloWorld就是刚才编写的类的名字。执行结果如图1.10所示。

图　1.10　HelloWorld程序执行结果

读者如果看到如图1.10所示的执行结果，说明HelloWorld程序执行成功了。在本例的源程序中，读者或许有很多疑惑，如main（）函数起什么作用，该函数的参数又如何使用，为什么可以直接调用System.out.println（“HelloWorld”）等，这些疑惑将在下一节深入探讨“HelloWorld”应用程序中详细解释。

1.3　深入探讨“HelloWorld”应用程序

本节将详细介绍HelloWorld源程序，在1.2节读者已经看到如何编写、编译和执行Java源程序。但是读者或许想知道源程序到底是如何运行的。为了更好地说明，这里再次列出该代码程序。

01 /**

02 HelloWorld类的功能是在标准输出端

03 打印一行输出"HelloWorld！"

04 */

05 class HelloWorld{

06 public static void main（String[]args）{

07 System.out.println（"HelloWorld！"）；//调用标准输出打印字符串

08 }

09 }

该源程序包括三个部分：程序注释、类定义和main（）方法。通过对这三部分的说明，读者应该理解程序HelloWorld的基本执行过程。至于更细节的内容，后面的章节会继续讲解。

1.3.1　注释源代码

在程序中有这样的注释部分，如以下代码所示。

01 /**

02 HelloWorld类的功能是在标准输出端

03 打印一行输出"HelloWorld！"

04 */

这是Java的注释部分，其格式是“/**注释*/”，程序会忽略掉注释的内容，Java还提供了另外两种注释，其格式分别是“//”和“/*注释*/”，其中“//”表示单行注释，一般用在代码行的后面。

1.3.2　实现类定义

下面代码的粗体字部分是类定义部分，其语法格式是class classname{code}，其中关键字class声明这是一个类，后面紧跟类名HelloWorld，用两个大括号括起来的部分是类定义的主体部分。

01 /**

02 HelloWorld类的功能是在标准输出端

03 打印一行输出"HelloWorld！"

04 */

05 class HelloWorld{

06 public static void main（String[]args）{

07 System.out.println（"HelloWorld！"）；//调用标准输出打印字符串

08 }

09 }

具体的类的介绍可以参考第7章，这里读者只需要理解该应用程序从一个类定义开始即可。

1.3.3　详解main方法

使用Java语言编写的应用程序必须包含一个main（）方法，它的格式如下所示：

public static void main（String[]args）

修饰符public和static的顺序可以互换，但是根据Java惯例把public放在前面，参数名args不是固定的，开发者可以任意命名，但是习惯上选择使用args或argv。

main（）方法是应用程序的入口，一个程序执行时会首先指定类的main（）方法开始，再启动程序所需要的其他资源。main（）方法接收一个字符串数据参数，实际上该参数提供了运行时系统向应用程序提供参数的途径。当然也可以不传递任何参数。如果一个应用程序可以读取一系列文件，则需要把这些文件的绝对路径告诉该程序。该参数是命令行参数，即在调用java ClassName时使用，方式为java ClassName args。

笔者在编译HelloWorld程序时没有输入参数，该应用程序忽视了该参数。但是读者一定要注意确实存在这样的参数，也允许调用这样的参数为应用程序所用。

1.4　常见疑难解答

对于初学者而言，在Java程序的开发、编译和执行过程中，都或多或少会遇到问题，如环境变量设置不正确，源程序的语法错误、语义错误，或在程序编译、运行时发生错误等。如果这些错误或问题不能得到适当的处理，那么会对初学者造成很大的障碍，本节将介绍几种常见的错误，以使读者在学习的过程中少走弯路。

1.4.1　环境变量设置错误

在编译并运行Java应用程序之前，需要设置环境变量，其目的是使Windows系统可以知道DOS窗口中运行的Java工具可执行程序，如Javac源程序编译工具等。图1.11说明Windows操作系统无法发现Javac编译程序。JDK环境变量的设置可以参考第2章的内容。

图　1.11　无法找到Javac可执行程序

当然如果已经安装了JDK，则可以在DOS提示符下输入Javac的绝对路径，通过绝对路径来识别该命令的位置。如果采用JBuilder9自带的jdk，那么目录为C：\JBuilder9\jdk1.5\bin。也可以在源文件的当前路径下输入C：\JBuilder9\jdk1.5\bin\Javac HelloWorld.Java。显然这种方式很烦琐，因为每次编译源文件都要输入一串路径和命令信息。读者可以通过第2.5节掌握如何设置JDK环境变量。

1.4.2　语法错误

Java语言规范设计了语法规则，但是如果疏漏了部分内容，违反了Java的语法规则，编译器会发出语法错误消息，该消息包含错误类型、发生错误的代码在程序中的位置（以该错误代码行为标识）等，并且在错误处标识一个“^”。语法错误如图1.12所示。

图　1.12　语法错误

在HelloWorld.Java源程序中将语句System.out.println（“HelloWorld！”）后的分号去掉，使得程序语句缺少结束标志，这就违反了Java语法规则。在编译期发生任何类型的错误都会导致无法生成.class文件。所以此时需要读者仔细分析错误类型，细致检查程序中的错误，直到源程序顺利通过编译。

1.4.3　语义错误

编译器也会发生语义错误。编译器无法识别一个标识的语义，如发现一个未定义的变量，错误输入了系统类库的类的名字等。图1.13演示了一个语义错误，笔者故意将语句System的首字母改为小写，使编译器无法识别，从而输出语义错误。

注意　Java程序区分大小写。

图　1.13　语义错误

1.4.4　运行时错误

运行时错误指在执行程序过程中发生的错误，如某个类没有实例化而造成空指针，无法找到指定的类文件等。下面介绍三种初学者常见的运行时错误。

1.无法发现类文件

在用户编译或执行Java程序时，由于种种原因可能输入了不正确的类名字，使得编译器或虚拟机无法发现该文件所在的位置。类文件名输入错误如图1.14所示。

图　1.14　类文件名输入错误

在图1.14中，笔者想运行HelloWorld程序，但是输入了错误的类名HelloWorldd，所以执行系统抛出Java.long.NoClassDefFoundError错误。

使用Java工具会启动Java虚拟机，此时系统会首先在当前目录下寻找类文件，如果没有则默认到操作系统的系统环境变量classpath中搜索。所以读者也可以在该变量中设置需要编译的类文件所在的目录。

注意　变量的设置方式可以参考第2.5节设置classpath环境变量。

2.执行Java工具时类名错误

对于初学者而言，很容易出现的一个错误是使用Java执行程序时，往往会输入类文件名如HelloWorld.class，这是不允许的，此时会抛出异常。类名输入错误如图1.15所示。

图　1.15　类名输入错误

注意

在调用Java工具执行程序时，需要在其后输入类名而不是类文件名，如输入Java HelloWorld才是正确的方式。

3.无法发现main（）方法

在第1.3节已经知道，任何Java应用程序执行的类中必须有main（）方法，该方法是程序的入口，通过它才可以继续调用程序所需的各种其他资源。如果在类中没有定义main（）方法或该方法书写错误，都会导致图1.16所示的缺少main（）方法错误。

图　1.16　缺少main（）方法错误

笔者在HelloWorld.Java源程序中修改了main（）方法的名字，继续编译并执行该程序，虚拟机将无法加载该类，因为无法发现类HelloWorld的程序入口。

1.5　小结

相信本章的学习不会耗费初学者多少脑力，但是作为学习一门语言的起步阶段，读者仅仅看到Java语言很小的一部分内容，所以希望读者可以轻松理解本章内容并建立学习Java语言的信心，并保持这样的状态继续下面章节的学习。

本章要求读者初步理解Java语言作为编程语言和作为软件平台的特性。对于1.3节深入探讨“HelloWorld”应用程序要用心体会，掌握一个应用程序的基本结构。因为本章还没有讲解环境配置等关键设置，所以并不建议读者动手学习，只需要了解Java程序从创建到执行的流程、Java程序中一些小的关键代码等即可。

1.6　习题

1.Java源代码文件的扩展名是________。

2.Java类文件的扩展名是________。

3.Java程序的主程序有什么特征？

4.main方法的写法是什么？

5.Java中的编译命令和执行命令分别是什么？

第2章　Java开发工具及环境设置

Java是很出色的面向对象高级语言，面向对象技术将在第8章介绍，这里只需要读者知道这个概念。正如C语言、C++语言、Delphi语言一样，任何高级语言都需要一个运行平台，即编写Java语言的计算机应用程序，需要一个编辑、编译和运行的环境，这里将详细介绍Java高级语言的开发工具及相应的环境设置。读者熟练掌握这些知识后，就可以编写简单的Java程序了。

本章重点：

❑开发工具的下载和安装。

❑JDK的组成和配置。

❑编写并运行简单的Java程序。

2.1　Java开发工具简介

Java语言是一种解释性的语言，即读一句程序执行一句，这样就需要一个解释器完成源程序到机器语言的翻译过程。同时Java是跨平台的语言，跨平台是指Java程序可以在安装任何操作系统的计算机上运行，其前提是需要安装虚拟机（JVM），而虚拟机和解释器都是Java JDK的一部分，JDK是Java开发工具集，它包含一套工具，如刚才讲的源程序解释器、JVM，还包括编译工具（Javac.exe）、Java执行程序（Java.exe）等。如果想让运行程序的计算机知道这些工具的位置，并找到这些工具，就需要下载、安装并配置JDK。

2.2　如何下载并安装JDK

Sun公司提供免费的JDK工具，这些工具包括在各种平台上运行的版本，如在Windows平台、在Linux平台、在Macintosh平台等，用户可以根据自己的选择来确定下载哪类工具。编者下载的是运行于Windows平台的版本（j2sdk_5_0.exe）。下载地址为www.sun.com，下载适合自己平台的JDK。

下载完成后是可执行程序，如编者使用的是j2sdk_5_0.exe，双击安装程序，只需要不断单击安装界面中的“Next”按钮，就可以顺利完成安装。这里需要注意安装目录，因为在环境设置中要清楚地知道该JDK的安装目录。安装目录的选择如图2.1所示。

说明

JDK默认的安装目录是C盘的Program Files目录，如果变更安装目录可以单击图2.1中的“Change”按钮。一般情况下，只要一直单击“Next”按钮执行默认安装即可。

图　2.1　选择合适的安装目录

2.3　JDK内置工具

Java程序需要运行环境的支持，同时编译、解释Java程序、执行Java应用程序和Java小程序也需要必要的工具，本节重点讲解JDK中包含的5个常用工具，即Javac.exe、Java.exe、appletviewer.exe、Javadoc.exe和Javap.exe。在Sun的JDK中还集成了Java虚拟机（JVM），JVM提供了Java程序的运行环境，它负责解释.class文件（Java源程序经过编译后的文件），并提交给机器执行。

注意　这里对JVM不再做过多的说明，读者只要知道它的作用就足够了。

2.3.1　JDK常用工具

JDK是一个开发工具集合，作为实用程序，工具库有5种主要程序。

（1）Javac：Java编译器，将Java源代码转换为字节码（生成和源文件名同名的.class文件）。

（2）Java：Java解释器，执行Java源程序的字节码。

（3）appletviewer：小应用程序浏览器，执行嵌入HTML文件中的Java小程序的Java浏览器。

（4）Javadoc：依据Java源程序和说明语句生成各种HTML文档。

（5）Javap：Java反汇编器，显示编译类文件中可访问的功能和数据，显示字节码的含义。

2.3.2　JDK常用工具实例

2.3.1　节已经详细介绍了各种实用工具的含义，这里就依次介绍如何使用这些工具给出一个具体实例。

Javac和Java工具在第2.6节通过一个Java应用程序详细说明了其用法。这里介绍2.3.1节所列的其他工具的使用，借用2.7节中的Java应用程序JavaApplet.Java。图2.2说明如何使用appletviewer。

图　2.2　使用appletviewer工具

说明

这里仅仅说明如何使用该工具，即该工具的语法规则，至于Java Applet如何使用，可以参考2.7节的例子。

图2.3说明如何使用Javadoc。

说明

这里借用了2.7节中的JavaApplet.Java源程序，在图2.3中可以清楚地看到Javadoc工具的执行过程，首先是加载源文件JavaApplet.Java，创建相关Javadoc信息，然后产生各种html文件，这些文件保存在执行Javadoc命令的当前目录下。至于文件内容，读者只要自己实践一次，打开观察一下，就很清楚了，这里不再作过多介绍。

图2.4为在D盘根目录下通过各种Javadoc生成的html文件。图2.5说明如何使用Javap。

图　2.3　使用Javadoc

图　2.4　Javadoc生成的各种html文件

图　2.5　使用Javap

说明

这里使用Javap反编译MyFirstJavaProgram.class（源程序可参见2.6节的代码）文件，反编译的结果是该类提供的可访问的方法和属性，这里可访问是指具有public访问权限的方法或静态属性，如在MyFirstJavaProgram.Java源文件中，有一个public访问权限的String testString和具有public访问权限的main（）方法，同时Java默认的构造函数也具有public访问权限，所以这些都可以通过反编译工具Javap体现出来。

本节详细介绍了JDK的几种常用工具，读者一定用心体会，自己操作一遍，就可以很快掌握这些工具的用法。尤其是Javac.exe和Java.exe，它们是最常用的两种工具，在2.6节有具体的操作实例，读者可以参考。随着学习的深入，还会附带介绍其他工具（如jar打包工具等），对于初学者首先掌握本节介绍的基本工具是最重要的，这样不会耽误下面的学习内容，同时为深入学习JDK的其他工具打下良好的基础。

2.3.3　应用程序发布工具（jar）

jar文件被打包成ZIP文件格式，所以可以使用jar文件实现压缩和解压缩数据。jar工具可以实现应用程序的发布，把应用程序所需要的资源（如类、视频、音频、图片等）打包成.jar文件，该文件具有跨平台特性，可以在任何运行虚拟机的操作系统平台上执行。

jar工具是JDK的一部分，使用该工具实现jar文件的基本功能。jar命令将启动打包工具软件，根据自己的需要调用不同的参数实现打包资源文件。表2.1为jar工具命令格式和操作命令的功能。

2.4　jar文件的创建与查看

本节将详细介绍如何实现最常用的打包方法。包括创建.jar文件、查看jar文件的内容、提取jar文件的内容、更新jar文件和运行打包的jar软件。通过本章的学习，了解Java中jar工具的真正意义与功能。

说明

如果读者采用完全自学，可以先忽略本节的内容，直接跳到第2.5节学习，等用到jar的时候再回来学习。

2.4.1　创建jar文件

创建.jar文件的基本命令格式为：

jar cf jar-file input-file（s）

其中每个选项或参数的含义如下：

❑jar：启动JDK的jar工具。

❑c：该选项表示此时调用jar工具的目的是创建一个jar文件。

❑jar-file：该选项表示打包的结果文件名，该文件生成后有.jar扩展名，该文件的名称没有要求，只要便于理解记忆即可。可以在创建jar文件时就在jar-file后添加.jar扩展名，默认不用添加扩展名。

❑input-file（s）：该参数表示添加到jar文件中的文件。该参数可以包含通配符*，也可以包含目录（一个或多个），如果是目录则把目录下的所有文件依次打包进jar文件。

命令jar cf会在当前目录下生成一个经过压缩的jar文件，并且产生一个默认的manifest文件，该文件会列出jar文件中的所有文件。

还有一些选项可以添加到cf选项之间，这些选项及其含义如表2.2所示。

在了解了如何创建基本的jar文件和各种选项之后，下面通过一个创建jar文件的例子，来更具体地学习jar工具的具体实现方式和各种参数的含义。

【实例2.1】笔者设计了一个类MyJarTest，该类需要音频资源和图片资源，把该文件打包成一个jar文件，这样就可在任何平台上运行该程序。该程序和资源放在目录MyJarTest目录下，文件结构如图2.6所示。

说明

因为此时读者还没有学习类的知识，这里只是给出一个大概的过程让读者了解打包jar文件的流程，不需要读者亲自动手。如果读者不感兴趣，可以直接跳过2.4节。

图　2.6　MyJarTest目录结构

其中包含两个子目录myaudio和myimages，这两个子目录中包含程序所需要的音频和图像信息。下面需要把上述文件打包成名为MyJarTest.jar的文件，首先在DOS窗口中转到MyJarTest目录下，在当前目录下输入打包的命令格式如下：

Jar cvf MyJarTest.jar MyJarTest.class myaudio myimages

说明

因为myaudio和myimages是目录，所以Jar工具会迭代地访问这些文件夹并把文件夹中的文件添加到MyJarTest.jar文件中。因为使用了cvf选项，所以在打包的过程中会显示添加文件的内容。最后生成的MyJarTest.jar文件存储在当前目录下。图2.7是创建压缩的jar文件。

图　2.7　创建压缩的jar文件

从图2.7中可以看出创建该jar文件中的文件都经过了压缩处理，并显示了压缩比例，如果不需要压缩处理而创建不经过压缩的jar文件，可以选择0参数。

Java的jar工具提供了非压缩的打包参数，是因为考虑到实际应用中的效率。如当用户下载Applet肯定不希望过长的传输时间，如果该Applet采用压缩过的jar文件，使用户在下载之前先解压压缩文件，再经过网络传输到客户端，这样就比较耗时，而采用不经过压缩Applet的jar文件就减少了解压缩的时间。创建非压缩的jar文件的命令格式如下所示。

Jar cvf0 MyJarTest.jar MyJarTest.class myaudio myimages

输出结果如图2.8所示，这是创建未压缩的jar文件。

图　2.8　创建未压缩的jar文件

刚才讲过Jar工具也接受使用*通配符的参数，如果在当前目录下的所有文件都需要打包就可以使用通配符，它表示在当前目录下的所有文件都需要打包成jar文件。可以采用如下的命令方式创建jar文件。

Jar cvf MyJarTest.jar*

该命令的执行结果与图2.8相似，只是向MyJarTest.jar中添加的文件数量不同。

上述例子中，在jar文件中附带了打包文件自己的路径名和目录结构。在查看jar文件时会清楚地看到这一点，如图2.9所示。

说明

这里使用了查看jar文件内容的命令jar tf jar-file.jar，读者不必关注其具体含义，关键是观察文件的内容，注意文件的目录结构。

Jar工具提供了一个-C选项，使创建的jar文件不保存被打包文件的相关路径名。

下面举例说明，假设用户想把音频文件和图像文件创建为一个jar文件，并且该jar文件中的所有文件没有目录结构，即所有文件在最高层的同一目录下。可以使用如下命令完成上述任务：

Jar cf ImageAudio.jar-C myimages.-C myaudio.

参数-C myimages告诉Jar工具进入myimages路径，随后告诉Jar工具将该目录下的所有文件打包。参数-C myaudio告诉Jar工具进入myaudio路径，随后告诉Jar工具将该目录下的所有文件打包。在参数-C、myimages和.之间必须有空格。为了显示压缩过程，这里使用cvf参数。执行结果如图2.10所示。

图　2.9　查看MyJarTest.jar文件内容

图　2.10　使用-C参数压缩并打包文件

在ImageAudio.jar创建成功后，该jar文件的内容列表如图2.11所示。此时调用了查看jar文件的命令jar tf jar-file.jar。输出如图2.11所示。

注意

图2.11中使用了查看jar文件内容的命令jar tf jar-file.jar，该命令显示jar文件的内容，注意这里没有文件的目录结构，所有文件在一个目录中。

如果没有采用-C选项将音频文件和图像文件打包成jar文件，则该jar文件的内容为具有目录结构的文件列表。使用命令jar cf ImageAudio.jar myimages myaudio来打包文件并使用jar tf ImageAudio.jar查看文件内容。文件内容列表如图2.12所示。

图　2.11　查看ImageAudio.jar内容

图　2.12　查看带目录结构的ImageAudio.jar

2.4.2　查看jar文件的内容

查看文件的基本操作命令如下所示。

jar tf jar-file.jar

下面分析一下该命令的各参数或选项的含义。

❑t选项说明查看jar文件的内容列表。

❑f选项指明需要查看的jar文件必须在命令行中说明。

❑jar-file参数是需要查看的jar文件的路径和名字。

说明

选项t和f的顺序可以互换，但是在二者之间不能有空格。上述命令的作用是jar文件的内容列表显示在标准输出装置。也可以在tf之间增加v选项来产生关于文件长度、修改时间等的额外信息，并显示在标准输出装置上。

【实例2.2】下面通过具体的例子说明如何查看jar文件的内容，以及显示结果。使用Jar工具查看ImageAudio.jar文件的内容，注意这里的ImageAudio.jar文件是带目录结构的。命令格式如下：

jar tf ImageAudio.jar

该命令将ImageAudio.jar中的文件内容显示在标准输出中。图2.13显示查看ImageAudio.jar文件的内容。

说明

从输出结果看，该ImageAudio.jar文件包含myimages和myaudio目录下的文件，并且从输出结果前两行可以看出该jar文件包含了默认的manifest文件，META-INF/MANIFEST.MF，该文件有JAR工具在创建jar文件时自动产生。

图2.13中所有的路径名都是以“pathname/”形式出现，该路径永远是以相对路径的方式出现，无论操作系统平台是Windows、Linux或者Mac，这里的路径名肯定不会出现绝对路径。

采用选项v会显示jar文件中所有文件的最后修改时间，命令格式如下所示。

jar tvf ImageAudio.jar

命令的输出结果如图2.14所示，通过选项cvf查看ImageAudio.jar内容。

图　2.13　查看ImageAudio.jar文件内容

图　2.14　通过选项cvf查ImageAudio.jar内容

2.4.3　提取jar文件的内容

提取文件jar文件内容的命令格式如下所示。

jar xf jar-file[archived-file（s）]

下面分析该命令的参数或选项含义。

❑x选项说明提取jar文件的内容。

❑f选项指明需要提取的jar文件必须在命令行中说明。

❑jar-file参数是需要提取的jar文件的路径和名字。

❑archived-file（s）是可选参数，包含从jar-file中提取的文件的列表，如果没有该可选项，则默认是提取jar-files中的所有文件。

说明

选项x和f的顺序可以互换，但是在二者之间不能有空格。当执行提取文件操作时，Jar工具复制想要提取的文件，并把复制的文件存储在当前目录下，重新建立目录结构。而原始的jar文件内容不发生变化。

【实例2.3】下面通过具体的例子说明如何提取jar文件的相关子文件。命令格式如下：

jar xf ImageAudio.jar

在图2.14中已经看到了ImageAudio.jar文件中的内容，为了方便分析，这里再给出该文件的内容。

mETA-INF/

mETA-INF/MANIFEST.MF

myimages；

myimages/T4.gif

myimages/T10.gif

myimages/T2.gif

myimages/T3.gif

myimages/T2.gif

myaudio/

myaudio/mid_warn.wav

myaudio/mid_info.wav

myaudio/mid_err.wav

假设想从ImageAudio.jar文件中提取T2.gif和mid_warn.wav两个文件，可以采用如下命令格式：

Jar xf ImgeAudio.jar myimages/T2.gif myaudio/mid_warn.wav

该命令做两件事：

❑复制目录myimages下的T2.gif和myaudio目录下的mid_warn.wav文件。

❑如果文件夹myimages或myaudio不存在则创建新文件夹，并把文件T2.gif放入myimages文件夹，把mid_warn.wav放入myaudio文件夹。

说明

因为ImageAudio.jar文件是带目录结构的，所以在提取文件时必须告诉Jar工具文件的相对位置，否则无法提取想要的文件。无论如何，原始的ImageAudio.jar不会发生任何变化。

读者可以以相同的方式提取任何类型的文件，当然前提是该文件在jar文件中存在，所以要求读者在提取文件前先使用命令jar tf jar-file来查看文件内容。

而如果没有指定要提取的文件名称，则默认是提取所有的jar文件中的内容，并重建文件目录。读者可以通过如下命令体验该功能：

Jar xf ImageAudio.jar

2.4.4　更新jar文件

Jar工具提供了u参数向已经存在的jar文件中增加新的文件，其基本命令格式如下所示。

jar uf jar-file.jar input_file（s）

下面分析该命令的参数或选项的含义。

❑u选项说明需要更新jar文件的内容列表。

❑f选项指明需要更新的jar文件必须在命令行中说明。

❑jar-file参数是需要更新的jar文件。

❑input_file（s）表示需要增加到jar文件中的文件列表。

说明　如果增加的文件在jar文件中已经存在，则新文件会覆盖掉原来的文件。

【实例2.4】下面举例说明如何向已经存在的jar文件中增加新文件。请读者再回忆一下ImageAudio.jar的内容：

mETA-INF/

mETA-INF/MANIFEST.MF

myimages；

myimages/T4.gif

myimages/T10.gif

myimages/T2.gif

myimages/T3.gif

myimages/T2.gif

myaudio/

myaudio/mid_warn.wav

myaudio/mid_info.wav

myaudio/mid_err.wav

假设想要向该jar文件中增加一个mid_dd.wav文件，首先在mid_dd.wav文件所在目录下执行如下命令：

Jar uf ImageAudio.jar mid_confirm.wav

再次查看ImageAudio.jar的文件内容则如下所示。

META-INF/

META-INF/MANIFEST.MF

myimages/

myimages/T4.gif

myimages/T10.gif

myimages/T2.gif

myimages/T3.gif

myimages/T2.gif

myaudio/

myaudio/mid_warn.wav

myaudio/mid_info.wav

myaudio/mid_err.wav

mid_confirm.wav

注意

在更新后的jar文件中增加了一个文件，该文件在文件列表的最后一行显示。文件mid_confirm.wav放在ImageAudio.jar文件目录结构的根目录下。

如果想添加的文件在文件夹newaudio中，也可以在该文件夹的直接上级目录中使用-C选项，指定目录，然后进入目录寻找想添加的文件。如下命令所示。

jar uf ImageAudio.jar-C newaudio mid_confirm.wav

该命令的作用是向ImageAudio.jar文件中添加一个音频文件，Jar工具需要在当前目录的newaudio目录下继续搜索名为mid_confirm.wav的文件。

以上介绍了Jar工具的各种使用方式，这里对Jar工具参数含义做个总结：

jar{ctxu}[vfm0Mi][jar-文件][manifest-文件][-C目录]文件名……

选项含义：

❑-c创建新的存档。

❑-t列出存档内容的列表。

❑-x展开存档中的命名的（或所有的）文件。

❑-u更新已存在的存档。

❑-v生成详细输出到标准输出上。

❑-f指定存档文件名。

❑-m包含来自标明文件的标明信息。

❑-o只存储方式，未用ZIP压缩格式。

❑-M不产生所有项的清单（manifest）文件。

❑-i为指定的jar文件产生索引信息。

❑-C改变到指定的目录。

说明

如果一个文件名是一个目录，它将被递归处理。清单（manifest）文件名和存档文件名都需要被指定，按“m”和“f”标志指定的顺序相同。

2.4.5　运行打包的jar软件

至此，读者已经学习过了如何创建jar文件，提取jar文件的内容，向jar文件中添加文件，以及查看jar文件的内容。本节将介绍如何运行打包的应用程序，以及jar文件在Applet中的应用。考虑以下两种情形：

❑jar文件包含Applet，该Applet在浏览器中运行。

❑jar文件包含应用程序，该应用程序通过命令行启动。

下面依次介绍这两种jar文件的应用方式。

1.jar文件包含Applet

为了激活Applet，使该Applet可以在浏览器中运行，使用Applet标签来表示在HTML中的Applet程序。如果该Applet与jar文件相关联，则需要使用Archive参数说明jar文件的相对路径。一个Applet的例子如下所示。

＜applet code=MyJarTest.class

Width=100 height=100＞

＜/applet＞

如果需要的程序被打包成名为MyJarTest.jar的jar文件，则可以通过调用Applet标签Archive的方式说明调用了一个jar文件，并说明该jar文件的相对目录。

＜applet code=MyJarTest.class

Archive="MyJarTest.jar"

Width=100 height=100＞

＜/applet＞

参数Archive表示包含类MyJarTest.class的jar文件的相对路径，本例中该jar文件和html文件在相同的目录中，如果在读者的程序中二者的路径不同，则需要修改jar文件的相对路径。如果该jar文件在html文件所在目录的下级目录myjar目录中，则该Applet应该做如下修改。

applet code=MyJarTest.class

Archive="myjar/MyJarTest.jar"

Width=100 height=100＞

＜/applet＞

2.jar文件包含应用程序

首先介绍如何打包应用程序。在前面介绍了如何将各种文件打包然后统一发布，但是对于应用程序来讲，这有些区别。在运行打包的jar应用程序前必须确保运行环境，知道该jar文件中的哪个类是程序的入口。

所以，为了说明哪个类是应用程序的入口，必须在该jar文件的manifest文件中增加一个Main-Class头信息。其格式如下所示。

Main-Class：MainClassName

其中MainClassName是作为应用程序入口的类名。下面通过一个具体的例子说明如何打包可运行的jar应用程序。

例如笔者把MyJarTest类作为应用程序的入口，首先需要创建一个名为Manifest.txt的文本文件，在文件内输入如下内容：

Main-Class：MyJarTest

注意

在Manifest.txt文本文件内的头信息必须以回车或换行结束，否则无法解析，在完成了上述文件的设置后，执行如下命令。

jar cfm MyJar.jar Manifest.txt MyJarTest.class myaudio myimages

此时完成了应用程序的打包，该应用程序打包后的jar文件名字是MyJar.jar，并且告知了运行环境应用程序执行的入口。接下来执行如下命令执行该应用程序。

Java-jar MyJar.jar

2.5　JDK环境设置

在2.3节学习了JDK的常用工具，其中在bin目录下的工具最为有用，在计算机上编译和执行Java程序时，需要知道编译和执行程序的工具，这样就需要设置环境变量。这里以jdk1.5.0为例，假设安装目录为C：\Program Files\Java\jdk1.5.0，操作系统为Windows XP，进行详细的环境配置的步骤说明。

2.5.1　设置Path环境变量

单击“开始”|“设置”|“控制面板”|“系统”|“高级”|“环境变量”命令，打开环境变量对话框，在“系统变量”列表框中设置环境变量，这样可以保证以任何身份登录的用户都能使用，单击Path行，如图2.15所示。在图2.15中单击“编辑”按钮，弹出如图2.16所示的对话框。

在图2.16的“变量值”文本框最后，增加JDK下的bin和lib目录：“C：\Program Files\Java\jdk1.5.0\lib；C：\Program Files\Java\jdk1.5.0\bin”。这样，在Windows的任何目录下都可以运行JDK中bin下的工具了。如果用户把Java源程序放在其他任何目录下，只要在该目录下运行Java程序的编译和执行工具，都可以成功运行。图2.17显示在DOS窗口中调用Javac编译工具（注意当前目录并不是该命令所在的目录），如果该命令执行成功，则说明配置正确。

图　2.15　编辑Path

注意“变量值”文本框中，新增加的目录与原先的目录之间也需要一个分号间隔。

图　2.16　设置环境变量

图　2.17　在DOS窗口中调用Javac编译工具

2.5.2　设置classpath环境变量

classpath环境变量的作用是告诉虚拟机，如果在执行Java类文件的当前目录下无法找到该类文件，则默认到classpath设置的路径中依次搜索该类文件。classpath环境变量的设置过程如下：

单击“开始”|“设置”|“控制面板”|“系统”|“高级”|“环境变量”命令，打开环境变量对话框，在“系统变量”列表框中设置环境变量，这样可以保证以任何身份登录的用户都能使用，单击classpath行，如图2.18所示。在图2.18中单击“编辑”按钮，弹出如图2.19所示的对话框。

说明　如果列表框中没有classpath行，则需要新建一个。

图　2.18　设置环境变量

图　2.19　设置classpath环境变量

在图2.19的“变量值”文本框后增加“C：\Program Files\Java\jdk1.5.0\lib\dt.jar和C：\Program Files\Java\jdk1.5.0\lib\tools.jar；”两目录，这些目录下提供了一些类工具，这样在采用集成开发环境编译程序时，一旦需要这些工具系统可以自动找到这些工具，而不需要用户的干预。对于读者自己编写的类文件目录也可以放在该变量中，这样如果用户不是在源文件的当前目录下执行程序，系统会自动到该变量所指向的目录中依次寻找类文件。

2.6　一个简单的Java应用程序

【实例2.5】现在，完成了JDK的安装和相应的环境设置，下面用Windows下的记事本编辑一个Java应用程序，并执行该程序，读者可以通过该程序的编译和执行对Java程序的执行有个直观的认识。

（1）编写源程序：打开记事本，编写如下的Java程序，并保存在D盘根目录下，即D：\MyFirstJava Program.java。以下是一个Java应用程序，只输出一句“Hello Java！”。

01 //定义一个类

02 public class MyFirstJavaProgram{

03 04 //程序执行的入口，每个Java应用程序都有一个main（）函数

05 public static void main（String args[]）{

06 //在DOS窗口打印一行字符串：Hello Java！

07 System.out.println（"Hello Java！"）；

08 }

09 }

【代码说明】第5行的程序很关键，是Java程序中应用程序的入口函数main（）。第7行是输出的关键，通过println（）中的参数内容输出用户看到的结果。

说明

在初学者编写源代码时，最好选择无格式的文本编辑器，如Windows下的记事本，存储源程序时其后缀名必须是.Java。当然还有诸如UltraEdit等很好的编辑工具，读者可以根据自己喜好或习惯选择，它们使用起来都很方便。

（2）编译源程序：单击“开始”|“运行”命令在运行对话框中输入“cmd”指令，如图2.20所示。单击图2.20中的“确定”按钮，打开DOS对话框，进入D盘根目录，在当前目录下输入“javac MyFirstJavaProgram.java”，编译有两个作用，一是检查程序的语法错误，二是导入源程序中需要的类库，编译的结果是.class文件，该文件可以被JVM直接运行。如果编译正确，则显示结果如图2.21所示。

图　2.20　运行命令对话框

注意

在读者开始使用记事本编辑Java源程序时，可能会出现一些输入法错误，造成程序无法编译，如字符串的双引号使用了中文输入法，则无法通过编译，会提示非法字符。

【运行效果】执行程序：在DOS窗口中输入“java MyFirstJavaProgram”，则程序执行结果如图2.22所示。

图　2.21　编译Java源程序

图　2.22　执行Java程序的结果

注意

在使用Java.exe工具执行编译过的Java程序时，需要执行的文件名就是源文件名，但不需要文件后缀（如.class）。执行MyFirstJavaProgram时，只需要输入java MyFirstJavaProgram（而不是java MyFirstJavaProgram.class）。

2.7　一个简单的Java Applet应用程序

【实例2.6】还有一种小应用程序，即Applet，其实它就是一段Java代码，但这段代码可以以适当的方式嵌入到HTML页面。这里举一个简单的例子，让读者对Java语言在网络上的应用有直观和初步的认识。

2.7.1　编写一个Applet

首先使用打开记事本，编写代码如下所示。将文件保存在D盘根目录下，文件名设置为JavaApplet.java。这是一个简单的Applet应用程序，只输出一句话“This is my first Java Applet！”。

01 //2、3行导入实现Applet需要的类库

02 import java.applet.Applet；

03 import java.awt.*；

04 //通过继承方式定义并实现一个Applet类JavaApplet

05 public class JavaApplet extends Applet{

06 public void paint（Graphics g）{

07 //调用Graphics对象g的drawString方法，在html页面的指定位置打印一行字符串

08 g.drawString（"This is my first Java Applet！"，20，10）；

09 }

10 }

【代码说明】第2～3行导入实现Applet需要的类库，通过import关键字执行导入。第5行创建一个类JavaApplet，第8行输出用户能看到的结果。

2.7.2　编写一个用来调用Applet Java程序的HTML文件

打开一个记事本，编写如下代码，同样保存在D盘根目录下，文件名为Applet.html，这是一个HTML页面文件。

01＜html＞

02＜title＞My Applet Test＜/title＞

03＜h1＞My Applet Test＜/h1＞

04＜applet

05 //JavaApplet.class就是JavaApplet.Java经过编译后的类，该类在这里得到调用

06 code="JavaApplet.class"

07 width=250

08 height=250

09 name="MyApplet"＞

10＜/applet＞

11＜/html＞

【代码说明】第1行和第11行的＜html＞＜/html＞是HTML文件的标识。第4～10行在HTML文件中插入了一个＜applet＞标签，注意第6行的code属性为JavaApplet.class，是编译后的JavaApplet.java。

2.7.3　Applet的运行过程

编译JavaApplet.java，在DOS窗口中该文件所在的目录下输入“javac JavaApplet.java”，如果编译成功，效果会如图2.23所示。在同样的目录下输入“appletviewer applet.html”，效果如图2.24所示。按Enter键后显示Java Applet的运行结果，如图2.25所示。

图　2.23　编译JavaApplet.java程序的结果

图　2.24　执行applet.html

图　2.25　Java Applet的运行结果

说明　只要读者认真体会上述过程，再实际运行一下，就会很快理解并掌握本节的基本内容。

2.8　注释及内嵌式文档

良好的注释可以增强程序的可读性和可维护性，读者应该养成注释的习惯。Java支持三种注释方式。（1）双斜线“//”在程序中表示注释，注释的语句不会编译，即编译器会略过该行。

如前面的代码中出现过如下的注释：

//在DOS窗口打印一行字符串：Hello Java！

System.out.println（"Hello Java！"）；

（2）另一种注释方式是实现多行注释，使用/*多行注释内容*/这种方式。以下就是一段多行注释。

01 /*定义一个类

02该类是实例程序，程序提供了一个入口，执行结果是在DOS窗口打印一行字符串

03"Hello Java！"

04 */

05 public class MyFirstJavaProgram{

06 }

07 }

说明

对于使用记事本这样的编辑工具而言，使用上述提到的两种注释已经足够了，当然在一些集成式编译环境中，如JBuilder等，还提供内嵌式注释，即只要按照其规范输入注释内容，会自动形成整个程序的类或函数的注释说明，并且会以HTML页面的形式提供。

（3）内嵌式文档注释。该类注释多用在集成开发环境下，如JBuilder、Eclipse等，其方式如下所示。

/**

注释内容

*/

该注释可以出现在类和接口的声明前、各种方法（函数）的定义前。在集成式开发环境中，只要输入/**，再按Enter键，就会自动形成注释的样式，代码如下所示。

01 /**

02 *＜p＞Title：

03 *＜p＞Copyright：Copyright（c）2007

04 *＜p＞Company：

05 *@author not attributable

06 *@version 2.0

07 */

08 public class ReadAndAnalyzeDataThread extends Thread{

09 }

2.9　常见疑难解答

2.9.1　JDK和J2SDK的区别

JDK即Java Development Kit，简称Java开发包。

J2SDK即Java 2 Software Develop Kit，简称Java2软件开发包。

J2SDk包含了Java Development kit（JDK）、Java Runtime Environment（JRE）和Java Plug-in。原先Sun公司的Java软件开发工具包是JDK，现在称为J2SDk了。

2.9.2　Java文件扩展名是否区分大小写

如果用记事本写了一个很短的程序，如下所示：

01 //定义一个类

02 public class FirstJava{

03 //程序执行的入口，每个Java应用程序都有一个main（）函数

04 public static void main（String args[]）{

05 //在DOS窗口打印一行字符串：Hello Java！

06 System.out.println（"Hello！"）；

07 }

08 }

将这个文件保存在D盘根目录下，名字为Hello.Java，那么在DOS中的D盘根目录下执行javac命令，根本就不会编译这个文件。将扩展名改为小写的Hello.java，则再执行javac命令，就可以编译通过了。所以说Java文件的扩展名区分大小写。

2.10　小结

在学习本章时，读者一定要注意带有“说明”和“注意”字样的段落，往往忽视这些内容的细节会造成程序编译、运行的异常，会给初学者带来理解上的困惑。在使用Jar工具时，注意各种参数的含义，在熟悉选项或参数的作用后再使用jar工具的各种功能。

2.11　习题

1.Java文件的扩展名是否区分大小写？

A.区分

B.不区分

2.编译文件的Javac命令是否区分大小写？

A.区分

B.不区分

3.JDK环境配置必须设置哪些环境变量？

4.Applet应用程序的作用是什么？

第3章　Java语言中的数据与运算符

本章主要介绍编程语言最基础的部分：数据和运算符。这是所有编程语言都必须掌握的基础知识，也是整个程序代码不可缺少的重要部分。本章将通过大量的程序代码，来讲述如何操作这些数据和运算符。熟练地掌握此章，对于Java开发有着非常重要的作用，并且还对以后学习其他开发语言有着重要的帮助。

本章重点：

❑Java语言中的数制。

❑数据类型。

❑变量和常量。

❑各种常见运算符。

3.1　数制

在介绍数据之前，先了解数制的概念。数制可以说是纯粹数学上的内容，在计算机语言开发中使用得比较频繁。下面将详细讲述数制的有关知识。

3.1.1　基本概念

在使用电脑时，会遇到数值、文字、图像、声音等信息，电脑是如何识别这些数据信息的呢？

首先，这取决于计算机底层硬件是如何识别数据的。计算机底层硬件只能识别“0”和“1”，这种只有“0”和“1”两个数字符号的组合被称为二进制。例如计算机要处理数字“128”，那么计算机会将其转化成二进制“10000000”。一个这么简单的数字，要用这么长的数字符号来代替，在现实生活中稍显麻烦，所以后来又引进了十六进制和八进制。实际开发中使用最多的是十进制，后面会介绍各个数制的特征和使用。

3.1.2　Java语言中的数制表现形式

数制一般包括二进制、八进制、十六进制和十进制。

1.二进制

二进制的特征：

❑由两个数字组成：“0”和“1”。

❑运算时逢二进一。

例如：1100110011、10000001。

2.八进制

八进制的特征：

❑由8个数字组成：“0”、“1”、“2”、“3”、“4”、“5”、“6”、“7”。

❑运算时逢八进一。

例如：014、0726。

注意

八进制的数据有一个0的前缀。它经常会与二进制产生混淆，所以建议在Java程序设计中，尽量不要使用八进制。

3.十六进制

十六进制的特征：

❑由16个数字组成：“0”、“1”、“2”、“3”、“4”、“5”、“6”、“7”、“8”、“9”、“A”、“B”、“C”、“D”、“E”、“F”。

❑运算时逢十六进一。

例如：0xB、0x12e。

注意

十六进制用A、B、C、D、E、F这6个字母分别表示10～15。字母不区分大小写。十六进制的数据有一个0x的前缀。

4.十进制

十进制的特征：

❑由10个数字组成：“0”、“1”、“2”、“3”、“4”、“5”、“6”、“7”、“8”、“9”。

❑运算时逢十进一。

例如：89、92、168。

3.2　数据类型

Java语言是一个强调数据类型的语言，在声明任何变量时，必须将该变量定义为一种数据类型。Java中的数据类型包括基本数据类型和对象类型。对象类型不属于本章所讲述的内容，本章主要介绍数据的基本类型。Java程序中，总共有8大基本类型，其中4种整型、1种字符型、2种浮点型、1种布尔型，除了这几种基本类型外，其他都属于对象类型的数据。

3.2.1　整型

什么是整型呢？从字面上就可以知道，整型就是整数类型，也就是没有小数点的数字，可以是正数也可以是负数。在Java中，整型主要有4种：字节型（byte）、整数型（int）、短整型（short）、长整型（long）。

1.字节型

【实例3.1】byte用一个字节来表示整数值，它的范围介于-128～127之间。通常这种类型的整型数据拥有上节中提到的所有进制。但无论采用哪种进制，在输出控制台上，系统都会将其自动转化为十进制，从下列代码段可以得到证实。

01 //x是十进制数

02 //y是八进制数

03 //z是十六进制数

04 public class Byte

05 {

06 public static void main（String[]args）

07 {

08 byte x=22；

09 byte y=022；

10 byte z=0x22；

11 System.out.println（"转化成十进制，x="+x）；

12 System.out.println（"转化成十进制，y="+y）；

13 System.out.println（"转化成十进制，z="+z）；

14 }

15 }

【代码说明】第8～10行定义了3个变量，分别代表不同的进制，第9行的变量值前缀是0，第10行的变量值前缀是0x。第11～13行并没有使用类型转换的函数，而是直接输出这3个变量。

【运行效果】在DOS窗口中，通过Javac编译该源代码，然后通过Java执行编译后的class文件。最终结果如下所示。

转化成十进制，x=22

转化成十进制，y=18

转化成十进制，z=34

运行程序的流程前面两章已经多次说明，这里只简要说明，后面不再重述。

2.短整型

【实例3.2】short用两个字节表示整数值，其整数值介于-32768～32767之间。它有八进制、十进制、十六进制3种表示方法，其表示方法与字节型是一样的，从下面的程序段可以证实。

01 public class Short

02 {

03 public static void main（String[]args）

04 {

05 short x=22；//十进制

06 short y=022；//八进制

07 short z=0x22；//十六进制

08 System.out.println（"转化成十进制，x="+x）；

09 System.out.println（"转化成十进制，y="+y）；

10 System.out.println（"转化成十进制，z="+z）；

11 }

12 }

【代码说明】第5～7行定义了3个short型变量，依然是用前缀0和0x代表八进制和十六进制。第8～10行没有使用数据类型转换的函数，直接输出结果。

【运行效果】

转化成十进制，x=22

转化成十进制，y=18

转化成十进制，z=34

可以看出，两个程序段运行结果都是一样的。其实，在实际编程过程中，开发者最常用的整型是int型。

3.int型

【实例3.3】int型又被称作整数型，用4个字节来表示整数值，其整数值介于-2147483648～2147483647之间。int型拥有以上所说的各种进制，其表示方法与字节型也相同，从下面的程序段同样可以证实。

01 public class Int

02 {

03 public static void main（String[]args）

04 {

05 int x=22；//------------------------------------------十进制

06 int y=022；//------------------------------------------八进制

07 int z=0x22；//------------------------------------------十六进制

08 System.out.println（"转化成十进制，x="+x）；

09 System.out.println（"转化成十进制，y="+y）；

10 System.out.println（"转化成十进制，z="+z）；

11 }

12 }

【代码说明】第5～7行同样定义了3种进制的变量，通过前缀0和0x来区别进制。第8～10行依然是直接输出变量的值，而没有显式地进行类型转换。

【运行效果】

转化成十进制，x=22

转化成十进制，y=18

转化成十进制，z=34

4.长整型

【实例3.4】长整型long用8个字节表示整数型，其数值介于-9223372036854775808～9223372036854775807之间。它的所有特性基本与前几种整型一样，唯一不同的是，长整型的数据后面有一个“L”字母，这个也是从表现形式上区别于其他整型的最大特征。可从下面的程序代码段中，比较长整型与其他整型有何区别。

01 public class Long

02 {

03 public static void main（String[]args）

04 {

05 long x=22L；//-------------------------------------------十进制

06 long y=022L；//-------------------------------------------八进制

07 long z=0x22L；//-------------------------------------------十六进制

08 System.out.println（"转化成十进制，x="+x）；

09 System.out.println（"转化成十进制，y="+y）；

10 System.out.println（"转化成十进制，z="+z）；

11 }

12 }

【代码说明】第5～7行定义了3个长整型变量，要注意的是结尾的标识“L”。第8～10行直接输出变量

的值，会自动进行类型转换，输出的结果都是用十进制表示的。

【运行效果】

转化成十进制，x=22

转化成十进制，y=18

转化成十进制，z=34

从以上程序代码段中可以看出，4种不同的整型类型的数据，在程序段中所表现出来的运行结果几乎是一样的。不同的是每个类型数据的取值范围不一样，随着字节数增多，而取值范围增大。虽然长整型的数据可以表示很大的数据，但如果超过了长整型的数据取值范围，该如何处理这些数据呢？在Java中，有一种大数值类型的数据，由于它属于对象类型数据，所以此处不作介绍。

3.2.2　字符型

字符型数据是平时程序设计中使用比较频繁的类型，占两个字节。特别注意的是，它必须以单引号表示，例如‘A’表示一个字符，这个字符就是A。“A”表示一个字符串，虽然只有一个字符，但因为使用双引号，所以它仍然表示字符串，而不是字符。

总之，字符数据类型只能表示单个字符，任何超过一个字符的内容，都不能被声明为字符型。字符的声明是用单引号，通过输出控制台，看到的是单引号内的字符数据。

【实例3.5】通过下列的程序代码，来看看字符型数据是如何输出的。

01 ///声明了x, y，z, a四个字符型数据变量

02 public class Char

03 {

04 public static void main（String[]args）

05 {

06 char x='美'；

07 char y='国'；

08 char z='人'；

09 char a='民'；

10 System.out.println（"这些字符组合起来就是："+x+y+z+a）；

11 }

12 }

【代码说明】第6～9行定义了4个字符型变量，第10行输出变量的内容，将字符型数据连接在一起使用的运算符也是“+”。

【运行效果】

这些字符组合起来就是：美国人民

字符型数据和整型数据都是无小数点的数据，下面将要讲述的类型则是带小数点的数据，用专业术语来讲，就是浮点型。

3.2.3　浮点型

浮点型数据表示有小数部分的数字，总共有两种类型：单精度浮点型（float）和双精度浮点型（double）。

1.单精度浮点型数据

单精度浮点型占4个字节，有效数字最长为7位，有效数字长度包括了整数部分和小数部分。一个单精度浮点型的数据定义如下所示。

float x=223.56F

注意

在每个单精度浮点型数据后面，都有一个标志性符号“F”或者“f”，有这个标志就代表是单精度浮点型数据。

【实例3.6】下面演示单精度浮点型数据在程序代码段中的使用情况。

01 ///声明了x, y，z三个浮点型变量

02 public class Float

03 {

04 public static void main（String[]args）

05 {

06 float x=22.2f；

07 float y=42.2f；

08 float z=x*y；

09 System.out.println（"x*y="+z）；

10 }

11 }

【代码说明】第6～7行定义了两个浮点型数据，它们都以“f”标识结尾。第8行定义了变量z，其值是前面定义的变量x和y的乘积。第9行输出计算结果z。

【运行效果】

x*y=936.84

提示

如果在一个浮点数后面加上“F”或者“f”，代表的就是单精度浮点型数据，否则，系统会认为是双精度浮点型数据。

2.双精度浮点型数据

双精度浮点型数据占据8个字节，有效数字最长为15位。其与单精度浮点型数据一样，后面也带有标志性符号“D”或“d”。系统默认不带标志性符号的浮点型数据是双精度浮点型数据。双精度浮点型数据的定义如下所示。

double x=33.5D

【实例3.7】下面有一个简单的程序段代码。

01 public class Double

02 {

03 public static void main（String[]args）

04 {

05 float x=23f；

06 double y=44；

07 System.out.println（"x="+x）；

08 System.out.println（"y="+y）；

09 }

10 }

【代码说明】第5行定义了一个单精度浮点型变量x，但没有带小数位。第6行定义了一个双精度浮点型变量y，也没有带小数位，也没有加标识“D”。第7～8行分别在控制台输出两个变量，注意输出的结果。

【运行效果】

x=23.0

y=44.0

从这个程序段代码中可以看出，即使浮点型数据是一个只有整数位没有小数位的数据，在输出控制台上，其仍然是带小数点的浮点数据，系统会自动加上小数点，并且小数位全部置为0。

3.2.4　布尔型

布尔型数据其实很简单，如果有人问：去不去麦当劳，可以说“不去”。如果有人问去不去看电影，可以说“去”。这里就隐藏着布尔型的数据，布尔型数据就是“是”与“否”。在程序中使用“真”和“假”来代替“是”与“否”，即“true”和“false”。

【实例3.8】仔细观察下列程序代码：

01 ///声明了a和b两个整型变量

02 ///声明了x, y，z三个布尔型变量

03 ///通过比较两个整型变量组成的表达式之间的大小，来获得不同的布尔型值

04 public class Boolean

05 {

06 public static void main（String[]args）

07 {

08 int a=30；

09 int b=59；

10 boolean x, y，z；

11 x=（a＞b）；

12 y=（a＜b）；

13 z=（（a+b）==50）；

14 System.out.println（"x="+x）；

15 System.out.println（"y="+y）；

16 System.out.println（"z="+z）；

17 }

18 }

【代码说明】

❑当说“a＞b”时，不等式不成立，所以x的结果是假。

❑当说“a＜b”时，不等式成立，所以y的结果是真。

❑当说“a+b==50”时，结果不成立，所以z的结果是假。

说明　布尔型数据的使用是在只有两种选择，不可能出现第三种选择的时候。

【运行效果】

x=false

y=true

z=false

在程序设计的过程中，当要对一个参数的是与否采取不同的行动时，可以将这个参数设为布尔型数据。也就是说，如果一个参数只有正和反两方面，就可以将其设置为布尔型变量。

3.3　变量

前面的代码使用了很多变量，那么究竟变量是什么呢？本节将介绍变量的基本概念，以及如何操作变量。

3.3.1　变量的声明

变量就是在程序的运行中可以变化的量，变量是程序设计中一个非常重要和关键的概念。在Java程序设计中，每个声明的变量都必须分配一个类型。声明一个变量时，应该先声明变量的类型，随后再声明变量的名字。下面演示了变量的声明方式。

01 ///这里salary, age, op都是变量名字

02 ///double, int, boolean是变量的类型

03 double salary；

04 int age；

05 boolean op；

每一行的第一项是变量类型，第二项是变量名。行尾的分号是必需的，这是Java语句的结束符号。如果没有这个分号，程序不会认为这是一句完整的Java语句。

同一类型的不同变量，可以声明在一行，也可以声明在不同行，如果要声明在同一行中，不同的变量之间用逗号分隔，例如下面的例子。

int studentNumber, people；

3.3.2　变量的含义

在程序设计中，经常会听到变量这个名词，到底什么是变量呢？它又有什么意义呢？

在程序运行过程中，空间内的值是变化的，这个内存空间就称为变量。为了操作方便，给这个空间取名，称为变量名，内存空间内的值就是变量值。所以，申请了内存空间，变量不一定有值，要想变量有值，就必须要放入值。

例如：“int x”，定义了变量但没有赋值，即申请了内存空间，但没有放入值。如果int x=5；说明不但申请了内存空间而且还放入了值，值为5。

3.3.3　变量的分类

变量的分类方式有多种多样，不可能单纯地将变量划分为几类，下面将以不同的分类方式来讨论变量的分类问题。

1.从作用范围来分

根据作用范围来分，一般将变量分为全局变量和局部变量。从字面上理解很简单，全局变量就是在程序范围之内都有效的变量，而局部变量就是在程序中的一部分内有效的变量。

【实例3.9】从专业的角度来解释，全局变量就是在类的整个范围之内都有效的变量。而局部变量就是在类中某个方法函数内或某个子类内有效的变量，下面将从实际程序代码中慢慢地体会。

01 ///a, b都是全局变量

02 ///c是局部变量

03 public class var

04 {

05 int a=10；

06 int b=21；

07 public static void main（String[]args）

08 {

09 var v=new var（）；

10 System.out.println（"这个是全局变量a="+v.a）；

11 v.print（）；

12 }

13 void print（）

14 {

15 int c=20；

16 System.out.println（"这个是局部变量c="+c）；

17 }

18 }

【代码说明】第5～6行定义了两个变量，它们在main（）方法外，第10行调用这个变量。第15行在print（）方法内定义了变量c，第16行在当前方法内输出此变量。

【运行效果】

这个是全局变量a=10

这个是局部变量c=20

【实例3.10】如果在main（）中同样输出“c”的值，会出现什么样的结果呢？下面从实际代码段中仔细体会。

01 public class Math1

02 {

03 public static void main（String[]args）

04 {

05 Math1 v=new Math1（）；

06 System.out.println（"这个是局部变量c="+v.c）；

07 }

08 void print（）

09 {

10 int c=20；

11 }

12 }

【运行效果】以上代码在编译时，会出现错误，就是找不到变量“c”。这说明变量“c”只在方法“print（）”中起作用，在方法外就无法再调用。

【代码说明】从上述代码中可以看出，如果一个变量在类中定义，那么这个变量就是全局变量，例如下面的代码段。

01 public class var

02 {

03 int a=10；

04 int b=21；

05 }

这里的变量“a”、“b”都是全局变量，而在类的方法、函数中定义的变量就是局部变量，例如下面的代码段。

01 public class var

02 {

03 void print（）

04 {

05 int c=20；

06 }

07 }

这里的变量“c”就是局部变量。因为它不是在类中直接定义的，而是在类的方法中定义的。

2.从类型来分

如果从类型来分，可以将变量分成基本类型变量和对象类型变量，而基本类型变量就是前面说的那8种基本数据类型的变量，如整型、浮点型、字符型、布尔型等。

说明　对象类型将在后面的章节中介绍，这里暂时不做具体的说明。

3.以所属范围分

如果按所属范围来分，可以将变量分成类变量和成员变量，类变量就是用关键字“static”声明的全局变量，它是属于类的。而成员变量就是不用“static”声明的其他实例变量，它是随对象本身的。

其实类变量就是在类中直接定义的，并且不随类产生的对象变化而变化。当在一个类中声明了一个类变量时，无论创造出多少个对象，使用对象引用这个变量，都不会发生变化。成员变量就不同了，它随着对象不同而变化。即针对同一个类，新创建一个对象，使用此对象引用这个变量，每次引用的值都不一样。

3.4　变量如何初始化

在C、C++、VB、PowerBuilder等语言中，都会提到变量的初始化，有关对象类型的变量初始化，将在后面的章节详细讲述，这里将把基本类型变量的初始化作为本节的主要内容。

【实例3.11】基本类型变量的初始化工作，就是给变量赋值。为了能够更加清晰地看到变量如何初始化，以及初始化时需要注意的知识点，下面通过实例来演示。

01 ///通过不同类型的数据的输出来查看变量如何初始化

02 ///所有的变量都是全局变量

03 public class var0

04 {

05 byte x；

06 short y；

07 int z；

08 long a；

09 float b；

10 double c；

11 char d；

12 boolean e；

13 public static void main（String[]args）

14 {

15 var0 m=new var0（）；

16 System.out.println（"打印数据x="+m.x）；

17 System.out.println（"打印数据y="+m.y）；

18 System.out.println（"打印数据z="+m.z）；

19 System.out.println（"打印数据a="+m.a）；

20 System.out.println（"打印数据b="+m.b）；

21 System.out.println（"打印数据c="+m.c）；

22 System.out.println（"打印数据d="+m.d）；

23 System.out.println（"打印数据e="+m.e）；

24 }

25 }

【代码说明】第5～12行定义了8个变量，它们分别对应8种数据类型。我们并没有为其设置初始值。第13～23行直接在控制台输出这些变量，读者可以在下面的运行效果中发现有的变量具备默认值，但有的变量什么也不输出。

【运行效果】

打印数据x=0

打印数据y=0

打印数据z=0

打印数据a=0

打印数据b=0.0

打印数据c=0.0

打印数据d=

打印数据e=false

【实例3.12】从以上例子可以看出，作为全局变量，无须初始化，系统自动给变量赋值。除了字符型数据被赋值为空，布尔型数据被赋值为false，其他一律赋值为0。下面再看一段程序代码。

01 ///通过不同类型的数据的输出来查看变量如何初始化

02 ///所有的变量都是局部变量

03 public class var1

04 {

05 void printnumber（）

06 {

07 byte x；

08 short y；

09 int z；

10 long a；

11 float b；

12 double c；

13 char d；

14 boolean e；

15 }

16 public static void main（String[]args）

17 {

18 var1 m=new var1（）；

19 System.out.println（"打印数据x="+m.x）；

20 System.out.println（"打印数据y="+m.y）；

21 System.out.println（"打印数据z="+m.z）；

22 System.out.println（"打印数据a="+m.a）；

23 System.out.println（"打印数据b="+m.b）；

24 System.out.println（"打印数据c="+m.c）；

25 System.out.println（"打印数据d="+m.d）；

26 System.out.println（"打印数据e="+m.e）；

27 }

28）

【代码说明】第7～14行定义了8个变量，但其被定义在printnumber（）方法中，属于局部变量。第19～26行在没有初始化这些变量的时候，在控制台输出这些变量，其实是不正确的。

【运行效果】这个程序段编译时就会报错，原因是所有局部变量都没有初始化。

从以上两段程序代码得出一个结果：全局变量可以不用进行初始化赋值工作，而局部变量必须要进行初始化赋值工作。

3.5　常量

常量就是从程序开始运行到结束都不变的量。在Java程序设计中，使用关键字“final”来声明一个常量。

【实例3.13】例如下面的程序代码。

01 ///这里的X是一个常量，但是在某个方法内的常量，也可以称为成员常量（作者给它取的名字）

02 public class var2

03 {

04 final int X=20；

05 public static void main（String[]args）

06 {

07 var2 m=new var2（）；

08 System.out.println（"打印数据X="+m.X）；

09 }

10 }

【代码说明】第4行通过关键字final定义了一个常量X，第8行输出这个常量的值。

注意　常量名一般都定义为大写字母。

【运行效果】

打印数据X=20

【实例3.14】如果要声明一个类常量，就需要使用关键字“static”和“final”的组合，例如下面的例子。

01 ///这里的X是类常量，所以无论是哪个对象的引用，它的值始终不变

02 public class var3

03 {

04 static final int X=20；

05 public static void main（String[]args）

06 {

07 System.out.println（"打印数据X="+X）；

08 }

09 }

【代码说明】第4行使用关键字“static”和“final”的组合，定义了类常量X。第7行在没有构造新对象的情况下，直接在控制台输出X的值。

【运行效果】

打印数据X=20

从上面的例子可以看出，如果这个常量是类常量，那么无须再构造对象，可以直接引用这个常量。前一个例子声明的常量是一般常量，不是类常量，所以一定要构造对象，通过对象来引用这个常量，所以切记类常量和一般常量的区别所在。

3.6　运算符

运算符就是在用变量或常量进行运算时，经常需要用到的符号，目前常用的总共有10种：算术运算符、关系运算符、逻辑运算符、位运算符、移位运算符、赋值运算符、三元运算符、逗号运算符、字符串运算符（将在第6章介绍）、转型运算符。下面将会对每种运算符结合实例作详细的讲述。

3.6.1　算术运算符

从小学开始就学过“加”、“减”、“乘”、“除”、“余”，其实这也是Java中的算术运算符。下面来看一种情况，当一个浮点数据加上一个整型数据，其结果是什么类型的数据？这涉及了数字精度问题，在不同类型的数据之间进行运算时，为了使结果更加精确，系统会将结果自动转化为精度更高的数据类型。

【实例3.15】以上所述的定义有点复杂，通过下面的例子进行说明。

01 ///这里的a是一个整型数据

02 ///这里的b是一个浮点型数据

03 ///相加后就是一个浮点型数据

04 public class var4

05 {

06 public static void main（String[]args）

07 {

08 int a=10；

09 float b=10f；

10 System.out.println（"a+b="+（a+b））；

11 }

12 }

【代码说明】第8行定义了整型变量a，第9行定义了浮点型变量b，第10行在控制台输出两个变量进行“加”运算后的结果。

【运行效果】

a+b=20.0

以上的程序代码中，变量“a”是整型，变量“b”是单精度浮点型，运算的结果是单精度浮点型。以上的实例说明了一点：为了保证经过算术运算符后结果的数据精度，尽量让结果与运算数据中精度较高的类型相同。这个例子就是让结果与“a”、“b”中精度较高的单精度浮点类型b类型相同，所以结果类型就是单精度浮点的数据类型。

如何将结果进行转换？转化有什么规律吗？笔者根据经验总结了以下几点。

❑当使用运算符把两个操作数结合到一起时，首先会将两个操作数转化成相同类型的数据。

❑两个操作数中如有一个是double型，那么另一个操作数一定先转化成double型，再进行运算。

❑两个操作数中如有一个是float型，那么另一个操作数一定先转化成float型，再进行运算。

❑两个操作数中如有一个是long型，那么另一个操作数一定先转化成long型，再进行运算。

❑其他任何两个基本类型数据操作，两个操作数都会自动转化成int型。

明白了数据精度的问题，再回到算术运算符的应用。算术运算符总共有7种：“+”、“-”、“*”、“/”、“%”（余数）、“++”（自加）、“--”（自减）。

【实例3.16】下面通过程序段来熟悉这些运算符的用法。

01 ///两个整型变量a, b通过算术运算符得出的结果

02 public class data1

03 {

04 public static void main（String[]args）

05 {

06 int a=10；

07 int b=3；

08 System.out.println（"a+b="+（a+b））；

09 System.out.println（"a-b="+（a-b））；

10 System.out.println（"a*b="+（a*b））；

11 System.out.println（"a/b="+（a/b））；

12 System.out.println（"a%b="+（a%b））；

13 }

14 }

【代码说明】第6～7行先定义两个整型变量，然后通过第8～12行的加、减、乘、除和求余运算，在控制台输出计算结果。

【运行效果】

a+b=13

a-b=7

a*b=30

a/b=3

a%b=1

下面重点讨论自加和自减运算符的用法，它可以使一个变量自动加1和自动减1，得到的值再赋给这个变量。

【实例3.17】自加运算符又分为两种：一种是前自加，一种是后自加。下面通过一个程序段看看什么是前自加和后自加。

01 public class data2

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（a++））；

07 }

08 }

【代码说明】上面的程序段介绍了后自加，其意义就是：先把a的值赋给a，然后，将a的值加1，存储到内存空间。于是，a输出的值就是10，而存储在内存中的a的值为11。

【运行效果】

a=10

【实例3.18】再看看下面的程序段。

01 public class data3

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（++a））；

07 }

08 }

【代码说明】上面的程序段演示了前自加，其意义就是：先让a的值加1，然后再将这个加之后的值赋给a。于是，a的输出值当然就是11。

【运行效果】

a=11

【实例3.19】下面来看一个综合的实例。

01 public class data4

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（a++））；

07 System.out.println（"a="+（++a））；

08 }

09 }

【代码说明】这个程序段首先将a的值赋值给a，然后再将a加1放到内存中，内存中的值为11，那么第一个打印语句的结果就是a=10，接下来，将内存中a的值加1再赋给a，那么a的值为12。

【运行效果】

a=10

a=12

同样自减运算符也有两种：一种是前自减，一种是后自减。

【实例3.20】先看下面的程序代码。

01 public class data5

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（--a））；

07 }

08 }

【代码说明】这个程序段介绍的是前自减，其意义是：先将a的值减1，然后赋值给a，于是a的结果就是9。

【运行效果】

a=9

【实例3.21】再来看看下面的程序段。

01 public class data6

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（a--））；

07 }

08 }

【代码说明】这个程序段介绍的是后自减，其意义是：将a的值赋给a后，再将a的值自动减1，于是输出a是10。

【运行效果】

a=10

【实例3.22】下面再看一个综合实例。

01 public class data7

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（a--））；

07 System.out.println（"a="+（--a））；

08 }

09 }

【代码说明】这个程序段首先将a的值赋值给a，然后再将a减1放到内存中，内存中的值为9。那么第一个打印语句的结果就是a=10。接下来，将内存中a的值减1再赋给a，那么a的值为8。

【运行效果】

a=10

a=8

【实例3.23】在现实的编程中，可能会遇到更加复杂的代码段。下面继续看一个综合的实例。

01 public class data8

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 System.out.println（"a="+（a--））；

07 System.out.println（"a="+（--a））；

08 System.out.println（"a="+（a++））；

09 System.out.println（"a="+（++a））；

10 }

11 }

【代码说明】首先将a的值赋给a，然后将a自动减1放到内存中，此时内存中值为9，所以第一个打印语句输出的值为10。接着，将内存的值先减1再赋给a, a就为8。随后，将a的值先赋给a，再将a的值加1放到内存中，所以内存中a为9。最后将内存中的值加1再赋给a, a的值为10。

【运行效果】

a=10

a=8

a=8

a=10

为了能方便地记忆自加和自减运算符的用法，总结如下：

❑++x：因为++在前，所以可以记忆为先加后用。

❑x++：因为++在后，所以可以记忆为先用后加。

❑--x：因为--在前，所以可以记忆为先减后用。

❑x--：因为--在后，所以可以记忆为先用后减。

3.6.2　关系运算符

关系运算符就是指两个操作数之间的关系，它包括“＞”、“＜”、“＞=”、“＜=”、“==”、“！=”。算术运算符的结果都是数字，而关系运算符的结果则是布尔型数据，这一点一定要注意。

在实际开发中，经常使用关系运算符来作为判断的条件，如果条件是真，将如何处理，如果条件是假，又该如何处理。

【实例3.24】下面看一个简单的例子，看看关系运算符的输出是什么样子。

01 ///关系运算符的应用

02 public class data9

03 {

04 public static void main（String[]args）

05 {

06 int a=10；

07 int b=21；

08 System.out.println（"说a＞b，对吗？"+（a＞b））；

09 System.out.println（"说a＞=b，对吗？"+（a＞=b））；

10 System.out.println（"说a＜b，对吗？"+（a＜b））；

11 System.out.println（"说a＜=b，对吗？"+（a＜=b））；

12 System.out.println（"说a==b，对吗？"+（a==b））；

13 System.out.println（"说a！=b，对吗？"+（a！=b））；

14 }

15 }

【代码说明】第6～7行首先定义了两个变量a和b，第8～13行通过比较两个变量的大小，来输出关系运算的结果。

【运行效果】

说a＞b，对吗？false

说a＞=b，对吗？false

说a＜b，对吗？true

说a＜=b，对吗？true

说a==b，对吗？false

说a！=b，对吗？true

说明　从以上的程序段可以看出，关系运算符的结果是布尔型数据。

3.6.3　逻辑运算符

逻辑运算符共有3种：“非”、“与”、“或”。逻辑运算符一般与关系运算符结合起来使用，下面将详细地介绍这3个逻辑运算符。

1.NOT运算符

NOT运算符就是一个否定的意思，因为英文中“NOT”就是“不”的意思，在Java中，NOT用符号“！”表示。

【实例3.25】下面看一个简单的例子。

01 ///非逻辑运算符的应用

02 public class data10

03 {

04 public static void main（String[]args）

05 {

06 int a=10；

07 int b=21；

08 System.out.println（"说a＞b，对吗？"+！（a＞b））；

09 }

10 }

【代码说明】在程序中，“a＞b”是假的，即“false”，但是前面有个“！”否定运算符，将“false”变成了“true”。

【运行效果】

说a＞b，对吗？true

2.AND运算符

根据英文“AND”的意思，就知道此运算符是“与”的意思。使用它必须要满足AND前后两个条件。AND运算符在Java中用符号“＆＆”表示。

【实例3.26】下面看一个简单的实例。

01 ///与逻辑运算符的应用

02 public class data11

03 {

04 public static void main（String[]args）

05 {

06 int a=10；

07 int b=21；

08 System.out.println（"认为既a＞b又a＜b，对吗？"+（（a＞b）＆＆（a＜b）））；

09 }

10 }

【代码说明】“a＜b”这个关系运算得出的结果是“true”，而“a＞b”这个关系运算得出的结果是“false”。两者用AND这个逻辑运算符连接后，得出的结果是“false”。

【运行效果】

认为既a＞b又a＜b，对吗？false

为什么会这样呢？下面是AND运算符的原理：

两个操作数只要有一个是“false”，那么结果就是“false”，如果两个操作数都是“true”，那么结果才是“true”。

3.OR运算符

根据英文“OR”的意思，就知道此运算符是“或”的意思，使用它只要满足“OR”前后两个条件中任意一个条件。OR运算符在Java中用符号“||”表示，其原理如下：

两个操作数只要有一个是“true”，那么结果就是“true”，否则结果就是“false”。

【实例3.27】下面看一个简单的例子。

01 public class data12

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 int b=21；

07 int c=10；

08 System.out.println（"认为既a＞b又a＜b，对吗？"+（（a＞=b）||（a==b）））；

09 System.out.println（"认为既a＞b又a=c，对吗？"+（（a＜b）||（a==c）））；

10 }

11 }

【代码说明】上面的程序段中，“a＞=b”和“a==b”两个操作数的结果都是“false”，所以或的结果就是“false”，而“a＜b”的结果是“true”，“a==c”的结果是“true”，所以结果就是“true”。

【运行效果】

认为既a＞b又a＜b，对吗？false

认为既a＞b又a=c，对吗？true

逻辑运算符主要用于判断条件，例如后面要讲述的判断语句、循环语句的条件判断等。

3.6.4　位运算符

位运算符主要针对二进制进行运算，它包括“与”、“非”、“或”、“异或”。从表面上看似乎有点像逻辑运算符，但逻辑运算符是针对两个关系运算符来进行逻辑运算，而位运算符主要针对两个二进制数的位进行运算。下面详细介绍每个位运算符。

1.与运算符

与运算符用符号“＆”表示，其使用规律如下：

【实例3.28】两个操作数中位都为1的情况下，结果才为1，否则结果为0，例如下面的程序段。

01 public class data13

02 {

03 public static void main（String[]args）

04 {

05 int a=129；

06 int b=128；

07 System.out.println（"a和b与的结果是："+（a＆b））；

08 }

09 }

【代码说明】“a”的值是129，转换成二进制就是10000001，而“b”的值是128，转换成二进制就是10000000。根据与运算符的运算规律，只有两个位都是1，结果才是1，可以知道上述结果就是10000000，即128。

【运行效果】

a和b与的结果是：128

2.或运算符

或运算符用符号“|”表示，其运算规律如下：

两个位只要有一个为1，那么结果就是1，否则就为0。

【实例3.29】下面看一个简单的例子。

01 public class data14

02 {

03 public static void main（String[]args）

04 {

05 int a=129；

06 int b=128；

07 System.out.println（"a和b或的结果是："+（a|b））；

08 }

09 }

【代码说明】a的值是129，转换成二进制就是10000001，而b的值是128，转换成二进制就是10000000，根据或运算符的运算规律，两个位中有一个是1，结果就是1，可以知道上述结果就是10000001，即129。

【运行效果】

a和b或的结果是：129

3.非运算符

非运算符用符号“～”表示，其运算规律如下：

如果位为0，结果是1；如果位为1，结果是0。

【实例3.30】下面看一个简单的例子。

01 public class data15

02 {

03 public static void main（String[]args）

04 {

05 int a=2；

06 System.out.println（"a非的结果是："+（～a））；

07 }

08 }

【代码说明】a的值是2，转换成二进制就是0010，因为非运算就是取相反的位值，而且取反是针对二进制的所有位置而言，因为二进制中最高位是1表示负数，所以最终转换为十进制就是-3。

注意

本章中所计算的一些二进制，写作方法都没有那么复杂，如int类型占4字节，每个字节是8位，则对于数值2来说，标准的二进制应该是00000000 00000000 00000000 00000010，一共是32位。但为了简化说法，我们并不把多余的给出来。上述二进制进行非运算后是11111111 11111111 11111111 11111101。如果计算机基础学得好的读者，可能还会知道负数的二进制，其实是它绝对值的二进制取反再加1。上述非运算后的结果是00000000 00000000 00000000 00000011这个二进制取反加1的结果，而这个值转换为十进制就是3，因为最高位是1表示负数，所以结果为-3。如果读者还不明白此内容，可以参考一些数据结构和汇编语言的专业书籍。

【运行效果】

a非的结果是：-3

4.异或运算符

异或运算符是用符号“^”表示的，其运算规律是：

两个操作数的位中，相同则结果为0，不同则结果为1。

【实例3.31】下面看一个简单的例子。

01 public class data16

02 {

03 public static void main（String[]args）

04 {

05 int a=15；

06 int b=2；

07 System.out.println（"a与b异或的结果是："+（a^b））；

08 }

09 }

【代码说明】a的值是15，转换成二进制为1111，而b的值是2，转换成二进制为0010，根据异或的运算规律，可以得出其结果为1101，即13。

【运行效果】

a与b异或的结果是：13

3.6.5　移位运算符

移位运算符也针对二进制的“位”，它主要包括左移位运算符（＜＜）、右移位运算符（＞＞＞）、带符号的右移位运算符（＞＞）。

1.左移运算符

左移运算符用“＜＜”表示，是将运算符左边的对象，向左移动运算符右边指定的位数，并且在低位补0。

【实例3.32】其实，向左移n位，就相当于乘上2的n次方，例如下面的例子。

01 public class data17

02 {

03 public static void main（String[]args）

04 {

05 int a=2；

06 int b=2；

07 System.out.println（"a移位的结果是："+（a＜＜b））；

08 }

09 }

【代码说明】首先从本质上来分析，2的二进制是00000010，它向左移动2位，就变成了00001000，即8。如果从另一个角度来分析，它向左移动2位，其实就是乘上2的2次方，结果还是8。

【运行效果】

a移位的结果是：8

2.右移运算符

【实例3.33】右移运算符用符号“＞＞＞”表示，是将运算符左边的对象向右移动运算符右边指定的位数，并且在高位补0，其实右移n位，就相当于除上2的n次方。

01 public class data18

02 {

03 public static void main（String[]args）

04 {

05 int a=16；

06 int b=2；

07 System.out.println（"a移位的结果是："+（a＞＞＞b））；

08 }

09 }

【代码说明】从本质上来分析，16的二进制是00010000，它向右移动2位，就变成了00000100，即4。如果从另一个角度来分析，它向右移动2位，其实就是除以2的2次方，结果还是4。

【运行效果】

a移位的结果是：4

3.带符号的右移运算符

带符号的右移运算符用符号“＞＞”表示，是将运算符左边的运算对象，向右移动运算符右边指定的位数。

【实例3.34】如果是正数，在高位补零，如果是负数，则在高位补1，先看下面一个简单的例子。

01 public class data19

02 {

03 public static void main（String[]args）

04 {

05 int a=16；

06 int c=-16；

07 int b=2；

08 int d=2；

09 System.out.println（"a的移位结果："+（a＞＞b））；

10 System.out.println（"c的移位结果："+（c＞＞d））；

11 }

12 }

【代码说明】a的值是16，转换成二进制是00010000，让它右移两位成00000100即4。c的值是-16，转换成二进制是11101111，让它右移一位是11111011即-4。

【运行效果】

a的移位结果：4

c的移位结果：-4

3.6.6　赋值运算符

赋值就是将数值赋给变量，而这个赋值运算符就充当了这个赋值的任务，其实最简单的赋值运算符就是“=”。

当然除了“=”外，还有很多其他的赋值运算符。有“+=”、“-=”、“*=”、“/=”、“%=”、“＞＞=”、“＞＞＞=”、“＜＜=”、“＆=”、“|=”、“^=”。

【实例3.35】下面给出一个简单的例子。

01 public class data20

02 {

03 public static void main（String[]args）

04 {

05 int a=5；

06 int b=2；

07 System.out.println（"a+=b的值："+（a+=b））；

08 System.out.println（"a-=b的值："+（a-=b））；

09 System.out.println（"a*=b的值："+（a*=b））；

10 System.out.println（"a/=b的值："+（a/=b））；

11 System.out.println（"a%=b的值："+（a%=b））；

12 System.out.println（"a＞＞=b的值："+（a＞＞=b））；

13 System.out.println（"a＞＞＞=b的值："+（a＞＞＞=b））；

14 System.out.println（"a＜＜=b的值："+（a＜＜=b））；

15 System.out.println（"a＆=b的值："+（a＆=b））；

16 System.out.println（"a|=b的值："+（a|=b））；

17 System.out.println（"a^=b的值："+（a^=b））；

18 }

19 }

【代码说明】第7行的“a+=b”就是a=a+b。第8行的“a-=b”就是a=a-b。第17行的“a^=b”就是a=a^b。

【运行效果】

a+=b的值：7

a-=b的值：5

a*=b的值：10

a/=b的值：5

a%=b的值：1

a＞＞=b的值：0

a＞＞＞=b的值：0

a＜＜=b的值：0

a＆=b的值：0

a|=b的值：2

a^=b的值：0

3.6.7　三元运算符

三元运算符一般用得很少，因为它在程序段中的可读性很差，所以笔者建议不要经常使用三元运算符，但很少使用并不代表不使用，所以还是要掌握好它的用法。三元运算符的表达形式如下：

布尔表达式？值0：值1

它的运算过程是：如果布尔表达式的结果是true，就返回值0；如果布尔表达式的结果是false，就返回值1。

【实例3.36】例如下面的程序段。

01 public class data21

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 int b=20；

07 System.out.println（"此三元运算式结果是："+（（a＞b）?'A'：'B'））；

08 }

09 }

【代码说明】因为“a”小于“b”，所以“a＞b”这个关系运算符的结果是“false”，既然是“false”，那么选择值1，即这个三元运算符的结果是“B”。

【运行效果】

此三元运算式结果是：B

3.6.8　逗号运算符

在Java程序设计中，逗号运算符一般是用来将几个数值彼此分开，例如数组中的每个元素都是使用逗号与其他元素分开的。这个运算符太简单了，这里不再给出实例。

3.6.9　转型运算符

转型运算符的用处是将一种类型的对象或数据，经过强制转换而转变为另一种类型的数据。它的格式是在需要转型的数据前加上“（）”，然后在括号内加入需要转化的数据类型。

【实例3.37】有的数据经过转型运算后，精度会丢失，而有的会更加精确，下面的例子可以说明这个问题。

01 public class data22

02 {

03 public static void main（String[]args）

04 {

05 int x；

06 double y；

07 x=（int）34.56+（int）11.2；

08 y=（double）x+（double）10；

09 System.out.println（"x="+x）；

10 System.out.println（"y="+y）；

11 }

12 }

【代码说明】第7行中，由于在34.56前有一个int的强制类型转化，所以34.56就变成了34。同样，11.2就变成了11，所以x的结果就是45。第8行中，在x前有一个double类型的强制转换，所以x的值变为45.0，而数值11也被强制成double类型，变成11.0，所以最后y的值变为56.0。

【运行效果】

x=45

y=56.0

3.6.10　运算符的优先级别

当多个运算符出现在一个表达式中，谁先谁后呢？这就涉及运算符的优先级别。在一个多运算符的表达式中，运算符优先级不同会导致最后得出的结果差别甚大，例如，（1+3）+（3+2）*2，这个表达式如果按加号最优先计算，答案是18，如果按照乘号最优先，答案则是14。

下面将详细介绍在Java程序设计中，各个运算符的优先级别，如表3.1所示。

3.7　常见疑难解答

3.7.1　如何将十进制转换成二进制

答：如何将十进制转换成二进制？作者有一个方法就是先熟练记忆2的n次方的结果，一般来说记到2的7次方就可以了。

下面将举例讲述这个方法：首先记住20=1、21=2、22=4、23=8、24=16、25=32、26=64、27=128。现在要把十进制157转换成二进制，因为157是大于128的，所以第七位上肯定是1。用157-128=29，因为29是大于16小于32的，所以第七位、第六位都为0，而第五位就是1。再用29-16=13，13大于8，所以第四位是1。再用13-8=5，5大于4，所以第三位为1。再用5-4=1，由于1小于2，所以第二位为0，而1正好等于第一位，所以第一位为1，综合起来就是10011101。

3.7.2　转型运算符会引起精度问题，为什么还要使用它

答：其实不但基本类型的数据会使用转型运算符，对象类型的数据也要使用转型运算符。在使用基本数据转型时，一般都要从低精度往高精度转，但是在某些特定的情况下，或者说在用户特殊要求下，会从高精度转向低精度。例如有的数字希望能够去掉小数位，那么就只能从高精度往低精度转型。

3.8　小结

本章是Java语言的基础，因为基本上所有的程序都是由一个变量或者一个表达式组成，程序中的数据离不开变量和常量，而程序的运算离不开这些数据类型和运算符。本章给出了37个小实例，希望读者能自己动手多加练习，并找到一些运算的技巧和原理。

3.9　习题

1.定义时就需赋值，且不能再被改变赋值的是______。

A.常量

B.变量

2.常量会带来什么好处？

3.数组的下标是从哪个数字开始的？

4.“=”和“==”有什么区别？

5.10%7在Java中的值是多少？

第4章　程序设计中的流程控制

什么是流程控制呢？流程就是做一件事情的顺序，或者说次序。在程序设计中，流程就是要完成一个功能，而流程控制则是指如何在程序设计中，控制完成某种功能的次序。本章将通过大量的实例，为读者讲述如何在程序中设计好流程控制。

本章重点：

❑Java语言的编程风格。

❑条件语句、分支语句和循环语句。

❑中断、继续、返回语句。

4.1　编程风格

本章开始接触到编写Java程序代码，有一点必须强调，那就是编程风格的问题，虽然其不影响程序代码段的运行，但对于程序的可读性起着重要的作用。自己编出的程序要让别人看懂，首先在排版方面要非常注意，下面将探讨编程风格的问题。

其实每个人、每个软件开发公司的编程风格都不一样。一个人编写的程序代码，就应该能让别人看懂，甚至是过了很长时间，自己也要看得懂，否则这个程序就成了一个没法扩展的程序。编程风格是指编程时的格式，让程序看上去就很有层次感。

【实例4.1】下面通过一些例子，说明编程风格的重要性，先来看第一个例子。

01 public class math

02 {

03 public static void main（String[]args）

04 {

05 int x=12；

06 double y=12.3d；

07 void print（）

08 {

09 char a='a'；

10 System.out.println（a）；

11 }

12 System.out.println（x+y）；

13 }

14 }

【代码说明】上面程序段的整个排版看起来是否很舒服，并且层次感很强？是否一眼看上去就知道整个程序架构？这里的关键在于缩排，缩排也称为跳格。

上一个程序段采用的跳格形式：“public class math”是顶格的，接着主运行程序前跳4个空格，在主运行程序内的运行代码段一律跳8个空格，而在主运行程序方法内的代码前，再跳4个空格。这样整个程序的所属关系就很明显了。主运行程序从属于“math”类，其余的都属于主运行程序，而在主运行程序方法内的代码段又属于此方法。规律就是空格多的代码从属于空格少的代码。

【运行效果】这段代码无法正常运行，这只是给读者讲解结构和排版的形式。

【实例4.2】除了空格外，空行也是必要的。为什么要使用空行呢？先看下列程序代码，再来仔细分析。

01 public class math

02 {

03 public static void main（String[]args）

04 {

05 int x=12；

06 int y=23；

07 void print（）

08 {

09 ……

10 }

11 12

void view（）

13 {

14 ……

15 }

16 }

17 }

【代码说明】在“print”方法与“view”方法之间有个空行（第11行），使用空行区分不同功能的模块。“print”方法所完成的功能与“view”所完成的功能不一样，所以使用空行将它们分开，这样更增加了程序的可读性。

【运行效果】这段代码无法正常运行，这只是给读者讲解结构和排版的形式。

另外，需要注意的是方法或属性的命名。这些名字应该有含义，最好有规律。不要只使用“a”、“b”这种通用变量，适当可以根据变量或函数的功能为其命名。上面的“print”，其他程序员一看就知道这个方法，是有关打印或输出的函数。再如变量名“name”，一看就知道是有关名字的变量。所以，一定要命名的有意义，否则程序的可读性不强。

还有一点是有关注释的。在每个方法的方法名旁边，应该添加一些注释，同时在一段程序完成之后，也要对程序的功能及如何操作做简单的描述。

只要做到以上几点，这个程序就是易读的。即使自己在很长时间后，再来读程序也会一目了然。

4.2　条件语句

在现实生活中，经常听人说：如果某人发财了，某人就会做什么。其实这就是程序设计中所说的条件语句。像“如果……”、“就……”、“否则……”，当然这只是很简单的条件语句，在真正的程序设计中，使用的条件语句要比这复杂得多。

4.2.1　条件语句的种类

在程序设计中，条件语句的标准格式如下：

01 if（条件）

02 {

03 目的一；

04 }

05 else

06 {

07 目的二；

08 }

【实例4.3】掌握格式后，先看一个简单的程序段。

01 ///将整型变量a, b的大小比较得出的布尔型变量作为条件语句的条件

02 ///通过条件语句来得出不同的输出结果

03 public class control

04 {

05 public static void main（String[]args）

06 {

07 int a=20；

08 int b=30；

09 if（a＞b）

10 {

11 System.out.println（"很幸运"）；

12 }

13 else

14 {

15 System.out.println（"很开心"）；

16 }

17 }

18 }

【代码说明】因为“a=20”而“b=30”，所以“a＜b”。在条件语句中，程序代码的意思是，如果“a＞b”，就输出“很幸运”，如果“a＜b”，就输出“很开心”，所以程序的输出就是“很开心”。

【运行效果】

很开心

注意

条件表达式是一个关系表达式，其结果是布尔型数据。换句话解释上面的程序段：如果“a＞b”是真，就输出“很幸运”，否则输出“很开心”。

在条件语句的程序设计中，有一种最简单的条件语句，如下所示：

01 if（条件）

02 目的；

如果有很多的目的，也可以采取下列的形式：

01 if（条件）

02 {

03 目的一；

04 目的二；

05 目的三；

06 目的四；

07 }

【实例4.4】下面看一个有关这种类型的条件语句的例子。

01 public class control1

02 {

03 public static void main（String[]args）

04 {

05 int salary=10000；

06 if（salary＞500）

07 {

08 System.out.println（"想请吃饭！"）；

09 System.out.println（"想请唱歌！"）；

10 System.out.println（"想请喝酒！"）；

11 }

12 13

}

14 }

【代码说明】条件语句中判断“salary＞500”是否为真，如果是真就输出“想请吃饭！想请唱歌！想请喝酒！”，如果是假，就什么都不做。在程序中“salary=10000”，满足“salary＞500”，条件为真，所以输出以上的三句话。

【运行效果】

想请吃饭！

想请唱歌！

想请喝酒！

注意　在多个目的的程序段中，一般按顺序执行，即先目的一，再目的二，再目的三，依次执行下去。

如果出现多种不同的条件，应该如何处理呢？可以使用条件语句中的复杂型，其结构如下：

01 if（条件1）

02 {

03 目的1；

04 }

05 else if（条件2）

06 {

07 目的2；

08 }

09 else if（条件3）

10 {

11 目的3；

12 }

13 else

14 {

15 不满足以上所有条件，如何办；

16 }

【实例4.5】根据以上的结构，学习有关这种复杂条件语句的实例，代码如下所示。

01 ///当achievement等于100，就奖励一台笔记本电脑

02 ///当achievement大于90小于100，就奖励一个MP4

03 ///当achievement大于80小于90，就奖励一块网卡

04 ///当achievement大于60小于80，不给予奖励

05 ///当achievement小于60，放假回学校补习

06 public class control2

07 {

08 public static void main（String[]args）

09 {

10 int achievement=85；

11 if（achievement==100）

12 {

13 System.out.println（"奖励一台笔记本电脑"）；

14 }

15 else if（（achievement＞=90）＆＆（achievement＜100））

16 {

17 System.out.println（"奖励一个MP4"）；

18 }

19 else if（（achievement＞=80）＆＆（achievement＜90））

20 {

21 System.out.println（"奖励一块网卡"）；

22 }

23 else if（（achievement＞=60）＆＆（achievement＜80））

24 {

25 System.out.println（"不给予任何奖励"）；

26 }

27 else

28 {

29 System.out.println（"放假到学校补习"）；

30 }

31 }

32 }

【代码说明】上述代码可以看出，当有多个不同的条件存在时，处理的结果就不一样。成绩在大于80小于90分之内，就可以奖励一块网卡，而成绩大于90小于100分，则奖励一个MP4。在此程序中，开始成绩是85分，所以处理的结果就是奖励一块网卡。

【运行效果】

奖励一块网卡

条件语句已经基本介绍完毕，很重要的一点就是，在程序设计的时候，思路一定要清晰，如何才能有很清晰的思路呢？那就是绘制流程图。流程图就是：在程序开发前，为了能够思路更加清晰，而将整个程序执行的顺序流程绘制出来。图4.1为一个有关条件语句的通用流程图。

将上面的程序段作为一个实例，绘制其基本的流程图，如图4.2所示。鉴于页面的版面，这里只给出了4种条件，并没有完全体现出上面案例的5种条件，读者可自己画一个完整的流程图。

图　4.1　条件语句流程图

图　4.2　一个实例的条件流程图

针对最复杂的条件语句，在程序设计中，有一种分支语句可以代替复杂条件语句。在实际程序开发过程中，使用条件语句类型比较多的是标准型，而复杂型的一般用分支语句来代替。当然也可以使用复杂型的条件语句。为了与实战结合，下面看一个稍微复杂的程序段。

【实例4.6】条件：设计一个程序，用于计算语文（90）、英语（75）、数学（90）、艺术（85）四门功课的平均分，并对此学生进行评价。

在编写程序之前要先绘制流程图，这样编程的思路就会非常清晰，本例流程如图4.3所示。

根据以上的流程图，开始编写程序，程序代码段如下。

01 ///通过计算学生的平均分Avg，得出这个学生的等级

02 ///如果是大于90小于或等于100则是优秀

03 ///如果是大于80小于或等于90则是良好

04 ///如果是大于70小于或等于80则是良

05 ///如果是大于60小于或等于70则是合格

06 ///如果小于或等于60则是不合格

07 public class control3

08 {

09 public static void main（String[]args）

10 {

11 int Chinese=90；

12 int English=75；

13 int Math=90；

14 int Art=85；

15 double Avg=（Chinese+English+Math+Art）/4；

16 if（（Avg＞90）＆＆（Avg＜=100））

17 {

18 System.out.println（"这个学生的所有功课的平均分是："+Avg）；

19 System.out.println（"这个学生的成绩应得A，是优秀"）；

20 }

21 else if（（Avg＞80）＆＆（Avg＜=90））

22 {

23 System.out.println（"这个学生的所有功课的平均分是："+Avg）；

24 System.out.println（"这个学生的成绩应得B，是良好"）；

25 }

26 else if（（Avg＞70）＆＆（Avg＜=80））

27 {

28 System.out.println（"这个学生的所有功课的平均分是："+Avg）；

29 System.out.println（"这个学生的成绩应得C，是良"）；

30 }

31 else if（（Avg＞60）＆＆（Avg＜=70））

32 {

33 System.out.println（"这个学生的所有功课的平均分是："+Avg）；

34 System.out.println（"这个学生的成绩应得D，是合格"）；

35 }

36 else

37 {

38 System.out.println（"这个学生的所有功课的平均分是："+Avg）；

39 System.out.println（"这个学生的成绩应得E，是不合格"）；

40 }

41 }

42 }

图　4.3　成绩代码条件流程图

【代码说明】第16～40行是复杂型的条件语句，判断了5种情况下不同的输出结果。第15行定义的是double变量，表示计算后的平均值。

【运行效果】

这个学生的所有功课的平均分是：85.0

这个学生的成绩应得B，是良好

4.2.2　如何使用好条件语句

使用好条件语句需要注意以下几点：

（1）应该绘制流程图，使编程时思路更加清晰。

（2）编程时，在最简单形式的条件语句中，可以不使用大括号，因为它不会产生混淆，但建议无论哪种形式的条件语句，都应该使用大括号。

4.3　循环语句

循环语句在程序设计中有什么作用呢？

【实例4.7】下面先看一段简单的程序段，再来看看使用循环语句编写程序的好处在哪里。

01 ///将x连续加1加10次

02 public class control4

03 {

04 public static void main（String[]args）

05 {

06 int x=1；

07 x=x+1；

08 x=x+1；

09 x=x+1；

10 x=x+1；

11 x=x+1；

12 x=x+1；

13 x=x+1；

14 x=x+1；

15 x=x+1；

16 x=x+1；

17 System.out.println（x）；

18 }

19 }

【代码说明】代码的含义是让变量“x”连续加1共加10次。如果想要加1加100次，那要让“x=x+1”这个表达式重复100次。

【运行效果】

11

这样庞大的程序段所要完成的功能，不过是一个很简单的相加功能。为了解决这类问题，程序设计中引入了循环语句。循环语句总共有3种常见的形式：for语句、while语句、do/while语句。下面将逐个进行详细的介绍。

4.3.1　for语句

for语句的基本结构如下所示。

01 for（初始化表达式；判断表达式；递增（递减）表达式）

02 {

03 执行语句

04 }

【实例4.8】针对for语句，来修改上面的程序段。

01 public class control5

02 {

03 public static void main（String[]args）

04 {

05 int x；

06 int n=10；

07 for（x=1；n＞0；n--，x++）

08 {System.out.println（x）；}

09 }

10 }

【代码说明】在这个程序段里，用第5～8行共4句代码完成了前面程序段里12句代码所解决的事情。【运行效果】

1 2

3 4

5 6

7 8

9 10

【实例4.9】如果前面的程序段中，需要连续加1加100次。则参考下面的程序段。

01 public class control6

02 {

03 public static void main（String[]args）

04 {

05 int x；

06 int n=100；

07 for（x=1；n＞0；n--，x++）{}

08 System.out.println（x）；

09 }

10 }

【代码说明】在这个程序段里，使用第5～8行共4句代码可以解决前面程序段中12句代码所解决的问题，这就是循环语句的优势。

【运行效果】

101

下面详细地解释for循环语句中各个子项的意义。

❑初始化表达式：初始化表达式的意义，在于定义循环之前变量的值是多少，如果没有这一项，就不知道该从哪个值开始循环。

❑判断表达式：判断表达式的作用在于规定循环的终点。如果没有判断表达式，那么此循环就成了死循环。

❑递增（递减）表达式：这一项规定每执行一次程序，变量以多少增量或减量进行变化。

注意　一定要注意递增（递减）表达式中可以有多个表达式，它们以逗号间隔，而不是分号。

其实也可以利用在条件判断语句中提到的流程图来编写程序，在流程图中可以看出程序运行的顺序，如图4.4所示。

【实例4.10】下面再看一个九九乘法表的程序段。先来绘制流程图，如图4.5所示。

图　4.4　循环语句流程图

图　4.5　九九乘法表流程图

根据流程图，使用循环语句编写一个乘法口诀表。

01 ///这是一个多重for循环语句

02 public class control7

03 {

04 public static void main（String[]args）

05 {

06 for（int x=2；x＜10；x++）

07 {

08 for（int y=1；y＜10；y++）

09 {

10 System.out.print（x+"*"+y+"="+（x*y））；

11 System.out.print（""）；

12 }

13 System.out.println（""）；

14 }

15 }

16 }

【代码说明】第6～14行是外围的for循环，第8～12行是一个嵌入在外围循环中的循环，这样的我们称之为嵌套循环。为什么要使用两重循环呢？因为它涉及两个变化的量。在一个程序里，有多少个不同变量，就要使用多少个for循环，当然也不是非常绝对的。

【运行效果】

2 *1=2 2*2=4 2*3=6 2*4=8 2*5=10 2*6=12 2*7=14 2*8=16 2*9=18

3 *1=3 3*2=6 3*3=9 3*4=12 3*5=15 3*6=18 3*7=21 3*8=24 3*9=27

4 *1=4 4*2=8 4*3=12 4*4=16 4*5=20 4*6=24 4*7=28 4*8=32 4*9=36

5 *1=5 5*2=10 5*3=15 5*4=20 5*5=25 5*6=30 5*7=35 5*8=40 5*9=45

6 *1=2 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=54

7 *1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63

8 *1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72

9 *1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81

在上面的程序段中，使用了嵌套for语句。其实真正复杂的程序段中，可能会出现4个嵌套的for语句，这要在实践中慢慢体会。

4.3.2　while循环

在英文中“while”这个词的意思是“当”，而在Java程序设计中，也可以将其理解为“当”。其语法结构是：

01 while（条件）

02 {

03 目的一；

04 目的二；

05 ……

06 }

当满足某种条件，就执行“目的一”、“目的二”等，while语句的循环体在{}中。

【实例4.11】下面看一段程序段，通过分析它，让读者更加清楚循环语句。

01 ///通过判断y是否大于0

02 ///如果y大于0则将计费次数减1，x加1

03 public class control8

04 {

05 public static void main（String[]args）

06 {

07 int x=0；

08 int y=100；

09 int sum=0；

10 while（y＞0）

11 {

12 x=x+1；

13 y--；

14 sum+=x；

15 }

16 System.out.println（sum）；

17 }

18 }

【代码说明】这个程序段是将数字从1一直加到100，条件是只要“y”大于0，就会执行大括号里的语句。“y”初始值是100，满足条件，所以执行大括号的表达式。先将“x+1”赋给“x”，因为x的初始值是0，所以x从1开始计数，然后，将“y”自减1，此时“y”变成了99，将“sum”加上“x”赋给“sum”。此时，执行语句结束了，又会回到小括号的条件，最后再比较“y=99”是否大于0，如果大于0，再继续执行大括号里的语句。

一旦“y”自减到小于0，则将结束执行大括号里的语句，开始执行大括号外的语句。在上面的程序段中，y就相当于一个计数器。

【运行效果】

5050

同样，在编写while语句时，要先绘制流程图，根据流程图再来编写程序段，整个思路就会很清晰了。下面先看while的流程图，如图4.6所示。

其实while语句很简单，根据这个流程图，就可以思路很清晰地编写程序段。下面针对上面的程序段来绘制流程图，如图4.7所示。

图　4.6　循环语句流程图

图　4.7　一个循环语句实例的流程图

看了这个流程图，会发现按照流程图来编写程序简单多了。为了巩固以上所说，再看一个例子。

【实例4.12】试编写程序实现输出1～100间的整数，并且此整数必须满足：它是3的倍数，但不是5的倍数，也不是9的倍数。针对这个例子，先来绘制一个流程图，如图4.8所示。

根据流程图，现在来编写程序段。

01 ///通过判断是否在100以内，并且是3的倍数

02 ///然后再判断这些数是否不是5的倍数

03 ///最后判断这些数是否不是9的倍数

04 public class control9

05 {

06 public static void main（String[]args）

07 {

08 int x=1；

09 while（（（3*x＞1）＆＆（3*x＜100）））

10 {

11 if（（3*x）%5！=0）

12 {

13 if（（3*x）%9！=0）

14 {

15 System.out.println（3*x）；

16 }

17 }

18 x++；

19 }

20 }

21 }

图　4.8　输出特殊数字的循环流程图

【代码说明】第9行是一个循环判断条件，判断是否在100以内，并且是3的倍数。第11行是一个条件语句，判断这些数是否不是5的倍数，第13行也是一个条件语句，判断这些数是否不是9的倍数。

【运行效果】

3 6

12 21

24 33

39 42

48 51

57 66

69 78

84 87

93 96

按照先绘制流程图，后编写程序的步骤，会显得思路更清晰。其实从上面的程序段中，可以总结一点：当由多个条件形成循环条件时，可以选择其中一个作为循环条件，而剩下的条件可以在循环体中作条件语句。

4.3.3　do……while语句

在学习do……while语句之前，先清楚while语句是如何工作的。while语句是先进行条件判断，再执行大括号内的循环体。do……while语句与while语句不同的是，它先执行大括号内的循环体，再判断条件，如果条件不满足，下次不再执行循环体。也就是说，在判断条件之前，就已经执行大括号内的循环体。

【实例4.13】下面先看一个程序段。

01 ///通过先判断这个数是否是3的倍数，并且是否是5的倍数

02 ///再判断是否是9的倍数

03 ///最后判断是否在100以内

04 public class control10

05 {

06 public static void main（String[]args）

07 {

08 int x=1；

09 do

10 {

11 if（（3*x）%5！=0）

12 {

13 if（（3*x）%9！=0）

14 {System.out.println（3*x）；}

15 }

16 x++；

17 }

18 while（（（3*x＞1）＆＆（3*x＜100）））；

19 }

20 }

【运行效果】

3 6

12 21

24 33

39 42

48 51

57 66

69 78

84 87

93 96

【代码说明】从上面的程序段输出结果可以看出，与使用while语句的输出结果是一样的，为什么会是一样的呢？下面来分析：

当“x=33”时，先不会检验“3*33=99”是否小于100，而会先执行大括号内的循环体。当检测到99是9的倍数时，条件是“false”，于是就会退出条件语句，继续执行“x”自加1表达式，于是“x”变成了34，由于“34*3=102”大于100，所以结束循环体。因此程序执行到“x=32”后就无输出了，最后输出的结果当然和while语句的输出一样。

其实在实际程序开发中，不经常使用do……while循环语句。因为这种语句是先执行循环体再检测条件，所以会有一些危险数据不经检测，就会被执行。建议使用while语句或者for循环语句来编写代码。

4.4　中断与继续语句

在实际编程中，可能会出现中断某个程序，或从一个程序点开始，继续执行程序的特殊情况。对于这种特殊情况，Java会使用中断与继续功能来解决。

4.4.1　中断控制语句

在Java程序开发中，使用关键字“break”来表示中断控制。中断控制语句用来强行退出程序的循环体部分。

【实例4.14】为了能熟悉中断控制语句，下面看一个简单的程序段，通过这个例子，可以看到中断控制语句在实际开发中的用处。

01 //通过System.out语句可以将数据打印出来

02 public class control11

03 {

04 public static void main（String[]args）

05 {

06 int i=1；

07 while（i＜=10）

08 {

09 System.out.println（i）；

10 i++；

11 if（i＞5）

12 {break；}

13 }

14 }

15 }

【代码说明】第11行添加了一个条件语句，当变量i大于5时，使用break语句退出while循环体。【运行效果】

1 2

3 4

5

【实例4.15】如果上述代码中，没有中断语句强行退出，则代码如下所示。

01 //通过将System.out语句放置在中断语句之前，则会循环到最后

02 public class control12

03 {

04 public static void main（String[]args）

05 {

06 int i=1；

07 while（i＜=10）

08 {

09 System.out.println（i）；

10 i++；

11 }

12 }

13 }

【代码说明】第7～11行是while循环体，这里没有使用中断语句，所以符合条件的变量i值全部输出。【运行效果】

1 2

3 4

5 6

7 8

9 10

从上面两个案例的程序段运行结果可以看出：当使用了break语句后，程序执行到“x=6”时，根据条件判断“x＞5”，执行中断语句，直接退出程序的循环体部分。

说明

由以上程序段可以总结一个规律：中断语句一般和条件语句结合使用，当满足条件语句中的条件时，就会执行中断语句。

【实例4.16】下面再看一个有关中断语句的例子。

01 //通过将System.out语句放置在中断语句之前，则会循环一次，再退出循环

02 public class control13

03 {

04 public static void main（String[]args）

05 {

06 for（int i=2；i＜10；i++）

07 {

08 System.out.println（i）；

09 if（i%2==0）

10 {break；}

11 }

12 System.out.println（"退出来了"）；

13 }

14 }

【代码说明】第9～10行又是条件语句和中断语句集合，在符合条件的时候，退出for循环体。当“i”是偶数的时候，直接中断循环体。由于第8行的输出方法属于循环体中的方法，所以不会执行它。

【运行效果】

2 退出来了

【实例4.17】将输出方法的位置进行调整，再看下面的代码。

01 //通过将System.out语句的位置放置在中断语句之后，则直接退出循环

02 public class control14

03 {

04 public static void main（String[]args）

05 {

06 for（int i=2；i＜10；i++）

07 {

08 if（i%2==0）

09 {break；}

10 System.out.println（i）；

11 }

12 System.out.println（"退出来了"）；

13 }

14 }

【代码说明】与前面的程序段一样，代码会直接跳出循环体程序，第12行的输出方法在循环体外，所以得以执行。

【运行效果】

退出来了

通过上述几个实例，可以总结出中断语句的使用方法如下：

一般和条件判断语句结合使用，中断语句是中断整个循环体。跳出循环体后，直接执行循环体以外的语句。

4.4.2　继续语句

在Java程序设计中，继续语句使用关键字“continue”表示。继续语句同中断语句一样，用在循环体中，继续语句是用于终止当前循环体，返回到循环开始处，接着开始新的循环。

【实例4.18】下面先看一个有关继续语句的实例。

01 ///只在奇数时，才输出，偶数时，会退出本次循环

02 public class control15

03 {

04 public static void main（String[]args）

05 {

06 for（int i=1；i＜10；i++）

07 {

08 if（i%2==0）

09 {continue；}

10 System.out.println（i）；

11 }

12 System.out.println（"退出来了"）；

13 }

14 }

【代码说明】仔细分析以上的程序段，如果使用“break”语句，那么运行结果中只有一个“1”，而用了继续语句，则输出了10以内的奇数。在程序中，如果“i”是偶数，遇到继续语句，就终止“System.out.println（i）”这个语句，又跳到循环开始，重新循环。所以，只要遇到偶数就会终止程序，遇到奇数程序就会继续运行。

【运行效果】

1 3

5 7

9 退出来了

4.5　分支语句

在讲述条件判断语句时，曾经提到当判断条件过多时，可以使用分支语句来编写。分支语句的基本结构是：

01 switch（整数因子）

02 {

03 case整数值1：语句；break；

04 case整数值2：语句；break；

05 case整数值3：语句；break；

06 case整数值4：语句；break；

07 case整数值5：语句；break；

08 ……

09 default：语句；

10 }

同样，先看看分支语句的流程图，如图4.9所示。

如果仍然使用条件判断语句，整个程序段会显得层次过多，程序显得过于复杂，不易阅读。

图　4.9　分支语句流程图

【实例4.19】下面通过实际程序段，来了解条件判断语句和分支语句的区别所在。

01 public class control16

02 {

03 public static void main（String[]args）

04 {

05 int i=8；

06 if（i==1）

07 {System.out.println（"是一月份"）；}

08 if（i==2）

09 {System.out.println（"是二月份"）；}

10 if（i==3）

11 {System.out.println（"是三月份"）；}

12 if（i==4）

13 {System.out.println（"是四月份"）；}

14 if（i==5）

15 {System.out.println（"是五月份"）；}

16 if（i==6）

17 {System.out.println（"是六月份"）；}

18 if（i==7）

19 {System.out.println（"是七月份"）；}

20 if（i==8）

21 {System.out.println（"是八月份"）；}

22 if（i==9）

23 {System.out.println（"是九月份"）；}

24 if（i==10）

25 {System.out.println（"是十月份"）；}

26 if（i==11）

27 {System.out.println（"是十一月份"）；}

28 if（i==12）

29 {System.out.println（"是十二月份"）；}

30 }

31 }

【代码说明】第6～29行是12个条件语句if。这是判断月份，如果要判断的条件更多，是不是需要写更多的if语句呢？这说明判断条件非常多时，使用if语句显得层次有些混乱。

【运行效果】

是八月份

这个程序段看得不是很舒服，并且有点杂乱，下面再看看使用分支语句编写的程序段是什么样子。先来绘制一下流程图，如图4.10所示。

图　4.10　输出一年月份程序流程图

【实例4.20】为了便于浏览，以上的图只绘制了1月份到6月份的流程。下面是这个程序的具体代码。

01 public class control17

02 {

03 public static void main（String[]args）

04 {

05 int i=8；

06 switch（i）

07 {

08 case 1：System.out.println（"是一月份"）；break；

09 case 2：System.out.println（"是二月份"）；break；

10 case 3：System.out.println（"是三月份"）；break；

11 case 4：System.out.println（"是四月份"）；break；

12 case 5：System.out.println（"是五月份"）；break；

13 case 6：System.out.println（"是六月份"）；break；

14 case 7：System.out.println（"是七月份"）；break；

15 case 8：System.out.println（"是八月份"）；break；

16 case 9：System.out.println（"是九月份"）；break；

17 case 10：System.out.println（"是十月份"）；break；

18 case 11：System.out.println（"是十一月份"）；break；

19 case 12：System.out.println（"是十二月份"）；break；

20 default：System.out.println（"fault"）；

21 }

22 }

23 }

【代码说明】第6～21行是完整的分支语句，每个条件通过case来设置，条件执行完后，通过break语句来中断。第20行的default表示上述条件都不满足时，则执行它设置的输出。

【运行效果】

是八月份

说明　通过观察上面的程序段，会发现使用分支语句，整个程序显得更容易阅读。

4.6　返回语句

返回语句就是在执行程序的过程中，跳转到另一个程序。一般返回语句用在子程序或程序的函数方法中。返回语句使用关键字“return”来表示。下面通过一个简单实例，了解返回语句的用法。

01 public void set（int a, int b）

02 {

03 i=a*b

04 return；

05 }

以上是一个小程序段，在程序中出现了return关键字，说明这个小程序结束了，返回到主运行程序中去。还有一种情况，先看看下面的例子。

01 public int set（）

02 {

03 return i=a*b

04 }

这种情况不但要返回到主运行程序，而且还要将“i=a*b”的值带回到主运行程序中，将其值赋给主运行程序中的“i”。返回语句牵扯到不同的方法之间调用的问题，我们会在讲解了类的方法之后再详细了解其应用环境。

4.7　常见疑难解答

4.7.1　普通循环是使用for语句还是while语句

答：根据情况不同而定，for循环语句主要针对有限循环而言，也就是说当循环有上限的时候，一般使用for循环。while循环语句则针对那些无限循环的代码而言，当循环没有明确上限，上限只是根据程序中的条件而定。

4.7.2　一般的程序可否用分支语句来代替条件语句

答：这个要视具体情况而定，如果条件在三重之内，最好使用条件语句。如果超过了三重，最好使用分支语句。

4.8　小结

本章讲解的是Java程序设计中的流程控制，这是每种开发语言的基础，如果你学习过C语言，则本章内容并不复杂。流程控制主要通过条件判断、循环、中断、执行等一系列语句来完成，每个语句实现的功能不同，或者说技巧也不同。建议读者对本章中的每个例子都能亲自动手实现，如果出现了问题，还要多注意如何解决问题，这样就能知道一些更多的知识和技巧。

4.9　习题

1.用来判断如果条件的关键字是______。

2.withdraw=remain＞=10000?5000：500，在这段代码中，如果remain为1100，那么withdraw会是多少？

3.switch语句可用哪些数据类型做参数？

4.以下代码的执行结果会是什么？为什么？

int signal=1；

switch（signal）{

case 1：

System.out.println（"红灯"）；

case 2：

System.out.println（"绿灯"）；

case 3：

System.out.println（"黄灯"）；

break；

default：

System.out.println（"默认开绿灯"）；

break；

}

第5章　数组

什么是数组？数组在实际程序中起到什么作用？数组用来存储数据，类似数据的缓存，是一组有序列的数据集合。通过本章的学习，可以了解数组如何进行数据存储，并且结合编程实例，掌握数组的设计和操作。

本章重点：

❑数组的定义。

❑一维数组和二维数组。

❑数组的应用实例。

5.1　数组概念的引入

本节将介绍数组的一些基本概念。这些概念有助于在以后的编程过程中，更好地使用数组。

5.1.1　实例的引入

走进一家运动器材店，会看到很多体育运动器材，有篮球、排球、足球、羽毛球、乒乓球、高尔夫、滑板、健身器材等。如果要为这家店做一个数据库系统，首先要建立一个类似于集合的表格，如下所示。

{篮球，排球，足球，羽毛球，乒乓球，高尔夫，滑板，健身器材}

在程序开发中，将这种集合形式经过改装，变成了本章要重点讲述的数组，将上述的例子用数组来表示：

运动器材{篮球，排球，足球，羽毛球，乒乓球，高尔夫，滑板，健身器材}

5.1.2　数组的概念

数组是具有相同数据类型的数据的集合，例如上一小节中提到的运动器材集合。相同的数据类型，意味着数组中每个数据都是同一类型数据，或者属于基本数据类型中相同类型的数据，或者属于对象类型中相同类型的数据。在生活中，一个班级的学生、一个学校的所有人、一个汽车厂的所有汽车等，这些都可以形成一个数组。

数组如果按照维数来分，分为一维数组、二维数组、三维数组和多维数组等，每一维代表一个空间的数据。一维数组代表的就是一维空间的数据，例如自然数1～10。

{1，2，3，4，5，6，7，8，9，10}

二维数组代表的就是二维空间的数据，例如在数学中的坐标。

{（1，2），（3，4），（5，6），（7，8）}

这里的每一组数据都代表了二维空间中x和y的坐标值。

三位数组代表的就是三维空间的数据，所谓三维空间就是指立体空间，例如立体坐标。

{（1，2，3），（2，3，4），（3，4，5），（4，5，6），（5，6，7）}

这里的每一组数据都代表了三维空间中的（x, y，z）轴的坐标值。

5.1.3　用实例说明数组的用处

本节重点是说明数组的优点，这里可能会遇到后面小节讲述的内容，先不要理会。下面来看一个有关数组的简单实例。

一个班有10个同学，分别是王垒、赵敏、宋江、刘户、孙洁、王浩、周杰、钱平、朱汉、马超。前面5名同学是男生，后面5名同学是女生。下面分析如何用数组来表示。

这个实例用数组来表示的方式有很多种，可以用一维数组来表示，也可以用二维数组来表示，还可以用三维数组来表示。下面先使用一维数组来表示。

某个班级的同学〔10〕{王垒，赵敏，宋江，刘户，孙洁，王浩，周杰，钱平，朱汉，马超}

“某个班级的同学”是这些同学的共同点，在程序中可以称之为相同的数据类型，中括号中的数组代表的是共有几个相同数据类型的数据，而大括号内的数据就是要使用的数据。

如果使用二维数组来表示，请看下面示例。

某个班级的同学〔10〕{（王垒，男），（赵敏，男），（宋江，男），（刘户，男），（孙洁，男），（王浩，女），（周杰，女），（钱平，女），（朱汉，女），（马超，女）}

此时在二维数组中，将　性别和姓名作为二维数组的一个数据元素。如果使用三维数组来表示，请看下面示例。

某个班级的同学〔〕｛（王垒，男，21），（赵敏，男，21），（宋江，男，21），（刘户，男，21），（孙洁，男，21），（王浩，女，21），（周杰，女，21），（钱平，女，21），（朱汉，女，21），（马超，女，21）｝

此时在三维数组中，将姓名、　性别和　年龄作为三维数组中的一个数据元素。

5.2　基本数据类型的数组

上面一节中使用实例对数组的用处作了分析，此节将针对基本数据类型的数组，讲述其声明方法和使用方法。本节将会利用大量编程实例，来加强对数组用法的理解。

5.2.1　基本类型数组的声明

使用一个数据时，必须要对其进行声明，这个道理对于数组来说也一样，数组在使用之前也必须先声明。先看下面的代码，是如何声明一个变量的。

int a；

仔细分析一下：int是指变量的数据类型，a是指变量名，由变量的声明可以联系到数组的声明。

int a[]；

仔细分析一下：int是指数组中所有数据的数据类型，也可以说是这个数组的数据类型，a[]表示数组名。

基本类型数组的声明有几种形式：

int a[]；

int[]a；

说明　这两种形式没有区别，使用效果完全一样，读者可根据自己的编程习惯选择。

5.2.2　基本类型数组的初始化

如何对基本类型的数组进行初始化呢？同样，可以先从变量的初始化开始。一个变量的初始化工作，其实就是一个变量的赋值工作，例如下面的变量初始化实例。

int a=3；

以上就是一个变量初始化的例子，那么数组的初始化是什么样子？下面看一个有关数组初始化的实例。

int[]a=new int{1，2，3，4，5}；

要用关键字“new”，是因为数组本来就是一个对象类型的数据。

数组的长度其实就是指数组中有几个数据，举个数组长度的例子。

int[]a={1，2，3，4，5}；这个数组的长度就是里面有几个数据，这个数组里有5个数据，说明这个数组长度是5；

在编写程序的过程中，如果要引用数组的长度，一般是使用变量“length”，在程序中一般是使用下列格式：

数组名.length

介绍了以上那么多内容，下面先看一个基本类型数组的例子。

【实例5.1】创建一个拥有10个元素的整数型数组a，并通过a[i]=i*i为每个数组元素赋值，最后将结果输出。

01 ///对a这个数组赋值

02 ///将数组a中的所有元素输出

03 public class arrary1

04 {

05 public static void main（String[]args）

06 {

07 int[]a；

08 a=new int[10]；

09 int i；

10 for（i=0；i＜10；i++）

11 {

12 System.out.println（"a["+i+"]="+（i*i））；

13 }

14 }

15 }

【代码说明】程序先做了个数组声明“int[]a”，然后，创建了一个数组对象“a=new int[10]”，最后使用循环语句输出数组中所有数据。

【运行效果】

a[0]=0

a[1]=1

a[2]=4

a[3]=9

a[4]=16

a[5]=25

a[6]=36

a[7]=49

a[8]=64

a[9]=81

说明

数组本身是对象类型数据。基本类型的数组，是指这个数组中数据的数据类型，与数组是否是对象类型数据毫无关系。

5.3　由实例引出不同数组种类及其使用

本节主要使用实例让读者能更加熟悉数组的使用。通过本节的学习，总结一些编程中所使用的编程思路。编程最重要的不是如何编写代码，使用哪种控制流程，最主要的是编程的思路，编程思路决定着这个程序代码的好与坏。

5.3.1　由实例引出一维数组及其使用

【实例5.2】先来看一个很简单的实例。有两个数组a[]、b[]，输出它们中的各个数据，并且输出它们的长度。

01 ///初始化两个数组a、b

02 ///使用循环语句将两个数组内元素输出

03 ///并且使用length属性输出数组的长度

04 public class arrary2

05 {

06 public static void main（String[]args）

07 {

08 int[]a=new int[]{1，2，3，4，5}；

09 int[]b=new int[]{2，3，4，5，6，7，8}；

10 for（int i=0；i＜a.length；i++）

11 {

12 System.out.println（"a["+i+"]="+a[i]）；

13 }

14 for（int j=0；j＜b.length；j++）

15 {

16 System.out.println（"b["+j+"]="+b[j]）；

17 }

18 System.out.println（"a数组的长度是："+a.length）；

19 System.out.println（"b数组的长度是："+b.length）；

20 }

21 }

【代码说明】第8～9行初始化两个数组a和b。第10～13行for循环的次数取决于数组a的长度。第14～17行的循环次数同样取决于数组b的长度。

【运行效果】

a[0]=1

a[1]=2

a[2]=3

a[3]=4

a[4]=5

b[0]=2

b[1]=3

b[2]=4

b[3]=5

b[4]=6

b[5]=7

b[6]=8

a数组的长度是：5

b数组的长度是：7

这个程序段主要是将两个数组中的每个数据和整个数组的长度输出，而这个例子也是数组最简单的实例，下面再看一个稍微复杂的实例。

【实例5.3】有两个数组a[]、b[]。将两个数组中数据一一对应相乘，得出数组c，输出数组c的元素和3个数组的长度。

01 ///初始化两个数组a、b

02 ///将两个数组中对应的元素相乘得出第三个数组的元素

03 ///输出三个数组的长度

04 public class arrary3

05 {

06 public static void main（String[]args）

07 {

08 int[]a=new int[]{1，2，3，4，5}；

09 int[]b=new int[]{2，3，4，5，6}；

10 int[]c=new int[5]；

11 for（int i=0；i＜a.length；i++）

12 {

13 c[i]=a[i]*b[i]；

14 }

15 for（int j=0；j＜a.length；j++）

16 {

17 System.out.println（"c["+j+"]="+（a[j]*b[j]））；

18 }

19 System.out.println（"a数组的长度是："+a.length）；

20 System.out.println（"b数组的长度是："+b.length）；

21 System.out.println（"c数组的长度是："+c.length）；

22 }

23 }

【代码说明】第10行首先声明一个没有初始数值的数组c，第11～14行通过循环为数组c赋值，然后第15～18行通过循环输出数组c中的元素，第19～21行输出3个数组的长度。

【运行效果】

c[0]=2

c[1]=6

c[2]=12

c[3]=20

c[4]=30

a数组的长度是：5

b数组的长度是：5

c数组的长度是：5

通过这段程序可以学习如何操作数组内各个元素，下面再看看更加复杂的程序段。

【实例5.4】创建一个整数型数组f，它拥有20个元素，并将它的各个元素赋值如下，然后输出它们。其函数关系如下所示：

f[0]=1；f[1]=2；f[i]=f[i-1]+f[i-2]；

实例的详细代码如下所示。

01 ///条件中的是一个递归函数

02 ///通过一个循环语句将数组中所有的元素输出

03 public class arrary4

04 {

05 public static void main（String[]args）

06 {

07 int[]f=new int[20]；

08 f[0]=0；

09 f[1]=2；

10 for（int i=2；i＜f.length；i++）

11 {

12 f[i]=f[i-1]+f[i-2]；

13 System.out.println（"f["+i+"]="+f[i]）；

14 }

15 }

16 }

【代码说明】这个程序段是数学中的一个递归函数，其中的运算过程交给了第10～14行的循环语句去处理。

【运行效果】

f[2]=2

f[3]=4

f[4]=6

f[5]=10

f[6]=16

f[7]=26

f[8]=42

f[9]=68

f[10]=110

f[11]=178

f[12]=288

f[13]=466

f[14]=754

f[15]=1220

f[16]=1974

f[17]=3194

f[18]=5168

f[19]=8362

上面所有的程序段，都是围绕着如何操作一维数组内的元素，其实一维数组在实际程序中的一些应用，不过是操作数组内部元素而已。

5.3.2　由实例引出二维数组及其使用

【实例5.5】针对二维数组，先看看下面的有关二维数组的例子：创建一个字符型二维数组，并根据执行结果为各元素赋值，然后输出各元素。

01 ///使用两个数组分别代表空间中的x和y轴

02 ///通过循环语句将对应的每一个坐标上的元素输出

03 public class arrary5

04 {

05 public static void main（String[]args）

06 {

07 char[][]a；

08 a=new char[4][10]；

09 a[0]=new char[10]；

10 a[1]=new char[10]；

11 a[2]=new char[10]；

12 a[3]=new char[10]；

13 a[0][0]=65；

14 a[1][0]=67；

15 a[2][0]=69；

16 a[3][0]=71；

17 for（int i=0；i＜a.length；i++）

18 {

19 for（int j=1；j＜a[i].length；j++）

20 {

21 a[i][j]=（char）（a[i][j-1]+1）；

22 System.out.print（a[i][j-1]）；

23 }

24 System.out.println（）；

25 }

26 }

27 }

【代码说明】第13～16行首先为二维中第一列的数据赋值，这样就可以通过第19～23行的循环，通过一次加1的方式，输出9个字母。第24行的代码非常关键，起到输出结果换行的作用。

注意

读者从程序中可以看出二维数据其实每行有10个数据，但我们只输出了9个，读者可通过更改第19～23行的代码来实现10个数据的输出。

【运行效果】

ABCDEFGHI

CDEFGHIJK

EFGHIJKLM

GHIJKLMNO

上面的程序段只是操作二维数组中的元素而已，所以只要牢牢地记住数组的基本概念，其他的问题就可以迎刃而解。

5.4　数组的综合实例

【实例5.6】设计一个程序，有两个整型数组：a[]和b[]。a数组中有5个元素，b数组中有5个元素。现在要求：

（1）分别输出两个数组中的各个元素及其长度。

（2）有一个数组c，它的元素就是a数组和b数组中一一对应的元素的乘积，并且输出其长度。

（3）有一个数组d，它的元素是前面三个数组中一一对应的元素满足的表达式：a[i]*c[i]-b[i]。

分析与编写：

要输出各个元素及长度。这个程序在前面也遇到过，具体程序段如下所示。

01 public class arrary6

02 {

03 public static void main（String[]args）

04 {

05 int[]a=new int[]{2，4，6，8，10}；

06 for（int i=0；i＜a.length；i++）

07 {

08 System.out.println（"a["+i+"]="+a[i]）；

09 }

10 System.out.println（"数组a的长度是："+a.length）；

11 }

12 }

【代码说明】第5行初始化数组，第6～9行通过循环输出数组中的元素。

【运行效果】

a[0]=2；

a[1]=4

a[2]=6

a[3]=8

a[4]=10；

数组a的长度是：5

输出另一个数组的程序代码如下。

01 public class arrary7

02 {

03 public static void main（String[]args）

04 {

05 int[]b=new int[]{1，3，5，7，9}；

06 for（int i=0；i＜b.length；i++）

07 {

08 System.out.println（"b["+i+"]="+b[i]）；

09 }

10 System.out.println（"数组b的长度是："+b.length）；

11 }

12 }

【代码说明】第5行初始化数组b，第6～9行输出数组中各个元素。【运行效果】

b[0]=1

b[1]=3

b[2]=5

b[3]=7

b[4]=9

数组b的长度是：5

在第二个要求里，必须要创建一个新的数组c，接着就是操作前两个数组的元素。这个要求内的程序段在前面的小节内已经详细举过例子，下面看程序段。

01 public class arrary8

02 {

03 public static void main（String[]args）

04 {

05 int[]a=new int[]{2，4，6，8，10}；

06 int[]b=new int[]{1，3，5，7，9}；

07 int[]c=new int[5]；

08 for（int i=0；i＜a.length；i++）

09 {

10 c[i]=a[i]*b[i]；

11 }

12 for（int j=0；j＜a.length；j++）

13 {

14 System.out.println（"c["+j+"]="+（a[j]*b[j]））；

15 }

16 System.out.println（"数组c的长度是："+c.length）；

17 }

18 }

【代码说明】

第5～7行创建了3个数组，其中数组a和b创建时有默认值，数组c没有默认值。

第8～11行设置数组c的值，然后第12～15行输出数组c。

【运行效果】

c[0]=2；

c[1]=12

c[2]=30

c[3]=56

c[4]=90；

数组c的长度是：5

这个程序是一个数组内部各个对应的元素的操作，这在前面已经举过例子，此处不再详细描述。

第三个要求比起前两个，稍微复杂了点，但是可以通过分析，来编写程序段。先看下面的程序，然后再来分析。

01 public class arrary9

02 {

03 public static void main（String[]args）

04 {

05 int[]a=new int[]{2，4，6，8，10}；

06 int[]b=new int[]{1，3，5，7，9}；

07 for（int i=0；i＜b.length；i++）

08 {

09 System.out.println（"d["+i+"]="+（a[i]*a[i]*b[i]-b[i]））；

10 }

11 }

12 }

【代码说明】读者在代码中没有看到数组c，因为本身数组c就是数组a和数组b中元素相乘的结果，所以第9行代码中用a[i]*b[i]代替数组c。

【运行效果】

d[0]=3

d[1]=45

d[2]=175

d[3]=441

d[4]=891

整个程序段其实与上一个程序段相似，只不过表达式比上一例稍微复杂。

上面三个程序段其实可以用一个程序段来表示，这就涉及面向对象编程的理念。代码如下所示。

01 public class arrary10

02 {

03 int[]a=new int[]{2，4，6，8，10}；

04 int[]b=new int[]{1，3，5，7，9}；

05 public static void main（String[]args）

06 {

07 arrary10 w=new arrary10（）；

08 w.print1（）；

09 w.print2（）；

10 w.print3（）；

11 w.print4（）；

12 }

13 void print1（）

14 {

15 for（int i=0；i＜b.length；i++）

16 {

17 System.out.println（"b["+i+"]="+b[i]）；

18 }

19 System.out.println（"数组b的长度是："+b.length）；

20 }

21 void print2（）

22 {

23 for（int i=0；i＜a.length；i++）

24 {

25 System.out.println（"a["+i+"]="+a[i]）；

26 }

27 System.out.println（"数组a的长度是："+a.length）；

28 }

29 void print3（）

30 {

31 for（int i=0；i＜b.length；i++）

32 {

33 System.out.println（"c["+i+"]="+（b[i]*a[i]））；

34 }

35 System.out.println（"数组c的长度是："+b.length）；//因为数组c和b相同，可以用b.length

36 }

37 void print4（）

38 {

39 for（int i=0；i＜b.length；i++）

40 {

41 System.out.println（"d["+i+"]="+（a[i]*a[i]*b[i]-b[i]））；

42 }

43 }

44 }

【代码说明】第35行在获取数组c的长度时，使用了b.length，因为数组c和数组b其实一样长，这里简化了代码，直接输出数组b的长度，同样也是数组c的长度。

【运行效果】

b[0]=1

b[1]=3

b[2]=5

b[3]=7

b[4]=9

数组b的长度是：5

a[0]=2

a[1]=4

a[2]=6

a[3]=8

a[4]=10

数组a的长度是：5

c[0]=2

c[1]=12

c[2]=30

c[3]=56

c[4]=90

数组c的长度是：5

d[0]=3

d[1]=45

d[2]=175

d[3]=441

d[4]=891

每个功能使用一个方法来实现，在主运行函数内，利用创建新对象，再使用对象的方法来引用功能函数。这样，整个程序看起来就很清晰明了，并且功能模块很独立。不会因为修改一处而导致全部代码修改，这也正是面向对象程序开发的一个最大优势。

在编写有关数组方面的程序时，主要是操作数组中的元素。而在实际的程序开发中，不可能像前面举的例子一样简单。在实际开发工作中，要涉及的不单纯是基本类型的数组，绝大多数会遇到数组中元素的数据类型是对象类型。至于对象类型的使用，其实同基本类型的数组一样，只不过是数据类型不同而已，对象数组将会在后面的章节中讲解。

5.5　常见疑难解答

5.5.1　声明数组需要注意什么

答：声明数组时，一定要考虑数组的最大容量，防止容量不够的现象。数组一旦被声明，它的容量就固定了，不容改变。如果想在运行程序时改变容量，就需要用到数组列表。数组列表不属于本章的内容，在数据结构一部分会详细讲述。

5.5.2　数组在平时的程序代码中使用是否频繁

答：其实数组有一个缺点，就是一旦声明，就不能改变容量，这个也是其使用频率不高的原因。一般存储数据会使用数组列表或Vector这两种数据结构来存储数据。

5.6　小结

本章讲解了数组的使用，其中实例讲解最多的是一维数组的使用，包括数组的声明、数组的初始化、数组中各元素如何输出等。希望读者通过本章的学习，深入了解数组的基本概念和基本使用，这里不需要读者有多精通，而是起到一个入门的作用，为后面的复杂数据使用打下良好的基础。

5.7　习题

1.以下声明数组的方式哪种不对？

A. int a[]；

B. int[]a；

C. int[]a；

D. int[]a=new int{1，2，3，4，5}；

2.int[]a={1，2，3，4，5}；中被定义的数组a的长度是多少？

3.说明数组应用有什么好处。

第6章　字符串的处理

本章将通过实例，重点讲述字符串类和字符串类中各种各样的方法。字符串属于类，虽然在本章之前还未讲解类，但本章的关键主要学习如何处理字符串。字符串是在程序开发中随时随地都能用到的对象型数据，处理好字符串数据，对于程序开发来说，是至关重要的。在程序不同的角落都会存在字符串处理的身影，例如，登录窗口的用户名和密码等。

本章重点：

❑理解字符串和它的用处。

❑掌握缓冲字符串类的使用。

❑字符串类的各种处理方法。

6.1　字符串的基本概念

提到字符串，读者一定不会陌生。在前面章节中，很多实例程序中都有字符串。

【实例6.1】先来看看下面的这段程序代码段。

01 ///这是一个简单的输出程序代码

02 public class str1

03 {

04 public static void main（String[]args）

05 {

06 System.out.println（"是一个优秀的程序员"）；

07 }

08 }

【代码说明】第6行在两个双引号之间的数据就是字符串。

【运行效果】

是一个优秀的程序员

下面要讨论为什么要使用字符串，其实字符串的真正作用就是处理文本。

Java中的类库相当丰富，对于Java语言要处理的数据，都会在类库中有相应的类。程序员可以通过类中的方法、对象和属性来处理相应的数据。这就给开发带来了巨大的方便，也减轻了开发的难度。

在Java语言中，处理文本主要应用的类是“String”类和“StringBuffer”类。如果是处理一些小的文本，建议使用“String”类，它会特别方便。如果使用String类来处理大型文本，会很消耗系统资源，所以Java语言特别引进了“StringBuffer”类。

注意　这两个类都是处理文本的，但是它们之间又有很大的差异，本章的后面会详细介绍这些差异。

【实例6.2】下面演示如何在程序中处理字符串数据。

01 ///通过初始化一个字符串str

02 ///通过System.out.println方法将这个字符串输出

03 public class str2

04 {

05 public static void main（String[]args）

06 {

07 String str；

08 str="I am a student, I am Chinese"；

09 System.out.println（str）；

10 }

11 }

【代码说明】第7行定义字符串对象，第8行为其赋值，第9行输出字符串。

注意　第7行定义字符串对象时，String的首字母是大写的。

【运行效果】

I am a student, I am Chinese

字符串是对象型的数据。由于类和对象的章节在第二篇才会讲述，所以本章中，只要遇到有关类和对象的概念，只是讲述如何操作，暂时不会描述为什么要这样使用。

6.2　字符串处理的类库种类

字符串处理类使用最多的有两种：“String”类和“StringBuffer”类。下面将详细讲述这两个类如何使用。

6.2.1　字符串的赋值

字符串类是处理字符串的类。“String”字符串与上一章学过的数组有一个共同点。就是它们被初始化后，长度是不变的，并且内容也不变。如果要改变它的值，就会产生一个新的字符串，如下所示：

String str1="very"；

str1=str1+"good"；

这个赋值表达式看起来有点像简单的接龙。在“str1”后面直接加上一个“good”字符串，形成最后的字符串“very good”。其运行原理是这样的：程序首先产生了“str1”字符串对象，并在内存中申请了一段空间。此时要追加新的字符串是不可能的，因为字符串被初始化后，长度是固定的。如果要改变它，只有放弃原来的空间，重新申请能够容纳“very”和“good”两个字符串的内存空间，然后将“very good”字符串放到内存中。

6.2.2　字符串的处理类—String

字符串的声明非常简单，具体声明方法如下所示：

字符串类型字符串名=字符串内容

例如：String str=“we are chinese”。

在后面类和对象中，会提到一个构造器的概念。构造器就是从类中构造出对象的方法。字符串类一共有9种不同的构造器，下面将详细讲述其中的6种。

1.字符串类的默认构造器

“String（）”这个构造器是最简单的构造器，也是系统默认的构造器，是不带参数的。

【实例6.3】下面通过实例来学习如何使用它。

01 ///创建一个空字符串str

02 ///因为是空字符串，所以输出也是没有内容的

03 public class str3

04 {

05 public static void main（String[]args）

06 {

07 String str=new String（）；

08 System.out.println（str）；

09 }

10 }

【代码说明】第7行只初始化了一个字符串对象str，并没有为其赋值。

【运行效果】这个程序输出的结果为空，因为这个构造器构造出的对象是个空对象。

2.字节参数的构造器

【实例6.4】“String（byte[]byte）”将字节数组中的元素作为字符串对象。请看下面的例子，再来体会这个构造器的作用。

01 ///初始化一个字节数组b

02 ///通过构造器，将字节数组中的元素连接成一个字符串

03 ///将此字符串输出

04 public class str4

05 {

06 public static void main（String[]args）

07 {

08 byte[]b={97，98，99}；

09 String str=new String（b）；

10 System.out.println（str）；

11 }

12 }

【代码说明】这个构造器的作用是将字节数组中的元素以字符串对象的形式输出。

【运行效果】输出结果并不是数值97的字符串“97”，而是其ASCII码代表的字符a。读者可自己动手实验，结果如下所示。

abc

3.获取指定字节数的构造器

“String（byte[]bytes, int offset, int length）”这个构造器的含义是：将字节数组中，从“offset”指定的位置开始，到“length”长度结束，中间的字符构成字符串对象。

【实例6.5】下面看一个简单的例子。

01 ///初始化一个字节数组b

02 ///通过构造器，将字节数组中的元素连接成一个字符串，并且从第4个位置开始，总共有2个元

03 ///素

04 ///将此字符串输出

05 public class str5

06 {

07 public static void main（String[]args）

08 {

09 byte[]b={97，98，99，100，101，102}；

10 String str=new String（b，3，2）；

11 System.out.println（str）；

12 }

}

【代码说明】在“String”对象中，第一个参数“b”，就是指开始初始化的字符数组。第二个参数是3，就是指定从第4个位置开始，因为数组是从零开始的，所以3是指第4个位置即“4”。第三个参数是2，指定从这个位置开始后几个字符，因为是2，所以是从“4”开始的两个字符。

【运行效果】

de

4.将字节型数据转换为字符集输出的构造器

“String（byte[]bytes, int offset, int length, String charsetName）”这个构造器的含义就是：将一个字节数组中从第“offset”个位置的字符开始，“length”长度的字符结束，中间的字符形成了字符串对象，然后将这个字符串按照某种字符集样式输出。

字符集一般有“us-ascii”、“iso-8859-I”、“utf-8”、“utf-16be”、“utf-16le”、“utf-16”等样式。

【实例6.6】下面先看看这种构造器的实例。

01 import java.io.*；

02 ///初始化一个字节数组b

03 ///通过构造器，将字节数组中的元素连接成一个字符串，并且从第4个位置开始，总共有2个元素

04 05///将此字符串以UTF-16的形式输出

06 public class str6

07 {

08 public static void main（String[]args）

09 {

10 byte[]b={97，98，99，100，101，102}；

11 try

12 {

13 String str=new String（b，3，2，"utf-8"）；

14 System.out.println（str）；

15 }

16 catch（UnsupportedEncodingException ex）{}

17 18

}

19 }

【代码说明】其实这个构造器的用法与上一个构造器差不多，唯独不同的就是输出结果的形式不一样。“String（byte[]bytes, String charsetName）”与上一个构造器是同类的构造器，此构造器是将整个字节数组作为字符串对象，并以某个字符集形式输出。

【运行效果】

de

5.字符数组的构造器

“String（char[]value）”构造一个字符数组，其实这个构造器与第二个构造器很相似，它是将字符数组内的字符连在一起，形成一个字符串。

【实例6.7】下面是这个构造器的实例。

01 ///初始化一个字符数组c

02 ///通过构造器，将字符数组中的元素连接成一个字符串

03 ///将此字符串输出

04 public class str7

05 {

06 public static void main（String[]args）

07 {

08 char[]c={'w'，'e'，'l'，'c'，'o'，'m'，'e'}；

09 String str=new String（c）；

10 System.out.println（str）；

11 }

12 }

【代码说明】这个程序段要做的事情，就是将字符数组中所有的元素连起来形成一个字符串。【运行效果】

welcome

6.截取部分字符串数组内容的构造器

“String（char[]value, int offset, int count）”这个构造器的含义是：将字符数组中从“offset”指定的位置开始，“count”指定的数目结束，中间的字符连成字符串。

【实例6.8】下面是这个构造器的实例。

01 ///初始化一个字符数组c

02 ///通过构造器，将字符数组中的元素连接成一个字符串，并从第4个位置开始，总共有4个元素

03 ///将此字符串输出

04 public class str8

05 {

06 public static void main（String[]args）

07 {

08 char[]c={'w'，'e'，'l'，'c'，'o'，'m'，'e'}；

09 String str=new String（c，3，4）；

10 System.out.println（str）；

11 }

12 }

【代码说明】第9行是截取字符串的意思，从第三个位置开始，因为数组位置从0开始，所以这里的3，其实就是从第四个字符c开始，截取4位。

【运行效果】

come

读者可以根据上面的构造器，自己编写一些代码来熟悉它们。

6.2.3　字符串处理的方法

字符串类拥有很多针对字符串操作的方法。在这里主要讲述串连接、提取子串、从字符串中分解字符、得到字符串的长度、测试字符串是否相等、查找特定字符串、从基本类型转换成字符串等。

1.串连接

在Java语言中，有两种串连接的方法：一种是使用“+”，另一种是使用方法函数“concat（String str）”。

【实例6.9】下面分别来举例说明。

01 ///通过加号将两个字符串str1和str2连在一起

02 public class str9

03 {

04 public static void main（String[]args）

05 {

06 String str1="you"；

07 String str2="welcome"；

08 System.out.println（str1+""+str2）；

09 }

10 }

【代码说明】第8行通过“+”将str1和str2两个字符串连接在一起。【运行效果】

you welcome

【实例6.10】上面实例使用了“+”号运算符来连接两个字符串，形成新的字符串。下面通过concat方法将两个字符串连接起来。

01 ///使用concat方法将两个字符串str1和str2连在一起

02 public class str10

03 {

04 public static void main（String[]args）

05 {

06 String str1="you"；

07 String str2="welcome"；

08 System.out.println（str1.concat（str2））；

09 }

10 }

【代码说明】第8行使用了concat（）方法，str1先输出，然后是str2。

【运行效果】

you welcome

这个实例使用了“concat”方法来连接两个字符串，形成新的字符串。无论用哪种方法将两个字符串连接起来，都同样形成新的字符串。

【实例6.11】下面将演示一个稍微复杂点的程序段。

01 ///初始化两个字符数组c和c1

02 ///通过构造器构造一个从字符数组c第1个元素开始，共4个元素组成的字符串str1

03 ///通过构造器构造一个从字符数组c1第2个元素开始，共1个元素组成的字符串str2

04 ///通过concat方法将两个字符串str2和str1连在一起

05 public class str11

06 {

07 public static void main（String[]args）

08 {

09 char[]c={'c'，'h'，'i'，'n'，'e'，'s'，'e'}；

10 char[]c1={'h'，'a'，'n'，'d'，'l'，'e'}；

11 String str1=new String（c，0，4）；

12 String str2=new String（c1，1，1）；

13 System.out.println（str1.concat（str2））；

14 }

15 }

【代码说明】第9～10行定义了两个字符数组。第11～12行通过构造器构造两个字符串对象。第13行使用concat（）方法连接两个字符串。

【运行效果】

china

分析以上的程序段，首先利用构造器，构造了两个将字符数组连在一起的字符串。再在这两个字符串中提取新的字符串，最后再将两个字符串合并成新字符串。仔细分析这个程序，发现这个程序段是几个知识点的组合。所以读者编程时，一定将每个知识点弄清楚，这样编写代码就不再是件难事。

2.提取子字符串

有时一个很长的字符串，其中只有一小部分是需要的，于是Java语言类库中，就提供了相应的获取局部字符串的方法。这些方法是：“substring（int beginIndex, int endIndex）”或“substring（int index）”。下面将详细地讲述这两个方法的意义和使用方法。

❑“substring（int index）”是指提取从index指定的位置开始，一直到字符串的最后。

❑“substring（int beginIndex, int endIndex）”是指提取由“beginIndex”位置开始，到以“endIndex”为结束位置的字符串。

【实例6.12】下面将举个如何提取子字符串的实例。

01 ///初始化一个字符串str

02 ///提取从字符串的第3个元素开始到第10个元素位置的字符串，并且将其输出

03 public class str12

04 {

05 public static void main（String[]args）

06 {

07 String str="we are students and he is worker"；

08 System.out.println（str.substring（2，10））；

09 }

10 }

【代码说明】第7行定义了一个字符串对象str，第8行使用substring（2，10）截取字符串的一部分。

【运行效果】

are stu

【实例6.13】下面再举个如何提取子字符串的例子。

01 ///初始化一个字符串str

02 ///提取从字符串的第4个元素开始到结束位置的字符串，并且将其输出

03 public class str13

04 {

05 public static void main（String[]args）

06 {

07 String str="we are students and he is worker"；

08 System.out.println（str.substring（3））；

09 }

10 }

【代码说明】第8行使用了substring（3），表示从第4个元素开始截取字符串。【运行效果】

are students and he is worker

【实例6.14】其实提取子字符串的方法很简单，下面将看一个综合的例子。

01 ///初始化一个字符串str

02 ///通过循环语句，提取从字符串的第1个元素开始到结束位置的字符串，一直到从最后一个元素

03 ///到最后的字符串，并且将其输出

04 public class str14

05 {

06 public static void main（String[]args）

07 {

08 String str="we are students and he is a worker"；

09 for（int i=0；i＜34；i++）

10 {

11 System.out.println（str.substring（i））；

12 }

13 }

14 }

【代码说明】这个程序以循环语句输出从第一个位置到最后一个位置的子字符串。这个程序段的运行结果看起来很复杂，但这个程序原理非常简单。

【运行效果】

we are students and he is a worker

e are students and he is a worker

are students and he is a worker

are students and he is a worker

re students and he is a worker

e students and he is a worker

students and he is a worker

students and he is a worker

tudents and he is a worker

udents and he is a worker

dents and he is a worker

ents and he is a worker

nts and he is a worker

ts and he is a worker

s and he is a worker

and he is a worker

and he is a worker

nd he is a worker

d he is a worker

he is a worker

he is a worker

e is a worker

is a worker

is a worker

s a worker

a worker

a worker

worker

worker

orker

rker

ker

er

r

3.从字符串中分解字符

上面的方法是从字符串中提取子字符串，而将要讲述的是从字符串中提取一个指定的字符。从字符串中分解字符的方法是：“charAt（int index）”，这个方法返回的是一个字符，而不是字符串，这是跟前面方法的区别。参数“index”是指字符串序列中字符的位置。

【实例6.15】下面将举个实例演示如何使用此方法。

01 ///从字符串中第2个位置将字符提取出来

02 public class str15

03 {

04 public static void main（String[]args）

05 {

06 String str="we are students and he is a worker"；

07 System.out.println（str.charAt（1））；

08 }

09 }

【代码说明】这个程序段输出的是字符串第二个字符，因为字符串位置从零开始计算，所以输出的字符是“e”。

【运行效果】

e

4.得到字符串的长度

在学习数组时，学到过数组长度，而在这里也要讲到字符串的长度。字符串长度使用方法“length（）”获取。

注意

数组的长度是“length”，而字符串的长度是“length（）”，数组的长度后面没有括号，得到的数组长度是一个属性值，而得到字符串长度是一个方法。

【实例6.16】看看下面的程序段。

01 ///输出字符串的长度

02 public class str16

03 {

04 public static void main（String[]args）

05 {

06 String str="we are students and he is a worker"；

07 System.out.println（str.length（））；

08 }

09 }

【代码说明】第7行使用了length（）方法直接输出str对象的长度。

【运行效果】

34

5.测试字符串是否相等

在实际程序开发中，经常会出现一些比较字符串的程序模块，通过比较字符串是否相等，来实现某个要求。例如，通过比较两个字符串是否相等，来确认密码和用户名是否正确，从而判断是否可以登录系统。这个在系统登录界面中经常遇到。测试字符串是否相等的方法是“equals（String str）”。

【实例6.17】下面看一个具体的测试字符串是否相等的实例。

01 ///通过比较字符串str和str1两个字符串是否相同，来确定不同的输出

02 ///如果相同则输出"密码正确，请登录系统"

03 ///否则输出"密码不正确，请重新输入密码"

04 public class str17

05 {

06 public static void main（String[]args）

07 {

08 String str="administrator"；

09 String str1="administrator"；

10 if（str.equals（str1））

11 {System.out.println（"密码正确，请登录系统"）；}

12 else

13 {System.out.println（"密码不正确，请重新输入密码"）；}

14 }

15 }

【代码说明】第8～9行是两个字符串。第10行是判断语句，通过equals（）方法判断两个指定的字符串是否相等。

【运行效果】

密码正确，请登录系统

在现实程序开发中，有的登录系统对于输入密码的大小写忽略。此时在Java语言中也有一个方法就是“equalsIgnoreCase（String str）”，这个方法忽略字符串大小写。

【实例6.18】下面看一个程序段的例子。

01 ///通过比较字符串str和str1两个字符串是否相同，来确定不同的输出，此时忽略大小写

02 ///如果相同则输出"密码正确，请登录系统"

03 ///否则输出"密码不正确，请重新输入密码"

04 public class str18

05 {

06 public static void main（String[]args）

07 {

08 String str="Administrator"；

09 String str1="administrator"；

10 if（str.equalsIgnoreCase（str1））

11 {System.out.println（"密码正确，请登录系统"）；}

12 else

13 {System.out.println（"密码不正确，请重新输入密码"）；}

14 }

15 }

【代码说明】第8～9行定义了两个字符串，它们的不同就是首字母是否大小写。第10行使用了equalsIgnoreCase（）方法来进行判断。

【运行效果】

密码正确，请登录系统

以上的程序段是使用了忽略大小写的方法，比较两个字符串是否相等。

【实例6.19】下面将通过不忽略大小写的方法编写代码，并与上例相比较。

01 ///通过比较字符串str和str1两个字符串是否相同，来确定不同的输出，此时不忽略大小写

02 ///如果相同则输出"密码正确，请登录系统"

03 ///否则输出"密码不正确，请重新输入密码"

04 public class str19

05 {

06 public static void main（String[]args）

07 {

08 String str="Administrator"；

09 String str1="administrator"；

10 if（str.equals（str1））

11 {System.out.println（"密码正确，请登录系统"）；}

12 else

13 {System.out.println（"密码不正确，请重新输入密码"）；}

14 }

15 }

【代码说明】第8～9行是一个单词，不同的是首字母一个大写，一个小写。第10行的equals（）方法在比较时并不忽略大小写，所以比较结果是false。

【运行效果】

密码不正确，请重新输入密码

6.查找特定子串

在程序开发的过程中，有的系统提供查找子系统，用于查找自己需要的内容。在Java语言中，也提供了查找特定子串的功能，可以帮助查找自己需要的子字符串。

查找字符串子串有三个方法：

❑“indexOf（子串内容）”方法是帮助查找子串，如果返回的是负数，就表示在当前字符串中没有找到所查找的子串。

❑“startsWith（子串内容）”方法测试当前字符串是否以一个子串开始。

❑“endsWith（子串内容）”方法测试当前字符串是否以子串内容为结尾。

【实例6.20】下面看一下具体的程序段，通过实例读者会理解得更加透彻。

01 ///通过indexOf方法来查找字符串中的元素位置

02 ///通过endsWith来查找当前元素是否是字符串的结尾

03 ///通过startsWith来查找当前元素是否是字符串的开头

04 public class str20

05 {

06 public static void main（String[]args）

07 {

08 String str="是一个很优秀的程序员"；

09 System.out.println（str.indexOf（"个"））；

10 System.out.println（str.endsWith（"员"））；

11 System.out.println（str.startsWith（"明"））；

12 }

13 }

【代码说明】str.indexOf（“个”）是测试字符串中的“个”这个子串的位置。str.endsWith（“员”）是测试字符串“员”是否是这个字符串的结尾。str.startsWith（“明”）是测试字符串“明”是否是这个字符串的开始。后面两个方法返回的是布尔型数据。

【运行效果】

2 true

false

7.从基本类型转换成字符串

使用“valueOf（）”将基本类型的数据转换成相应的字符串。由于这个方法很简单，读者在后面的程序中会看到，这里不作详细的解释。

8.toString（）方法

为什么会把这个方法提出来讲述呢？因为它是程序开发语言中非常重要的字符串处理方法。

【实例6.21】复习下面的程序段。

01 public class str21

02 {

03 public static void main（String[]args）

04 {

05 String str="小明是一个很优秀的程序员"；

06 System.out.println（str）；

07 }

08 }

【代码说明】第5行定义一个字符串str，第6行直接输出str。

【运行效果】

小明是一个很优秀的程序员

为什么可以直接将这个对象作为输出的参数呢？因为在Java语言内含一种机制，系统默认会在这些对象后面自动加上“toString（）”方法。在Java类库的基本类中，每一个类都有一个“toString（）”方法，可以将这个方法写出来，也可以直接使用对象来代替“toString（）”方法。

【实例6.22】下面来看看这个方法的实际例子。

01 ///通过toString方法来输出对象的字符串形式

02 public class str22

03 {

04 public static void main（String[]args）

05 {

06 String str="小明是一个很优秀的程序员"；

07 System.out.println（str.toString（））；

08 }

09 }

【代码说明】其实上面的程序段与前面的程序段输出一模一样。它们之间唯一不同的是，在输出语句中一个是把对象作为参数，将其内容输出。而上面的程序段则是直接运用“toString（）”方法，将对象中的字符串提取出来，然后再进行输出。

【运行效果】

小明是一个很优秀的程序员

对于Java语言的类库来说，一般可以省去“toString（）”方法，但如果是自己设计的类，最好加上这个方法，养成良好的编程习惯。

6.2.4　缓冲字符串处理类—StringBuffer

前面介绍过，“String”类一旦声明初始化后，是固定不变的。如果要改变它，就必须重新申请空间，重新声明和初始化。Java类库中有一个类，可以解决上面的问题，那就是缓冲字符串类—“StringBuffer”类。

当创建StringBuffer类对象时，系统为对象分配的内存会自动扩展，以容纳新增的内容。针对“StringBuffer”类创建对象时的构造器有两个，下面将详细地讲述。

6.2.5　缓冲字符串StringBuffer类的构造器

在这一小节中，将学习缓冲字符串类的构造器知识。通过学习来对比StringBuffer类和String类的不同点和相同点。

1.默认的构造器

StringBuffer sb=new StringBuffer（）；

默认构造器是由系统自动分配容量，而系统容量默认值是16个字符。

【实例6.23】下面来看这个默认构造器的实例。

01 ///输出字符串的容量capacity

02 ///输出字符串的长度length

03 public class str23

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb1=new StringBuffer（）；

08 System.out.println（sb1.capacity（））；

09 System.out.println（sb1.length（））；

10 }

11 }

【代码说明】这个程序段要说明的是，缓冲字符串对象的容量和缓冲字符串的长度。缓冲字符串的容量，就是指在刚刚创建对象时，系统分配的内存容量的大小。缓冲字符串的长度，则是指实际缓冲字符串对象的内存空间中，字符串的长度。在这个程序中，由于是默认的构造器，所以它的容量也是默认的，即16。由于它没有赋值，所以这个缓冲字符串对象的长度就是0。

【运行效果】

16 0

2.设定容量大小的构造器

StringBuffer sb=new StringBuffer（int x）

“x”是设置容量的大小值。

【实例6.24】例如下面的语句。

01 ///输出字符串的容量capacity

02 ///输出字符串的长度length

03 public class str24

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb1=new StringBuffer（100）；

08 System.out.println（sb1.capacity（））；

09 System.out.println（sb1.length（））；

10 }

11 }

【代码说明】这里通过构造器设定了缓冲字符串对象的容量。虽然容量改变了，但长度仍然是零，因为它的内存空间中没有值。“capacity（）”方法代表了字符串对象在内存中，可以容纳字符串的个数。如果想要扩充内存容量，可以使用方法“ensureCapacity（）”。方法“length（）”表示内存中已经存在的字符串的个数，如果想要改变字符串长度，可以使用“setLength（）”方法。

【运行效果】

100 0

【实例6.25】下面看一个缓冲字符串实例。

01 ///输出字符串的容量capacity

02 ///输出字符串的长度length

03 public class str25

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（40）；

08 System.out.println（sb.capacity（））；

09 sb.ensureCapacity（100）；

10 System.out.println（sb.capacity（））；

11 }

12 }

【代码说明】这里通过构造器设定了缓冲字符串对象的容量，第一次是40。第9行使用方法“ensureCapacity（）”扩充内存容量到100。

【运行效果】

40 100

6.2.6　缓冲字符串处理的方法

下面将学习StringBuffer类的一些主要方法。其实这些方法有很多与String类的方法很相似，通过学习可以进行比较。

1.初始化字符串

StringBuffer sb=new StringBuffer（字符串）；

使用这种形式的构造器，可以构建具有初始化文本的对象，容量大小就是字符串的长度。一旦创建了“StringBuffer”类的对象，就可以使用“StringBuffer”类的大量方法和属性。“StringBuffer”类最常用的是“append（）”方法，它将文本内容添加到现有的“StringBuffer”对象内存中字符串的结尾处。

【实例6.26】下面看看这个方法应用的实例。

01 ///构造一个缓冲字符串类的对象

02 ///通过append方法，在这个对象后面添加一个新字符串

03 public class str26

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"小明是一个优秀"）；

08 sb.append（"的程序员"）；

09 System.out.println（sb）；

10 }

11 }

【代码说明】第8行使用append（）方法将两个字符串连接在一起，有点像String类的concat（）方法。

【运行效果】

小明是一个优秀的程序员

【实例6.27】前面讲了“setLength（）”方法，现在通过实例了解其用法。

01 ///构造一个缓冲字符串类的对象

02 ///通过append方法，在这个对象后面添加一个新字符串

03 ///通过setLength方法来设置缓冲字符串对象的长度

04 public class str27

05 {

06 public static void main（String[]args）

07 {

08 StringBuffer sb=new StringBuffer（"小明是一个优秀"）；

09 sb.append（"的程序员"）；

10 sb.setLength（3）；

11 System.out.println（sb）；

12 }

13 }

【代码说明】第8行定义了字符串sb，第9行将两个字符串连接起来，此时字符串的长度是11。第10行指定字符串长度是3，所以读者要注意输出结果。

【运行效果】

小明是

2.取字符串的单个字符

charAt（）方法返回字符串中的单个字符。

【实例6.28】下面是这个方法的实例。

01 ///构造一个缓冲字符串类对象

02 ///返回指定位置的字符

03 public class str28

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"小明是一个优秀"）；

08 System.out.println（sb.charAt（3））；

09 }

10 }

【代码说明】第7行定义了字符串sb，第8行指定输出sb的第四个元素，因为索引时从0开始的，所以代码是charAt（3）。

说明　除特殊情况外，基本上所有的索引都是从0开始。

【运行效果】

一

3.单个字符串赋值

setCharAt（）方法对字符串中的单个字符赋值或进行替换。

【实例6.29】下面是这个方法的实例。

01 ///构造一个缓冲字符串类对象

02 ///将指定位置的元素替换成新的字符

03 public class str29

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"小明是一个优秀程序员"）；

08 sb.setCharAt（0，'张'）；

09 System.out.println（sb）；

10 }

11 }

【代码说明】“setCharAt（）”方法使用的格式是“setCharAt（int index, char ch）”。上面的实例将字符串中的“小”替换成“张”。

【运行效果】

张明是一个优秀程序员

4.指定位置插入字符串

insert（）方法在字符串指定位置插入值。

【实例6.30】下面学习一个实例。

01 ///构造一个缓冲字符串类对象

02 ///使用insert方法将新字符串插入到指定的位置上

03 public class str30

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"我是一个优秀"）；

08 sb.insert（6，"的程序员"）；

09 System.out.println（sb）；

10 }

11 }

【代码说明】第8行的结构就是“insert（int index, string str）”。最终的结果其实是连接了两个字符串，因为指定的位置正好是字符串sb的结束位置。

【运行效果】

我是一个优秀的程序员

上面的这个例子有点像“append（）”方法。

【实例6.31】下面再看一个实例。

01 ///构造一个缓冲字符串类对象

02 ///使用append方法连接字符串

03 public class str32

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"我是一个优秀的"）；

08 sb.append（"程序员"）；

09 System.out.println（sb）；

10 }

11 }

【代码说明】第8行使用了“append（）”方法实现了和上一个实例相同的功能。

【运行效果】

我是一个优秀的程序员

5.返回字符串的子串

substring（）方法返回字符串的一个子串。

【实例6.32】下面是这个方法的实例。

01 ///构造一个缓冲字符串类对象

02 ///使用substring方法返回指定位置开始到结束位置的子串

03 public class str33

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"我是一个程序员"）；

08 System.out.println（sb.substring（2））；

09 }

10 }

【代码说明】这个程序段返回的是，从字符串的第三个位置开始，到最后位置之间的子字符串。

【运行效果】

一个程序员

【实例6.33】针对这个方法，再看看下面的实例。

01 ///构造一个缓冲字符串类对象

02 ///使用substring方法返回指定位置开始到另一个指定位置结束的子串

03 public class str34

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"我是一个程序员"）；

08 System.out.println（sb.substring（2，7））；

09 }

10 }

【代码说明】其实这个方法同“String”类中的方法用法相同。返回从开始位置到结束位置之间的字符串。

【运行效果】

一个程序员

6.倒置字符串的内容

reverse（）方法用来倒置“StringBuffer”的内容。

【实例6.34】下面针对这个方法看一个实例。

01 ///构造一个缓冲字符串类对象

02 ///将字符串倒置后输出

03 public class str35

04 {

05 public static void main（String[]args）

06 {

07 StringBuffer sb=new StringBuffer（"我是一个程序员"）；

08 System.out.println（sb.reverse（））；

09 }

10 }

【代码说明】这段代码比较简单，第8行直接调用reverse（）方法，不需要任何参数。

【运行效果】

员序程个一是我

6.2.7　缓冲字符串类的特点

字符串是绝大多数应用程序经常使用且不可缺少的对象之一。由于缓冲字符串类有着比字符串类更加宽裕的空间，所以缓冲字符串可以用来处理一些动态字符串，而一般字符串类只能处理静态的不可变化的字符串。

6.3　用实例演示如何处理字符串

【实例6.35】下面看一个实例，通过这个实例可以更熟练地处理字符串数据。下面是字符串处理要求：

❑有两个字节数组：｛‘I’，‘a’，‘m’，‘a’，‘b’，‘o’，‘y’｝，以及｛‘h’，‘e’，‘i’，‘s’，‘a’，‘b’，‘o’，‘y’｝，请将它们以字符串形式输出。

❑用缓冲字符串类来输出上面的字符串。

❑用前面学到的字符串处理方法来处理它。

01 ///初始化两个字符数组c1和c2

02 ///利用这两个字符数组构造两个字符串对象str1和str2，并且将其输出

03 ///将字符串str1构造成一个缓冲字符串对象sb

04 ///使用append方法将str2与sb连在一起，并且将其输出

05 ///提取某个charAt方法位置上的元素

06 ///使用reverse方法将字符串倒置，并且将其输出

07 public class str36

08 {

09 public static void main（String[]args）

10 {

11 char[]c1={'I'，'a'，'m'，'a'，'b'，'o'，'y'}；

12 char[]c2={'h'，'e'，'i'，'s'，'a'，'b'，'o'，'y'}；

13 String str1=new String（c1）；

14 String str2=new String（c2）；

15 System.out.println（str1）；

16 System.out.println（str2）；

17 StringBuffer sb=new StringBuffer（str1）；

18 sb.append（str2）；

19 System.out.println（sb）；

20 System.out.println（sb.charAt（0））；

21 sb.setCharAt（0，'y'）；

22 sb.insert（1，'o'）；

23 sb.insert（2，'u'）；

24 System.out.println（sb）；

25 System.out.println（sb.substring（7））；

26 sb.reverse（）；

27 System.out.println（sb）；

28 }

29 }

【代码说明】第18行使用了append（）方法连接字符串，第20行使用了charAt（）方法获取第1个字符。第21行使用了setCharAt（）方法替换第1个字符。第22～23行使用了insert（）方法插入两个字符。第25行使用了substring（）方法截取字符串，第26行使用了reverse（）方法倒置字符串。

【运行效果】

Iamaboy

heisaboy

Iamaboyheisaboy

I

youamaboyheisaboy

oyheisaboy

yobasiehyobamauoy

以上这个程序段，是前面所讲过的字符串处理方法的一个汇总，只要能够真正地理解这个程序段，字符串的处理也就掌握了。

6.4　如何格式化输出

程序员经常使用“System.out.println（x）”方法向控制台输出数据，这条命令会按照x的数据类型所允许的非零数字位的最大数字打印。

【实例6.36】下面通过例子进行演示。

01 public class str37

02 {

03 public static void main（String[]args）

04 {

05 double x=（100/3.0）；

06 System.out.println（x）；

07 }

08 }

【代码说明】第5行是一个除法运算，返回结果是double型。

【运行效果】

33.333333333333336

如果用这个结果表示货币、百分数或有一定小数位数的小数时，肯定不合适。

下面将给出一些格式器，让读者能够套用它们，改变数字的格式。

NumberFormat.getNumberInstance（Locale inLocale）；//指定数字格式

NumberFormat.getCurrencyInstance（Locale inLocale）；//指定货币格式

NumberFormat.getPercentInstance（Locale inLocale）；//指定百分比格式

以上这些格式器，针对特定地区而用。如果是默认地区，就可以使用下面的格式器。

NumberFormat.getNumberInstance（）；//指定默认地区的数字格式

NumberFormat.getCurrencyInstance（）；//指定默认地区的货币格式

NumberFormat.getPercentInstance（）；//指定默认地区的百分比格式

以上介绍的这些格式器，限于篇幅和本书重点的问题，在这里只是简要介绍，不作详细的分析和举例。

6.5　常见疑难解答

6.5.1　“equals”和“==”的区别

答：如果操作两边都是对象句柄，就比较两个句柄是否指向同一个对象。如果两边是基本类型，比较的就是值。

“equals”比较的是两个对象的内容，如果不重载“equals”方法，自动调用object的“equals”方法，则和“==”样。在JDK中像“String”、“Integer”，默认重载了“equals”方法，则比较的是对象的内容。在实际编程中，建议使用“equals”方法。

6.5.2　String类为何被定义成final约束

答：主要是考虑“效率”和“安全性”的缘故。若“String”允许被继承，则其频繁地被使用，可能会降低程序的性能，所以“String”被定义成“final”。

6.5.3　char类型如何转换成int类型，int类型如何转换成字符串

char类型转换成int类型的代码如下所示。

char c='A'；

int i=c；

//反过来只要作强制类型转换就行了

c=（char）I；

将整数int转换成字串String有两种方法：

（1）String s=String. valueOf（i）；

（2）String s=Integer. toString（i）；

6.6　小结

本章重点讲解了字符串的处理，其实从大局来分，主要分为两部分：String类的处理和StringBuffer类的处理。这两个类都包含很多处理字符串的方法，读者在操作时一是要记住这些方法的名字，二是要注意这些方法的大小写，在Java中大小写非常重要。本章的实例都经过测试，希望读者能亲自动手实验每个实例。

6.7　习题

1.以下哪个方法不能实现字符串的连接？

A.使用“+”

B.使用方法函数concat（）

C.使用方法函数substring（）

2.在Java类库的基本类中，每一个类都有一个“toString（）”方法。这种说法是否正确？3.缓冲字符串类的特点是什么？

第二篇　Java语言特色篇

第7章　类

什么是类？类有什么用处？为什么它会是面向对象编程的一个典型特征？带着这些问题，本章将会结合大量的实例为读者一一讲解。

类是面向对象编程中最基本、也是最重要的特征之一。从本章开始，将介绍如何进行面向对象的程序开发，以及程序开发的过程中，所需要具备的重要思想是什么。编程思想学不好是很多学习编程的人，学习时间长却学不好编程的一个主要原因。

本章重点：

❑了解JDK 5和JDK 6特性。

❑认识类的概念和常见类。

❑学习如何设计类。

7.1　JDK 5和JDK 6特性

本节介绍什么是JDK，以及Java开发工具集中两个不同的JDK版本。本节的讲解比较简单，有兴趣深入学习的读者可查看其他的相关书籍。

7.1.1　什么是JDK

JDK即Java开发工具包（Java Development Kit, JDK），JDK是Sun公司免费提供的Java语言工具，是Java应用程序开发的基础。通常第三方开发工具“JBuilder9.0”、“IBM VisualAge for Java”等都是基于JDK进行开发。

7.1.2　JDK 5的特点

JDK 5其实就是JDK1.5，JDK1.5（开发代号猛虎）的一个重要主题就是通过新增一些特性来简化开发，包括泛型、for-each循环、自动装包/拆包、枚举、可变参数、静态导入。

使用这些特性有助于编写更加清晰、精悍、安全的代码。下面简单介绍一下这些新特性。

❑泛型（Generic）

C++通过模板技术可以指定集合的元素类型，而Java在1.5之前一直没有相对应的功能。在C++中，一个集合可以存放任何类型的对象，相应地从集合里面拿对象的时候，也不得不对它们进行强制的类型转换。JDK1.5引入了泛型，并且允许指定集合里元素的类型，这样可得到强类型在编译时刻进行类型检查的好处。

❑for-each循环

for-each循环的加入简化了集合的遍历。

❑自动装包/拆包（Autoboxing/unboxing）

自动装包/拆包大大方便了基本类型数据和其包装类的使用。自动装包就是基本类型自动转为包装类。自动拆包就是包装类自动转为基本类型。在JDK1.5之前，总是对集合不能存放基本类型而耿耿于怀，现在自动转换机制解决了问题。

❑枚举（Enums）

JDK1. 5加入了一个全新类型的“类”—枚举类型。

❑可变参数（Varargs）

可变参数的出现，使程序员可以声明一个接受可变参数数目的方法。

注意　可变参数必须是函数声明中的最后一个参数。

❑静态导入（Static Imports）

要使用静态成员（方法和变量），必须给出提供这个方法的类。使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见，从而在使用这些静态成员时无须再给出它们的类名。

7.1.3　JDK 6的特点

浏览了JDK6的新特性，感觉“Compiler API”的应用有很大空间。JDK6提供对代码的编译功能，可以实现代码编辑、编译及加载运行，可以在系统运行中动态配置和扩展用户自定义的功能，甚至可以把Java语句作为脚本，内嵌到原有系统中扩充功能集。

7.1.4　JDK 5与JDK 6的比较

2006年底，Sun公司发布了Java Standard Edition 6（Java SE 6）的最终正式版，代号“Mustang（野马）”，跟“Tiger（Java SE 5）”相比，“Mustang”在性能方面有了不错的提升。从与“Tiger”在API类库的比较来讲，有了大幅度加强，虽然“Mustang”在API库方面的新特性显得不太多，但其提供了许多实用和方便的功能：在脚本、Web Service、XML、编译器API数据库、JMX、网络方面都有不错的新特性和功能加强。

说明　随着不断的发展和进步，相信JDK 6会有不错的表现，但目前公司最通用的是JDK 5。

7.2　用比较法阐述类的概念

现实生活中有一个例子：造房子的砖头有红色的砖、有方砖、有圆砖，此时，在现实生活中各种各样的砖头都可以称作是对象。红色的砖可以称为一个对象，圆砖可以称为一个对象，它们有共同点，但也有不同的方面，这些不同的方面使得它们不可能是同一个对象。然而，砖头就是所有种类的总称，所有的砖都是由它派生而来，所以砖可以称为是一个类。

类就是模板，也可以说类其实就是创建对象的基地，它能产生很多不同的对象。再举个例子，汽车就是一个类，而卡车、轿车等都是从汽车这个类中派生出来的，也就是说，这些都是属于汽车这个类。其实类是一个很灵活的概念，也可以将轿车作为一个类，在轿车这个类中，再创建各种品牌的轿车对象出来，有桑塔纳、红旗、奔驰，而每种轿车都是一个对象。类与对象的关系，有点像一个母亲与子女的关系。

说明　本章重点讲述类的知识，至于对象的知识将在下一章详细讲述。

7.3　Java中已有的类

目前，Java中已经存在了很多由前辈开发出来的类，可以被开发人员直接使用，将这些有共同特征的类组合在一起形成了类库。在Java中包含大量用于不同目的的类库，这些类库是开发Java软件的基础。即使需要设计自己的类，也有可能使用到Java类库中的很多类。

可以打开API文档，查看一些已经存在的类库，能发现Java类库非常丰富。鉴于读者都是初学者，所以暂时只介绍一些简单的API类库，随着学习的深入，后面读者会接触到更多的类。

7.3.1　Java的数学运算处理类Math

看到“Math”这个单词，立刻就能明白这个类一定跟数学有关。其包含了丰富的数学函数，可以使用这个类的方法，而不用管它们内部是如何实现的。通过查看API文档，知道“Math”类中的所有方法和字段都是可以直接访问的，在Java中，称之为静态方法和静态字段。有关静态方法和静态字段的概念，后面章节有介绍，现在只需要知道，这些方法和字段可以直接去使用即可。

那么如何访问其中的方法和字段呢？因为这些成员是静态成员，所以可通过“类名.方法名称”和“类名.字段”来访问方法和字段。在Math类中，由于都是静态成员，就可以使用“Math.方法名称或常量名”。下面把这个类中经常使用到的方法和属性值列举出来，以提供给大家参考。

Math类中包含了两个静态常量：

Math.PI-----------------------------------------------表示数学常量π

Math.E------------------------------------------------表示和e最可能接近的近似值

Math类提供了常用的三角函数：

Math.sin----------------------------------------------表示正弦函数

Math.cos----------------------------------------------表示余弦函数

Math.tan----------------------------------------------表示正切函数

Math.asin---------------------------------------------表示反正弦函数

Math.acos---------------------------------------------表示反余弦函数

Math.atan---------------------------------------------表示反正切函数

Math.atan2--------------------------------------------表示反余切函数

Math类还提供了幂函数、指数函数和自然对数函数：

Math.pow----------------------------------------------返回a的b次方

Math.exp----------------------------------------------返回e的a次方

Math.log----------------------------------------------返回a的常用对数值

Math类提供了常用的数学运算函数：

Math.abs----------------------------------------------返回绝对值

Math.sqrt---------------------------------------------返回平方根

Math.max----------------------------------------------返回最大值

Math.min----------------------------------------------返回最小值

Math类提供了角度与弧度相关的转换运算方法：

Math.toDegrees（double angrad）------------------------将弧度转换成角度值

Math toRadians（double angdeg）------------------------将角度值转换成弧度

Math类提供了四舍五入的运算及截断运算：

Math.round（double e）---------------------------------四舍五入运算

Math.floor（double e）--------------------------------返回不大于e的最大整数

Math类提供了一个专门用来产生随机数的函数：

Math.random（）-----------------------------------------用来产生随机数的函数

以上是在Math类中常用的方法和函数。下面将通过举例来熟悉它们。

【实例7.1】首先通过下列程序代码来熟悉数学运算函数的用法：

01 public class Math1

02 {

03 public static void main（String[]args）

04 {

05 int x=9；

06 int y=16；

07 System.out.println（Math.sqrt（x））；//计算x的平方根的结果

08 System.out.println（Math.abs（x））；//计算x的绝对值的结果

09 System.out.println（Math.max（x, y））；//计算x与y的最大值

10 System.out.println（Math.min（x, y））；//计算x与y的最小值

11 }

12 }

【代码说明】在运用这些函数方法的时候，希望读者能注意到函数所带的参数，“Math.sqrt（）”、“Math.abs（）”带的是一个参数。“Math.max（）”、“Math.min（）”带的是两个参数，这一点必须记住。

【运行结果】

3.0

9 16

9

【实例7.2】再通过下列程序代码来熟悉四舍五入函数的用法：

01 public class Math2

02 {

03 public static void main（String[]args）

04 {

05 double x=4.51；

06 System.out.println（Math.round（x））；//返回的是四舍五入的值

07 System.out.println（Math.floor（x））；//返回的是不大于x的最大整数

08 }

09 }

【代码说明】这两个方法都是只有一个参数。第7行返回的是不大于参数值的最大整数。

【运行结果】

5 4.0

通过以上两个例子可以看出，函数方法的使用很简单，关键是要注意其参数。纵观类库，其实就是一个方法和属性的集合。至于类库的学习方法，关键是多练习，只要熟悉了类库中各种方法的使用，也就掌握了类库。

7.3.2　测试时间和日期的类Date

下面要介绍另一个比较重要的类：Date类。Date类包括了有关日期和时间操作的一些方法。Date类提供相应的方法，可将日期分解为年、月、日、时、分、秒。Data类还可以将日期转换成一个字符串，甚至可以执行反向的操作。

因为Math类中的方法和字段属性都是静态的，所以可以直接使用它。而Date类中的方法和字段属性不是静态的，所以不能直接用“类.方法名或字段名”，必须使用“对象名.方法名或字段名”。

如何将类变成对象呢？因为类是模板，对象是实物。在Java中，从模板中创建一个实物是使用关键字“new”来实现的（在下一章会详细介绍创建对象方面的知识）。针对非静态的类或方法，就要使用对象来操作，不能使用类来操作。

下面演示如何从类中产生一个对象：

new Date（）

这个表达式构造了一个日期对象，并把这个对象初始化为当前的日期和时间，其实可以从一个类中，产生多个不同的对象。Date类可创建很多种不同的对象，如：

Date（int year, int month, int date），

Date（int year, int month, int date, int hrs, int min）；

但是如果在程序中需要将时间显示出来，应该使用什么方法呢？这里有两种方法：（1）将Date对象作为一个参数，传给“println”方法。

【实例7.3】下面是这个方法的实例。

01 import java.util.Date；

02 public class Math3

03 {

04 public static void main（String[]args）

05 {

06 System.out.println（new Date（））；

07 }

08 }

【代码说明】第1行非常关键，使用import导入类库。第6行直接输出日期。这个程序代码段是显示当前的日期和时间。它将Date（）作为当前时间日期，然后将此对象传给“println”方法。

【运行结果】

Wed Aug 26 23：08：34 CST 2009

（2）使用Date类中的方法“toString（）”（有关这一点在前面已经介绍过），它可以直接将时间日期按照字符串的形式显示出来。

【实例7.4】下面是这个方法的实例。

01 import java.util.Date；

02 public class Math4

03 {

04 public static void main（String[]args）

05 {

06 System.out.println（new Date（））；

07 System.out.println（new Date（）.toString（））；

08 }

09 }

【代码说明】这个例子将两种方法作了对比，其实从输出结果来看，是一模一样的。只是使用的方法不同而已。

【运行结果】

Wed Aug 26 23：10：12 CST 2009

Wed Aug 26 23：10：12 CST 2009

7.3.3　测试日历的类GregorianCalendar

在对Date类的介绍中，读者会发现Date类所表示的是一个时间点，也就是在创建对象当时的时间点，而这对于进行日期的相关操作及运算，是非常不方便的。为此，前辈们在Java类库中，为开发者提供了一个方便操作日期的类：GregorianCalendar类。其实GregorianCalendar类是Calendar类的一个扩展而已，Calendar类是从总体上描述历法的类。

Date类中也有用于得到日期的方法函数，如“getDay（）”、“getMonth（）”等，但是这些方法已经不被推荐使用了，在程序中尽量不要使用不被推荐（deprecated）的方法函数。相比之下，GregorianCalendar类拥有更多的对日期操作的方法函数。GregorianCalendar类的常用方法：

public int get（int field）

这里的“field”指的Calendar类中定义的常数，返回与“field”相关的日期。

【实例7.5】举个具体的例子：

01 import java.util.*；

02 public class GregorianCalendars

03 {

04 public static void main（String[]args）

05 {

06 GregorianCalendar gc=new GregorianCalendar（）；

07 int X=gc.get（Calendar.MONTH）；

08 System.out.println（X）；

09 }

10 }

【代码说明】第7行通过get（）方法返回月份。Calendar.MONTH返回日历中的当前月。【运行结果】

7 "public void set（int field, int value）"

将“field”所表示的日期替换成“value”的值。

【实例7.6】举个具体的例子：

01 import java.util.*；

02 public class GregorianCalendar1

03 {

04 public static void main（String[]args）

05 {

06 GregorianCalendar gc=new GregorianCalendar（）；

07 gc.set（Calendar.YEAR，2009）；

08 System.out.println（gc.get（Calendar.YEAR））；

09 }

10 }

【代码说明】第7行设置年份为2009，然后第8行输出当前年份。

【运行结果】

2009

下面是有一些有关如何设置系统时间的方法。

"public final void set（int year, int month, int date）"

"public final void set（int year, int month, int date, int hour, int minute）"

"public final void set（int year, int month, int date, int hour, int minute, int second）"

这里的“final”在后面一章会讲述。上面的三个方法函数是指如何设定时间和日期。

【实例7.7】看下列代码段，就可以明白以上这三个方法函数是什么含义：

01 import java.util.*；

02 public class GregorianCalendar2

03 {

04 public static void main（String[]args）

05 {

06 GregorianCalendar gc=new GregorianCalendar（）；

07 gc.set（2009，06，18）；

08 System.out.println（gc.get（Calendar.YEAR））；

09 System.out.println（gc.get（Calendar.MONTH））；

10 System.out.println（gc.get（Calendar.DATE））；

11 }

12 }

【代码说明】在上面的例子里，先通过“set”方法设置年份为2009年，然后设置月份为6月，接着设置日期是18号，最后再通过“get”访问器的方法取出值。

【运行结果】

2009 6

18

7.3.4　日历处理的实例解析

对于月历类来说，最重要的参数是年、月、日、小时、分、秒、毫秒，所以Calendar类提供了这些参数：

YEAR（年）、MONTH（月）、DATE（日）、HOUR（小时）、MINUTE（分钟）、SECOND（秒）、MILLISECOND（毫秒）

另外一年还有十二个月的参数，所以Calendar类提供了：

JANUARY（一月）、FEBRUARY（二月）、MARCH（三月）、APRIL（四月）、MAY（五月）、JUNE（六月）、JULY（七月）、

AUGUST（八月）、SEPTEMBER（九月）、OCTOBER（十月）、NOVEMBER（十一月）、DECEMBER（十二月）

依次表示1～12个月份，需要注意的是它们从零开始计算。

例如：

get（Calendar.MONTH）+1

另外，一周的每一天使得Calendar类提供了：

SUNDAY（=1）、MONDAY（=2）、TUESDAY（=3）、WEDNESDAY（=4）、THURSDAY（=5）、FRIDAY（=6）、SATURDAY（=7）

如果获取星期或改变星期时使用：

DAY_OF_WEEK

如果表示上午/下午：

AM_PM

【实例7.8】上面介绍了很多常用函数和属性常量，下面看一个实例。

01 import java.util.*；

02 public class calendar1

03 {

04 public static void main（String args[]）

05 {

06 GregorianCalendar gc=new GregorianCalendar（）；

07 String now=gc.get（Calendar.YEAR）+"年"+（gc.get（Calendar.MONTH）+1）+"月

08"+gc.get（Calendar.DATE）+"日"+gc.get（Calendar.HOUR）+"时"

+gc.get（Calendar.MINUTE）+"分

09"+gc.get（Calendar.SECOND）+"秒"；

10 System.out.println（"当前时间是："+now）；

11 }

12 }

【代码说明】这个程序段很简单，对这个类中每一种函数都展示了其用法。读者可以根据上面的介绍依次查看每个函数。

【运行结果】

当前时间是：2009年8月26日11时32分50秒

【实例7.9】下面来编写一个稍微复杂的实例，然后来仔细分析这个程序段的编程思路。有个人的生日是阳历7月20日，请输出2007—2020年之间，这个人每年的生日究竟是星期几。

01 import java.util.*；

02 public class calendar2

03 {

04 public static void main（String[]args）

05 {

06 GregorianCalendar gc=new GregorianCalendar（）；

07 final char[]kor_week={'日'，'一'，'二'，'三'，'四'，'五'，'六'}；

08 for（int i=2007；i＜=2020；i++）

09 {

10 gc.set（i, Calendar.JULY，20）；

11 char week=kor_week[gc.get（Calendar.DAY_OF_WEEK）-1]；

12 System.out.println（i+"年的生日是星期"+week）；

13 }

14 }

15 }

【代码说明】这个程序的具体代码，很容易读懂。在此，需要讲述的是一个编程的思路问题。当拿到这个程序的要求时，首先分析是要求输出“2007—2020”年之间每年的生日究竟是星期几，这时就要考虑使用循环语句。

通过“set”函数将年份依次改为2007—2020年，这样就可以通过“get”函数返回此年份月份和日子到底是星期几，如果是这样分析，编写它就不难了，剩下的只不过是一些细节问题。

【运行结果】

2007年的生日是星期五

2008年的生日是星期日

2009年的生日是星期一

2010年的生日是星期二

2011年的生日是星期三

2012年的生日是星期五

2013年的生日是星期六

2014年的生日是星期日

2015年的生日是星期一

2016年的生日是星期三

2017年的生日是星期四

2018年的生日是星期五

2019年的生日是星期六

2020年的生日是星期一

7.4　用实例分析设计一个类的流程

下面将会通过一个综合实例，来总结本章中的一些比较重要的知识，从而总结一下编程思想。

7.4.1　如何在现实程序设计中提取一个类

在现实程序开发过程中，需要根据客户提供的要求来编写程序。在编写程序之前，要分析客户的要求，这是很关键的一步。针对客户的要求，程序员要学会从客户要求中提取出类，然后根据类再创建对象，在对象中规划出方法和属性值，最后，由这些方法和属性值共同完成客户提供的要求。

【实例7.10】下面举一个实例，通过这个实例来证明以上所讲述的步骤。

对于一个班的学生，开发一个学生信息的程序段。其要求如下所示：

（1）男生有10名，女生有15名。请输出他们的姓名、　性别、　学号。

（2）其中张杰是男生，也是班长，请输出其信息。

（3）对所有学生的成绩信息进行输出。

（4）通过班上的成绩，选出最好的学生和最差的学生。

这个实例看起来有点复杂。其实仔细分析应该不是很难。编写程序最关键是程序员的思路，思路清晰了，代码自然很好解决。下面看看如何分析这个实例。

对于这个实例，从所有的要求看来，都是围绕着学生，所以提取一个类，就是学生类。如下：

01 class student

02 {

03 private name；

04 private code；

05 private sexy；

06 private duty；

07 private achievement；

08 }

这个类中的变量是根据实例要求而定义的，这些变量就是类中的属性。再来分析实例中需要程序员做什么事情，而要做的事情就是类中的方法。观察以上的4个要求，主要需要做的事情是输出和排序，那么类的方法中必须要有输出方法和排序方法。

对于第一个要求输出的信息。可以编写下列方法函数。

void print（）

{

}

下面再来观察第二个要求、第三个要求，其实也是围绕着学生信息这个数组来输出的。而第四个要求中，要求将所有的数据进行排序，选择出最大的数据和最小的数据。

好了，现在可以开始编写程序了。先来看看此程序的流程图，如图7.1所示。

图　7.1　有关类的创建的实例

看下面的代码。

01 ///这是一个学生类

02 ///通过带参数的构造器来构造对象

03 ///通过设置器来设置对象的属性

04 ///通过访问器来访问对象的属性

05 ///tostring是一个将对象以字符串的形式输出的方法

06 public class student

07 {

08 String name=""；

09 String code=""；

10 String sexy=""；

11 String duty=""；

12 double achievement；

13 public student（String name）

14 {

15 this.name=name；

16 }

17 18

void set（String name, String code, String sexy, String duty）

19 {

20 this.name=name；

21 this.code=code；

22 this.sexy=sexy；

23 this.duty=duty；

24 }

25 public String getname（）//返回姓名

26 {

27 return name；

28 }

29 public String getcode（）//返回　学号

30 {

31 return code；

32 }

33 public String getsexy（）//返回　性别

34 {return sexy；

35 }

36 public String getduty（）//返回职务

37 {return duty；}

38 public void setachi（double achievement）//设置成绩

39 {

40 this.achievement=achievement；

41 }

42 public double getachi（）//返回成绩

43 {

44 return achievement；

45 }

46 public void print（）//输出

47 {

48 System.out.println（"学生"+name+"的成绩是："+achievement）；

49 }

50 51

public String tostring（）

52 {

53 String infor="学生　姓名："+name+"；"+"学生　学号："+code+"；"+"学生　性别："

54+sexy+"；"+"学生职务："+duty；

55 return infor；

56 }

57 ///这是主运行程序

58 ///构造出25个对象。使用带参数的构造器来构造

59 ///再使用设置器来设置对象属性

60 ///使用tostring方法将对象信息输出

61 public static void main（String[]args）

62 {

63 ///构造出学生对象

64 student st1=new student（"王浩"）；

65 student st2=new student（"李敏"）；

66 student st3=new student（"李杰"）；

67 student st4=new student（"王杰"）；

68 student st5=new student（"王超"）；

69 student st6=new student（"赵浩"）；

70 student st7=new student（"钱浩"）；

71 student st8=new student（"王松"）；

72 student st9=new student（"朱涛"）；

73 student st10=new student（"张杰"）；

74 student st11=new student（"王敏"）；

75 student st12=new student（"孙洁"）；

76 student st13=new student（"赵丽"）；

77 student st14=new student（"王丽"）；

78 student st15=new student（"钱珍"）；

79 student st16=new student（"王珍"）；

80 student st17=new student（"王萍"）；

81 student st18=new student（"钱萍"）；

82 student st19=new student（"王燕"）；

83 student st20=new student（"赵燕"）；

84 student st21=new student（"孙燕"）；

85 student st22=new student（"孙丽"）；

86 student st23=new student（"林丽"）；

87 student st24=new student（"张丽"）；

88 student st25=new student（"郑丽"）；

89 ///构造一个学生类的对象数组，将所有的对象放到数组内

90 student[]st=new student[]

91 {st1，st2，st3，st4，st5，st6，st7，st8，st9，st10，st11，st12，st13，st14，st15，st16，st17，st18，st19，st20

92，st21，st22，st23，st24，st25}；

93 ///通过设置器对几个对象进行赋值

94 st1.set（"王浩"，"1"，"男"，"班员"）；

95 st2.set（"李敏"，"2"，"男"，"班员"）；

96 st3.set（"李杰"，"3"，"男"，"班员"）；

97 st4.set（"王杰"，"4"，"男"，"班员"）；

98 st5.set（"王超"，"5"，"男"，"班员"）；

99 st6.set（"赵浩"，"6"，"男"，"班员"）；

100 st7.set（"钱浩"，"7"，"男"，"班员"）；

101 st8.set（"王松"，"8"，"男"，"班员"）；

102 st9.set（"朱涛"，"9"，"男"，"班员"）；

103 st10.set（"张杰"，"10"，"男"，"班长"）；

104 st11.set（"王敏"，"11"，"女"，"班员"）；

105 st12.set（"孙洁"，"12"，"女"，"班员"）；

106 st13.set（"赵丽"，"13"，"女"，"班员"）；

107 st14.set（"王丽"，"14"，"女"，"班员"）；

108 st15.set（"钱珍"，"15"，"女"，"班员"）；

109 st16.set（"王珍"，"16"，"女"，"班员"）；

110 st17.set（"王萍"，"17"，"女"，"班员"）；

111 st18.set（"钱萍"，"18"，"女"，"班员"）；

112 st19.set（"王燕"，"19"，"女"，"班员"）；

113 st20.set（"赵燕"，"20"，"女"，"班员"）；

114 st21.set（"孙燕"，"21"，"女"，"班员"）；

115 st22.set（"孙丽"，"22"，"女"，"班员"）；

116 st23.set（"林丽"，"23"，"女"，"班员"）；

117 st24.set（"张丽"，"24"，"女"，"班员"）；

118 st25.set（"郑丽"，"25"，"女"，"班员"）；

119 System.out.println（st1.tostring（））；

120 System.out.println（st2.tostring（））；

121 System.out.println（st3.tostring（））；

122 System.out.println（st4.tostring（））；

123 System.out.println（st5.tostring（））；

124 System.out.println（st6.tostring（））；

125 System.out.println（st7.tostring（））；

126 System.out.println（st8.tostring（））；

127 System.out.println（st9.tostring（））；

128 System.out.println（st10.tostring（））；

129 System.out.println（st11.tostring（））；

130 System.out.println（st12.tostring（））；

131 System.out.println（st13.tostring（））；

132 System.out.println（st14.tostring（））；

133 System.out.println（st15.tostring（））；

134 System.out.println（st16.tostring（））；

135 System.out.println（st17.tostring（））；

136 System.out.println（st18.tostring（））；

137 System.out.println（st19.tostring（））；

138 System.out.println（st20.tostring（））；

139 System.out.println（st21.tostring（））；

140 System.out.println（st22.tostring（））；

141 System.out.println（st23.tostring（））；

142 System.out.println（st24.tostring（））；

143 System.out.println（st25.tostring（））；

144 ///通过设置器给几个对象进行赋值

145 st1.setachi（87.5）；

146 st2.setachi（98）；

147 st3.setachi（78）；

148 st4.setachi（90）；

149 st5.setachi（84）；

150 st6.setachi（78）；

151 st7.setachi（91）；

152 st8.setachi（99.5）；

153 st9.setachi（64）；

154 st10.setachi（100）；

155 st11.setachi（98）；

156 st12.setachi（76）；

157 st13.setachi（88）；

158 st14.setachi（64）；

159 st15.setachi（97）；

160 st16.setachi（68）；

161 st17.setachi（90）；

162 st18.setachi（99）；

163 st19.setachi（77）；

164 st20.setachi（78）；

165 st21.setachi（67）；

166 st22.setachi（99）；

167 st23.setachi（97.5）；

168 st24.setachi（92）；

169 st25.setachi（88）；

170 st1.print（）；

171 st2.print（）；

172 st3.print（）；

173 st4.print（）；

174 st5.print（）；

175 st6.print（）；

176 st7.print（）；

177 st8.print（）；

178 st9.print（）；

179 st10.print（）；

180 st11.print（）；

181 st12.print（）；

182 st13.print（）；

183 st14.print（）；

184 st15.print（）；

185 st16.print（）；

186 st17.print（）；

187 st18.print（）；

188 st19.print（）；

189 st20.print（）；

190 st21.print（）；

191 st22.print（）；

192 st23.print（）；

193 st24.print（）；

194 st25.print（）；

195 ///通过循环语句对数组元素进行排序

196 for（int i=0；i＜st.length；i++）

197 {

198 for（int j=0；j＜st.length；j++）

199 {

200 ///通过比较两个元素的大小，如果前面比后面元素大，进行排序

201 if（st[i].achievement＜st[j].achievement）

202 {

203 student x；

204 x=st[j]；

205 st[j]=st[i]；

206 st[i]=x；

207 }

208 }

209 }

210 System.out.println（"成绩最好的是："+st[24].name+"，成绩是："+st[24].achievement）；

211 System.out.println（"成绩最差的是："+st[0].name+"，成绩是："+st[0].achievement）；

212 }

213 }

【代码说明】这个程序段的主要思路如下所示：

❑先提取一个类即学生类student。

❑根据学生类，创建对象。此时每个同学就是一个学生类的对象。

❑编写设置器和访问器（第25～45行）。（后面会详细讲述它）

❑输出学生信息（第119～143行）。

❑通过循环语句和判断语句对学生成绩进行排序（第196～212行）。

了解上面的思路后，整个程序就很清晰了。剩下的就是对代码的处理。

【运行结果】

学生　姓名：王浩；学生　学号：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；学生　性别：女；学生职务：班员

学生王浩的成绩是：87.5

学生李敏的成绩是：98.0

学生李杰的成绩是：78.0

学生王杰的成绩是：90.0

学生王超的成绩是：84.0

学生赵浩的成绩是：78.0

学生钱浩的成绩是：91.0

学生王松的成绩是：99.5

学生朱涛的成绩是：64.0

学生张杰的成绩是：100.0

学生王敏的成绩是：98.0

学生孙洁的成绩是：76.0

学生赵丽的成绩是：88.0

学生王丽的成绩是：64.0

学生钱珍的成绩是：97.0

学生王珍的成绩是：68.0

学生王萍的成绩是：90.0

学生钱萍的成绩是：99.0

学生王燕的成绩是：77.0

学生赵燕的成绩是：78.0

学生孙燕的成绩是：67.0

学生孙丽的成绩是：99.0

学生林丽的成绩是：97.5

学生张丽的成绩是：92.0

学生郑丽的成绩是：88.0

成绩最好的是：张杰，成绩是：100.0

成绩最差的是：朱涛，成绩是：64.0

7.4.2　设置器和访问器

在上面的程序段中，出现过“set”和“get”函数，那么这些函数起着什么作用？在Java语言中把“set”函数称为设置器，把“get”函数称为访问器。

访问器只查看对象的状态或者返回对象的属性值。访问器有以下特点：

❑方法声明部分有返回值类型。

❑方法声明没有参数。

❑方法体内有返回语句。

设置器主要是完成某个对象属性值的赋值功能。设置器有以下特点：

❑方法返回类型为void，即不返回类型。

❑方法声明中至少有一个参数。

❑方法体内肯定有赋值语句。

把上例中的设置器和访问器单独拿出来看看是否是具有这些特点。

01 void set（String name, String code, String sexy, String duty）

02 {

03 this.name=name；

04 this.code=code；

05 this.sexy=sexy；

06 this.duty=duty；

07 }

08 public String getname（）

09 {

10 return name；

11 12

}

13 public String getcode（）

14 {return code；

15 }

16 public String getsexy（）

17 {return sexy；

18 }

19 public String getduty（）

20 {return duty；}

21 public void setachi（double achievement）

22 {

23 this.achievement=achievement；

24 }

25 public double getachi（）

26 {

27 return achievement；

28 }

【代码说明】先观察这些“set”函数：“set”、“setachi”函数。“void set（String name, String code, String sexy, String duty）”带了4个参数，无返回值，存在4个赋值语句。“public void setachi（double achievement）”带了一个参数，无返回值，且存在一个赋值语句。

再来观察“get”函数：“getname”、“getcode”、“getduty”、“getsexy”、“getachi”。这5个访问器都是有返回值，不带任何参数，且方法体内有一个返回语句。

如果要实现一个对属性的访问和设置，一般应该有以下几项内容：

❑一个私有的字段变量。

❑一个公开的字段访问器。

❑一个公开的字段设置器。

7.4.3　总结

本章正式开始接触Java程序设计的特点，主要讲述了什么是类和如何提取一个类。在本章中举了一个现实生活中的程序开发的实例，并详细地分析了编写程序的主要思路。希望读者能熟练地掌握。其实想要掌握一门编程语言，最关键的就是要多多练习，正所谓熟能生巧。

7.5　常见疑难解答

7.5.1　类在程序语言中起到了什么作用

答：其实类的出现，将原先的过程化程序设计推到了面向对象编程，这是一个质的变化。类的出现，让程序都是以模块化结构来编写的，为程序员编写程序的思路清晰带来了很大的好处。

7.5.2　设置器和访问器的作用

答：设置器和访问器的作用是在创建对象后，为数据对象设置一些字段，主要是减轻构造器的负担。

7.6　小结

本章首先介绍了JDK提供的一些类库，这些类库可以方便我们快速地开发程序，因为其中封装了大量常用的方法和常量。7.4节通过一个完整实例，介绍了如何从现实项目中提取一个类，并为类设计各种字段和方法。尤其在类的设计过程中，重点提到了设置器和访问器，这非常关键。希望读者通过最后一个案例能掌握好类的应用。

7.7　习题

1.用来产生随机数的函数是什么？

2.使用Date类时，必须导入哪个类库？

3.Calendar类提供表示十二个月的参数，它们分别是什么？

4.类中的“set”和“get”函数起着什么作用？

第8章　对象和包

上一章介绍过对象的概念，并且提出了对象是通过类创造出来的，为什么呢？是如何创造出来的呢？本章将介绍对象和包的知识。通过对对象的讲述，希望读者能将对象和类作个对比。对象、包是Java程序设计的核心之一，也是面向对象程序设计与面向过程程序设计的最大不同点之一。

本章重点：

❑认识对象以及对象的成员。

❑了解访问控制符。

❑重载和包。

❑深入学习Java的注释。

8.1　对象

什么是对象呢？本节将会详细地讲述对象的概念、用法，以及如何创建它们。对象是一个比较抽象的概念。在这里将会结合实例进行讲述、分析，让读者能够从本质上很好地理解对象。

8.1.1　什么是对象

很多编程爱好者，包括有过很多年编程经验的人，对于对象的概念都是很模糊的，如果将对象的概念与现实生活中的实物相比，就会发现对象其实是很好理解的。

对象就是实际生活中的事物，可以说一切都是对象，在现实生活中时时刻刻都接触到对象这个概念，例如桌子、椅子、电脑、电视机、空调等。这些实物都可以说是对象。

对象有三个主要的特征：

❑对象行为：这个对象能做什么，即可以让这个对象完成什么样的功能，比如自行车可以载大家去想去的地方。

❑对象的状态：当操纵对象的方法时，对象所保持的一种特定的状态，比如可以扭转车头使得自行车转弯、刹车会使车停下来。

❑对象的标识符：可以通过标识符，区别具有相同行为或类似状态的对象，例如自行车的颜色、样式都是对象的标识符。通过它能区分不同的对象。例如红色的自行车、蓝色的自行车等，通过颜色这个标识符，可以区分两种不同自行车对象。代码如下所示，这里仅作演示。

01 class自行车

02 {

03 int color；-------------------------------对象的标识符

04 int material；----------------------------对象的标识符

05 int type；--------------------------------对象的标识符

06 int a------------------------------------对象的状态属性

07 void ride（）{……..}------------------对象的行为

08 void control（int a）----------------------对象的状态方法函数

09 {

10 if a=number1

11 转弯

12 else if a=number2

13 直行

14 else a=number3

15 停止

16 }

17 }

从以上例子可以清楚地看出对象到底是什么，下面将学习如何去操作对象。

8.1.2　操作对象

读者初步了解到，对象其实就是现实生活中的事物，现实生活中要经常去使用和操作这些事物，那么如何去操作这些事物（对象）呢？

针对这个问题，提出了一个概念，就是对象句柄。什么是对象句柄？为了能清楚地解释这个概念，先举个例子：使用汤勺去喝汤，汤勺的勺部分是用来盛汤的，汤勺把手部分是让大家能够操作汤勺的工具。对象句柄就相当于前面所说的汤勺把手，而汤勺就像前面所说的对象。

通过上面的例子可以总结出，对象句柄其实是一个指向对象所在内存地址的指针，如果要操作对象，只需要操作对象句柄，正如要想让汤勺能盛汤，只要握住汤勺把手的道理是一样的。

8.1.3　初始化对象

创建一个对象时，总希望它能立即被初始化，即立刻将其与相应的对象进行关联。在Java中，这是一个非常简单的工作。使用关键字“new”来完成对象句柄的命名，用“=”将对象句柄与对象关联。举例：

String str=new String（"Hello"）；

解释与分析：String是Java中用到最多的字符串对象。在这个例子中，先给对象句柄命名，并且声明这个对象句柄的类型是属于什么类型的对象，最后，将“Hello”这个字符串类型对象的内存地址赋给这个对象句柄。于是，对象句柄初始化的工作就算完成了。以后要操纵这个对象，只要操纵这个“str”对象句柄就可以了。

【实例8.1】下面看一个程序段。

01 public class object1

02 {

03 public static void main（String[]args）

04 {

05 object1 m=new object1（）；

06 }

07 }

【代码说明】以上这个例子中，通过第5行的代码，为“object1”类初始化一个“object1”对象“m”，而这个“m”就是前面提到的对象句柄。在学完本章的其他小节后，就可以通过对象句柄，来访问这个对象的方法函数和属性值。

【运行效果】本例没有输出任何内容。

真正开发的时候，就可以体会到使用对象句柄的优点在什么地方。对象初始化工作是非常重要的，它是从一个类中创造一个对象的过程。

注意

首先将对象句柄的类型声明好，再将“=”作为一个指向，指向由关键字所创建的新对象所存储的内存地址，以后，就可以通过这个对象句柄来操作这个新对象。一般情况下，是将对象句柄看成是新对象的替代物。

虽然对象句柄被看成对象的替代物，但必须将这两个不同的概念区分开来，因为真正要操作的是对象，而对象句柄只不过是一个指向对象所存储的内存地址指针，是一个操作对象的工具。其实对象句柄就好比是C++中的指针，但是在Java中不存在指针这个概念。

操作对象有什么用处呢？其实操作对象就是通过访问对象内的成员，来实现某种功能。那对象里面有些什么成员呢？下一节将更加深入地讲解对象中的成员。

8.2　对象家族中的成员

对象家族中有什么呢？有对象的成员方法和对象的成员变量，这些都有什么含义？如何使用它们？在本节中，将会通过实例详细地讲述。

8.2.1　对象的成员方法

方法就是能够让这个对象做什么，或者表现出什么状态的函数。举一个例子，有一个自行车的对象，如何让它停止、如何驾驶它，这些都是前面所提到的方法。方法就是其他编程语言中所提到的函数，而这个函数用来实现对象的某个功能，或表现出对象的某个状态。

什么是成员？成员的意思就是指它是属于对象的，就好比某个俱乐部的成员是属于这个俱乐部的一样。

将前面两个概念连在一起，就很清楚了。所谓的成员方法就是属于对象的，能够让对象做什么或表现出什么状态的函数。

【实例8.2】下面看一个实例。

01 public class object2

02 {

03 void print（）

04 {

05 System.out.println（"小明是一名优秀的程序员"）；

06 }

07 public static void main（String[]args）

08 {

09 object2 m=new object2（）；

10 m.print（）；

11 }

12 }

【代码说明】从这个程序段中，可以看到“print（）”函数其实就是“object2”这个类中的成员方法。要想使用这个成员方法，必须根据这个类“object2”构造出一个对象，再利用这个对象初始化一个对象句柄，这样就可以利用这个句柄来访问这个成员方法。

【运行效果】

小明是一名优秀的程序员

【实例8.3】下面再看一个复杂一点的有关成员方法的实例。

01 public class object3

02 {

03 public void print（）

04 {

05 for（int i=0；i＜=5；i++）

06 {

07 for（int j=5-i；j＞=0；j--）

08 {

09 System.out.print（"*"）；

10 }

11 System.out.println（）；

12 }

13 }

14 public static void main（String[]args）

15 {

16 object3 pro=new object3（）；

17 pro.print（）；

18 }

19 }

【代码说明】在这个程序中，构造了一个方法“print（）”，然后构造对象pro，通过pro来访问这个方法。【运行效果】

******

*****

****

***

**

*

【实例8.4】仔细来看看下面的程序，将上面程序改变一下。

01 public class object4

02 {

03 public static void main（String[]args）

04 {

05 for（int i=0；i＜=5；i++）

06 {

07 for（int j=5-i；j＞=0；j--）

08 {

09 System.out.print（"*"）；

10 }

11 System.out.println（）；

12 }

13 }

14 }

【代码说明】这段程序没有定义类中的方法“print（）”，而是直接输出内容。这是一种面向过程开发的程序。

【运行效果】

******

*****

****

***

**

*

这个程序是否比前面那个程序段读起来要费劲呢？而这个就是面向对象程序设计和面向过程程序设计的最大区别。面向对象的程序设计将这个程序中每个部件（包括成员方法），作为一个模块，等要使用它的时候，再随时调用它们，而面向过程的程序设计，将这个程序的需求按照顺序的步骤来编写。

下面再分析一下实例8.3中的代码，在面向对象的程序设计中，读者一定要学会一点，首先看主运行程序，也就是“public static void main（String[]args）{}”。在这个主运行程序中，看到了两句代码。一句是对象初始化语句，一句是利用对象引用成员方法的语句，这样读者就很清楚，这个程序要干什么，然后再仔细地看这个成员方法是干什么的，这样会使整个程序变得越来越轻松。

【实例8.5】成员方法越来越复杂。面向对象的程序设计的思路，会使得程序很容易读。下面再看一个更加复杂的程序，来体会使用成员方法的优点。

01 public class object5

02 {

03 public void print2（）

04 {

05 for（int i=5；i＞0；i--）

06 {

07 for（int k=0；k＜=（5-i）；k++）

08 {

09 System.out.print（""）；

10 }

11 for（int j=1；j＜=（2*i-1）；j++）

12 {

13 System.out.print（"*"）；

14 }

15 System.out.println（）；

16 }

17 }

18 public void print1（）

19 {

20 for（int i=1；i＜=5；i++）

21 {

22 for（int j=0；j＜=（5-i）；j++）

23 {

24 System.out.print（""）；

25 }

26 for（int k=1；k＜=（2*i-1）；k++）

27 {

28 System.out.print（"*"）；

29 }

30 System.out.println（）；

31 }

32 }

33 public static void main（String[]args）

34 {

35 object5 pro=new object5（）；

36 pro.print1（）；

37 pro.print2（）；

38 }

39 }

【代码说明】这个程序段看起来有点复杂，使用上面说的读程序的方法。首先看看主运行程序，在主运行程序中有3句：一句是对象初始化语句，一句是引用“print1（）”方法，最后一句是引用“print2（）”方法。然后再看“print1（）”和“print2（）”两个方法是如何实现的。至于这个方法的实现过程，应该很简单，只不过是一个多层for循环语句而已，读者可以自己分析它的实现过程。

【运行效果】

*

***

*****

*******

*********

*********

*******

*****

***

*

成员方法就是函数段，而函数段拥有自己的特性。在定义函数时必须遵照下面的结构：

返回值类型方法名（形式参数）

{

方法体

}

首先来讨论一下返回值类型。什么叫返回值？返回值就是一个函数结果所获得的值，将这个值返回到主程序中，并且将这个值作为主程序中运行的参数的值。

【实例8.6】下面看一个有关返回值的例子。

01 public class object6

02 {

03 int print（int x）

04 {

05 return 2*x；

06 }

07 public static void main（String[]args）

08 {

09 object6 pro=new object6（）；

10 System.out.println（pro.print（3））；

11 }

12 }

【代码说明】这个程序段中，第3行创建了一个“int print（int x）”方法，在这个方法中返回了“2*x”，将这个值带回到主运行程序中去，于是在主运行程序中，根据“pro.print（x）”中的参数“x=3”，得出这个方法的返回值是6。其实还有两个概念希望读者能清楚，就是实参和形参。刚才说的“int print（int x）”中的“x”就是形参，而主运行程序中相同函数的参数被称为实参。

【运行效果】

6

上面说到返回值，但有的函数方法是没有返回值的，那该如何处理呢？在程序设计中，如果一个方法函数没有返回值，那么可以将函数结构中的返回值类型写成“void”，这个就代表此函数无返回值。

【实例8.7】例如下面的程序段。

01 public class object7

02 {

03 void print（）

04 {

05 System.out.println（"我是一名程序员"）；

06 }

07 public static void main（String[]args）

08 {

09 object7 pro=new object7（）；

10 pro.print（）；

11 }

12 }

【代码说明】第3行定义了print（）方法，其只是输出一段内容，并不返回任何值，所以定义为void类型。第10行演示如何调用这个方法。

【运行效果】

我是一个名程序员

【实例8.8】上面的程序段就是一个无返回值类型的程序例子。为了加深印象，下面看一个详细的复杂的实例。

01 public class object8

02 {

03 public int sum（int x, int y, int z）

04 {

05 int sum；

06 return sum=x+y+z；

07 }

08 public int aver（int x, int y, int z）

09 {

10 int aver；

11 return aver=（x+y+z）/3；

12 }

13 void print（）

14 {

15 System.out.println（"这个就是这个同学的成绩"）；

16 }

17 public static void main（String[]args）

18 {

19 object8 num=new object8（）；

20 System.out.println（num.sum（90，80，70））；

21 System.out.println（num.aver（90，80，70））；

22 num.print（）；

23 }

24 }

【代码说明】以上的程序中包含无返回值的print（）方法（第13～16行），同时也包含了带返回值的方法sum（）和aver（）（第3～12行）。

【运行效果】

240 80

这个就是这个同学的成绩

8.2.2　对象的成员变量

成员变量是什么呢？理解了成员方法，一定可以知道成员变量是什么。成员变量就是对象所拥有的、并且代表对象的属性值，例如一个自行车对象，它的颜色、材料等都是自行车的属性值，也可以说是它的成员变量。

提起成员变量，就想到前面讲过的局部变量。其实这里说的成员变量相当于前面说过的类变量。它也可以不用初始化，系统自动给它赋值。局部变量就不能不初始化了，否则在编译的时候会报错，前面已经有过实例，此小节就不再赘述。

8.3　对象中访问控制符的重要性

在Java程序设计中，有一个很重要的知识点，就是访问控制符。不要看它的内容简单，但其重要性决定了程序的可运行性。

下面将讲述访问控制符，希望读者能够好好地掌握它的用法，它决定了一个程序的运行结果。甚至决定了一个程序是否能运行。

8.3.1　什么是访问控制符

访问控制符在Java语言中，有着举足轻重的作用。那么什么是访问控制符呢？所谓访问控制符，就是能够控制访问权限的关键字。在Java语言中的访问控制符有好几种，但是它又被分为不同的类别，具体的划分情况如下：

（1）出现在类之前的访问控制符：

❑public

❑default

（2）出现在成员变量与成员方法之前的访问控制符：

❑private

❑public

❑default

❑protected

下面将重点讲述这些访问控制符的作用和用法。

1.出现在成员变量与成员方法之前的访问控制符private

当用在成员变量和成员方法之前的访问控制符为private时，说明这个变量只能在类的内部被访问，类的外部是不能被访问的。其实当看到单词“private”时，就应该知道是私有的意思，所以private控制符就是指私有成员。

【实例8.9】看看下面这个有关private的实例。

01 public class object9

02 {

03 public static void main（String[]args）

04 {

05 pri1 p=new pri1（）；

06 p.print（）；

07 System.out.println（2*（p.x））；

08 }

09 }

10 class pri1

11 {

12 private int x；

13 private void print（）

14 {

15 System.out.println（"我是一名程序员"）；

16 }

17 }

【代码说明】这个程序段经过编译后，出现了错误，错误就是成员变量“x”和成员方法“print（）”都是属于“pri1”类私有的，不能被其他的类所使用。通过使用“private”控制符，将所有的成员数据都封装到了类里面，其他的类无法使用它们，也无法知道它们是如何实现的。这个就是本书后面要详细讲述的封装性。

【运行效果】编译错误，没有运行结果。

【实例8.10】下面再看一个有关访问控制符的实例。

01 class pri1

02 {

03 private int x；

04 void setX（int y）

05 {

06 x=y；

07 }

08 int getX（）

09 {

10 return x；

11 }

12 }

13 public class object10

14 {

15 public static void main（String[]args）

16 {

17 pri1 p=new pri1（）；

18 p.setX（3）；

19 System.out.println（p.getX（））；

20 }

21 }

【代码说明】这个程序段中，第3行的变量“x”是私有成员变量，外部类无法使用它，而成员方法不是私有的，所以外部类可以访问它，如代码第18行调用setX（3）方法，第19行调用getX（）方法。

【运行效果】

3

2.public控制符

public控制符是指所有类都可以访问。当成员数据前面加上了控制符“public”时，意味着这个成员数据将可以被所有的类访问。

【实例8.11】下面来看一个有关它的实例。

01 public class object11

02 {

03 public static void main（String[]args）

04 {

05 pri1 p=new pri1（）；

06 p.print（2）；

07 System.out.println（2*（p.x））；

08 }

09 }

10 class pri1

11 {

12 public int x=1；

13 public void print（int y）

14 {

15 System.out.println（2*y）；

16 }

17 }

【代码说明】第12行使用“public”控制符声明了一个变量x，第13～16行使用“public”控制符定义了一个方法print（int y）。

【运行效果】

4 2

从上面的程序段中，可以看出“public”控制符的用法，只要记住“public”控制符是任何类都可以使用的，而“private”控制符只能是声明它的那个类才能使用即可。

❑如果在成员数据的前面加上“default”控制符，那么就意味着只有同一个包中的类才能访问。

❑如果在成员数据的前面加上“protected”控制符，那就意味着不仅同一个包中的类可以访问，并且位于其他包中的子类也可以访问。

3.出现在类之前的访问控制符

❑当在一个类的前面加上“public”控制符，同前面一样，也是在所有类中可以访问。

❑当在一个类的前面加上“default”控制符，同前面一样，也是在同一包中的类可以访问。这个访问控制符是Java程序中默认的控制符。当在类前不加任何控制符时，默认就是“default”。

8.3.2　如何使用访问控制符及其重要性

本节继续讲述访问控制符，学习应该如何在程序中灵活运用这些访问控制符。访问控制符对于整个程序段是非常关键的，当需要让自己编写的这个类被所有的其他类所公共拥有时，可以将类的访问控制符写为“public”。当需要让自己的类只能被自己的包中的类所共同拥有时，就将类的访问控制符改为“default”。

另外，当需要访问一个类中的成员数据时，可以将这个类中的成员数据访问控制符设置为“public”、“default”和“protected”。至于使用哪一个，就要看哪些类需要访问这个类中的成员数据。

【实例8.12】下面看一个有关访问控制符的实例。

01 public class object12

02 {

03 public static void main（String[]args）

04 {

05 pro pro=new pro（）；

06 pro.print（）；

07 }

08 }

09 class pro

10 {

11 public void print（）

12 {

13 for（int i=1；i＜100；i++）

14 {

15 if（（i%3）==0＆＆（i%5）！=0＆＆（i%9）！=0）

16 System.out.print（i+""）；

17 }

18 }

19 }

【代码说明】第9～19行定义一个类pro，然后第6行调用类的print（）方法，从第11行可以知道，此方法被定义为“public”，所以其他类可以访问。

【运行效果】

3 6 12 21 24 33 39 42 48 51 57 66 69 78 84 87 93 96

从上面的程序段可以看出，当一个方法的访问控制符设置成“public”时，其他的类都可以访问它。

【实例8.13】下面将这个程序段修改一下，看看有什么结果。

01 public class object13

02 {

03 public static void main（String[]args）

04 {

05 pro pro=new pro（）；

06 pro.print（）；

07 }

08 }

09 class pro

10 {

11 private void print（）

12 {

13 for（int i=1；i＜100；i++）

14 {

15 if（（i%3）==0＆＆（i%5）！=0＆＆（i%9）！=0）

16 System.out.print（i+""）；

17 }

18 }

19 }

【代码说明】上面这个程序段在编译的时候，就会报错，错误就是“print（）”方法是类“pro”中私有的方法，是不能被其他类所访问的。

【运行效果】编译错误，没有结果。

从这两个程序中，读者应该能充分体会到“public”和“private”这两个访问控制符的使用环境。

8.4　重载

重载在整个Java语言设计中，有着非常重要的地位。本节将先讲述重载的概念，然后使用大量的实例，让读者更加清晰地熟悉重载，并且联系实际，将这个概念使用到现实生活中的程序开发中去，从而为以后的开发工作奠定基础。

8.4.1　什么是重载

在Java中，同一个类中的两个或两个以上的方法可以有同一个名字，只要它们的参数声明不同即可，这种方法就被称为重载（overloaded），此过程称为方法重载（method overloading）。

【实例8.14】下面看一个详细的实例。

01 class overload

02 {

03 ///一个普通的方法，不带参数

04 void test（）

05 {

06 System.out.println（"No parameters"）；

07 }

08 ///重载上面的方法，并且带了一个整型参数

09 void test（int a）

10 {

11 System.out.println（"a："+a）；

12 }

13 ///重载上面的方法，并且带了两个参数

14 void test（int a, int b）

15 {

16 System.out.println（"a and b："+a+""+b）；

17 }

18 ///重载上面的方法，并且带了一个双精度参数，与上面带一个参数的重载方法不一样

19 double test（double a）

20 {

21 System.out.println（"double a："+a）；

22 return a*a；

23 }

24 public static void main（String args[]）

25 {

26 overload o=new overload（）；

27 o.test（）；

28 o.test（2）；

29 o.test（2，3）；

30 o.test（2.0）；

31 }

32 }

【代码说明】第4～23行定义了4个相同名称的test（）方法，不同的是它们的参数，有没有参数的，有一个参数的，也有两个参数的，而且参数可以是不同的数据类型。第27～30行分别调用这4个方法。

【运行效果】

No parameters

a：2

a and b：2 3

double a：2.0

通过上面的实例，读者可以看出，重载就是在一个类中，有相同的函数名称，但形参不同的函数。重载的结果，可以让一个程序段，尽量减少代码和减少程序的函数种类。

8.4.2　用实例来说明重载的意义

【实例8.15】为了能让读者更加熟练运用重载方法编写程序，下面再举一个有关重载的实例。

01 public class overload1

02 {

03 ///一个输出方法

04 void print（）

05 {

06 System.out.println（"你好"）；

07 }

08 ///重载上面的输出方法，加上了参数

09 void print（String name）

10 {

11 System.out.println（name+"，你好"）；

12 }

13 ///重载上面的输出方法，加上了两个参数

14 void print（String name, int age）

15 {

16 System.out.println（name+"，你好，你有"+age+"岁了"）；

17 }

18 public static void main（String args[]）

19 {

20 overload1 o=new overload1（）；

21 o.print（）；

22 o.print（"王华"）；

23 o.print（"王华"，30）；

24 }

25 }

【代码说明】第4～17行定义了3个同名称print（）方法，不同的是参数个数和参数类型。第21～23行分别调用这3种方法。

【运行效果】

你好

王华，你好

王华，你好，你有30岁了

使用重载其实就是避免繁多的方法函数名，有些函数的功能是相似的，如果重新建立一个函数，重新取个函数名称，会让程序段显得比较不容易阅读。

下面总结重载的实质：

❑方法名相同

❑参数个数不同

❑参数类型不同

当访问一个重载方法时，首先编译器会通过比较参数类型与实际调用方法中使用值的类型，以选择正确的方法，若没有发现匹配的，则编译器报错。这就叫做重载分辨。

8.5　包

“包”机制是Java中特有的，也是Java中最基础的知识之一。一些初学Java的朋友，通常像学其他语言一样从教材上“copy”一些程序来运行，可是却常常遇到莫名其妙的错误提示，这些问题事实上都是对“包”的原理不理解。本节将就此问题进行深入阐述。

8.5.1　什么是Java中的包

在Java语言中，为了开发方便，会将多个功能相似的类放到一个组内，而这个组就是“包”，包就像一个目录结构。

先来观察目录结构。目录的结构分为目录、子目录和文件。在操作系统中，如何表示一个文件的目录结构呢？先看一个有关文件目录结构的例子。

D：\Java\wp.doc

其实包的表示有点像文件的目录结构。

例如：java.wp。

下面来分析一下上面的代码，“java”就是包名称，而“wp”就是类名称。

包就是将一些类组合在一起。例如，在一个Java包中有wp类、wp1类、wp2类等。那么如何来使用它们？为什么要使用它们？下一节将会详细地介绍。

8.5.2　如何实现包

在Java中要想使用包，必须先声明一个包，而声明一个包必须使用关键字“package”。具体如下所示：

package java.wp

【实例8.16】声明一个包时，声明语句必须放在类所有语句的最前面。下面先看一个实例。

01 public class package1

02 {

03 public static void main（String[]args）

04 {

05 System.out.println（new Date（））；

06 }

07 }

【代码说明】

编辑出错，系统提示找不到类Date。在Java中调用其他包中的公用类，可以使用两种方式：

（1）在每个类名前加上完整的包名。具体如下所示。

java.util.Date today=new java.util.Date（）；

这样的对象实例化看起来与以前用的“Date today=new Date（）”有很大区别，就是在类名称前面加上了包的名字。

【实例8.17】再把刚才那个程序段修改，如下所示。

01 public class package1

02 {

03 public static void main（String[]args）

04 {

05 System.out.println（new java.util.Date（））；

06 }

07 }

【代码说明】这次编译器编译通过了，因为第5行指定了对象所在的包。

【运行效果】

Thu Aug 27 22：28：38 CST 2009

（2）通过引入特定的类。在一个类中引入特定的类通过关键字“import”来实现。

【实例8.18】下面仍旧将上面的例子修改一下。

01 import java.util.Date；

02 public class package2

03 {

04 public static void main（String[]args）

05 {

06 System.out.println（new Date（））；

07 }

08 }

【代码说明】这个程序段通过第1行引入特定的类的方式，首先将类直接引入，那么系统编译时，就会先调用这个类，这样编译时就不会报错。

【运行效果】同上例相同。

平时在编写程序时，没有必要把要引入的类写得那么详细，可以直接引入特定包中所有的类。

【实例8.19】例如下面的例子。

01 import java.util.*；

02 public class package3

03 {

04 public static void main（String[]args）

05 {

06 System.out.println（new Date（））；

07 }

08 }

【代码说明】第1行的java.util.*最后通过*来导入java.util包中所有的类。

【运行效果】同上例相同。

8.5.3　什么是类路径和默认包

在编程时，也可以不使用关键字“import”来导入包和类，这是为什么？为什么有的时候要导入，而有的时候不用导入？

Java虚拟机在运行时，系统会自动导入“java.lang”包，只要程序用到这个包的类，不需要导入，因为系统自动为程序员导入了，就像在这个包内编写程序段一样。除了“java.lang”包外，要使用其他的包时，都必须手工导入。

由于“java.lang”这个包由系统自动导入，所以称这个包为系统的默认包。类路径是什么呢？前面在配置Java编程环境时，配置了类路径。类路径就是能自动让系统找到程序员需要导入的类，所以在配置Java编程环境中，配置类路径是非常关键的。

8.5.4　包的作用域

在前面提到过“public”和“private”访问控制符，被声明为“public”的类、方法或成员变量，可以被任何类使用，而声明为“private”的类、方法或成员变量，只能被本类使用。当没有指定“public”或“private”的部件时，只能被本包中的所有方法访问，在包以外的任何方法都无法访问它。

8.6　注释及嵌入文档

注释对于读懂程序起着不可缺少的作用，一个程序的可读性是否好，关键就是注释。对于一个程序的二次开发而言，要想读懂前面的程序代码，首先要在程序中有大量的注释文档。

本节将介绍如何对代码进行注释。通过实例来证明注释在开发工作中的重要地位。前面曾经提到过注释，读者也许印象不太深，这里也算复习一遍。

8.6.1　如何添加注释

如何添加注释呢？一般使用下面的方法。

/**

包名引用

*/

这个是类和包、方法的注释，下面将详细地介绍这些注释。

8.6.2　类、方法等注释的方法

在这一小节中，将简单地介绍类、方法、字段等的注释方法，虽简单但是很重要。

1.类注释

类注释一般必须放在所有的“import”语句之后，紧靠在类声明之前。

/**

this is the student class.

it includes the student name.

*/

public class student

{

……

}

2.方法注释

方法注释必须紧靠在方法声明的前面，除了/**……*/标签外，还可以使用下列的以@开始的标签。

❑@param变量描述

❑@return返回类型描述

❑@throws异常类描述

如果以上的注释都没有，把程序拿给别人看，别人会轻易看懂吗？如果过了很长时间以后，自己再来看看这些程序，可能也已经忘记这些代码是做什么的了。

所以说，注释对于程序的可读性来说，是非常重要的，希望读者不要忽视它，以免造成无可挽回的损失。

8.7　常见疑难解答

8.7.1　包在实际编程中究竟有什么作用

答：现在读者可能还不会很清楚地知道包的作用，等学到后面，一定能够理解。先观察包里面有什么，其实包里面存放的是一些方法和类、接口等，这些都是在编写程序时，需要调用的，例如要编写SQL数据库程序，那么就要调用包含SQL的包。

8.7.2　访问控制符在实际开发中有什么作用

答：在实际开发工作中，有一个很重要的概念“封装”。这个封装涉及访问控制符。另外，当一个包的方法需要调用另一个包的方法时，也涉及访问控制符。所以说访问控制符贯穿了整个程序开发，起着重要的作用。

8.7.3　一个文件中定义了两个class类是否生成一个.class文件

答：本章中很多例子都将多个类放在一个.java文件中，那么使用javac编译后，是生成一个.class文件，还是多个呢？读者通过练习可能已经知道了结果，即生成多个.class文件。

8.8　小结

前一章大概介绍了一下类，类是一组对象的集合。本章开始就让读者先认识什么是对象，然后对比了现实生活中的对象和程序中的对象，主要是加深读者的印象。对象具备对象的成员属性和成员方法，读者一定要明白什么是方法，什么是属性。本章后面介绍了重载和包，这也是Java程序中随处可见的知识点。希望读者通过本章的学习，了解并会使用对象编程。

8.9　习题

1.钥匙、车子可不可以抽象为对象？

2.公开访问的修饰符是什么？

3.protected有什么特点？

4.用什么关键字来声明一个静态变量？静态变量有什么特点？

5.创建对象的关键字是什么？

第9章　继承

本章重点通过对比的方法讲述继承的概念。在面向对象的程序设计中，继承是一个不可分割的重要组成部分，没有使用继承的类的程序设计，就不能称为面向对象的程序设计。继承的重要性和特殊性可以通过本章的学习中得以领会。

本章重点：

❑类的继承。

❑什么是构造器。

❑类之间的关系。

❑类的层次。

9.1　什么是继承

继承和现实生活中的“继承”有相似之处，都是保留一些父级的特性。本节将通过对比两个不同领域中相同的词语，来真正地理解继承这个概念。

9.1.1　继承的引出

看到“继承”这个词，会想起一句俗语：子承父业。所谓“子承父业”，就是晚辈继承父辈的事业及财产，这就是现实生活中所说的继承权。而在Java语言中，也有继承，其意义跟现实生活中的继承十分相似。下面来分析这个概念。

在现实生活中，继承应该具备两个必要的条件：一是必须要有父辈，如果没有父辈，那继承谁呢？二是必须要有子辈，如果没有子辈，那么由谁来继承呢？同样的道理，在Java语言中，继承是针对类来说的。既然生活中的继承要有父辈和子辈，那么Java中也得有父类和子类。继承的原理如图9.1所示。

图　9.1　生活中和程序中的继承

9.1.2　继承的概念

继承，就是在已经存在的类的基础上，再进行扩展，从而产生新的类。已经存在的类称为父类、超类或基类，而新产生的类称为子类或派生类。

既然有了继承的双方，那么要继承什么呢？在现实生活中，要继承父辈，可以继承属于父辈的所有东西。在Java中，父类所拥有的一切，子类都可以继承（初学者可以这么理解，但这句话并不算精确）。父类拥有自己的属性字段和方法函数，这些子类都可以继承。子类继承了父类所有的属性和方法，就可以使用它们。另外，子类除了拥有父类的属性和方法，也可以创建自己的特性。根据这些特性，总结出继承的关系图，如图9.2所示。

图　9.2　继承图

当遇到一种类包含另一种类的属性、方法时，就可以使用继承父类的方式来创建子类，无须重新创建一个重复的类。这样不但可以减少代码，而且易于维护，还可以更加直观地体现出面向对象程序设计的思路。这就是Java程序设计，准确地说就是面向对象程序设计的一个特色。

为了能够更好地说明继承的概念，下面举一个实际生活中有关汽车类的例子：

要开发一个有关汽车的Java程序，首先会建立一个汽车的父类，此类拥有一些属性字段和方法函数。当要设计一个具体的厂家的汽车时，可以建立这个厂家自己的汽车类，让这个类作为汽车父类的子类，即让它能拥有汽车父类的所有属性和方法。那么在子类的代码中，就无须书写父类中已经存在的属性和方法。在子类中，还可以将这个厂家汽车自身的特点，以新属性和方法的方式列进子类中。整个汽车类的继承关系如图9.3所示。

注意　父类无法使用子类独有的属性和方法。

图　9.3　汽车类继承图

下面就是这个汽车类实例的一段抽象代码。

汽车类

{

汽车有四个轮子属性

引擎属性

方向盘属性

如何驾驶它（）-----------方法函数

如何保养它（）-----------方法函数

}

宝马厂家的敞篷跑车继承汽车类

{

如何打开敞篷-----------方法函数

}

这段代码中，父类拥有的属性，子类通过继承也拥有了。而子类有一个自己的方法“如何打开敞篷”，它只属于子类，父类无法使用。父类对象可以使用“宝马厂家的敞篷跑车.引擎”，或者“宝马厂家的敞篷跑车.如何保养它（）”，但是绝对不能使用“汽车类.如何打开敞篷（）”这个方法。

9.2　如何实现继承

如何知道一个类是继承了父类的子类呢？在Java语言中，继承通过关键字“extends”来实现。用extends标明当前类是子类，并标明其是从哪个类继承而来。“extends”在英语中就是扩展的意思，而在Java语言中，继承也有扩展的含义。这样将两者联系在一起，就很容易理解了。

现在将上面的代码修改一下，就可以清楚地看到父类和子类的继承关系：

汽车类

{

汽车有四个轮子属性

引擎属性

方向盘属性

如何驾驶它（）-----------方法函数

如何保养它（）-----------方法函数

}

宝马厂家的敞篷跑车extends汽车类

{

如何打开敞篷--------方法函数

}

这样是否更清楚了？下面举个实例来具体分析继承的意义。

01 class Person

02 {

03 int age；------------------------------年龄属性

04 int height；---------------------------身高属性

05 void eat（）{}；------------------------吃的方法函数

06 void sleep（）{}；----------------------睡觉的方法函数

07 }

08 class Student

09 {

10 int age；------------------------------年龄属性

11 int height；---------------------------身高属性

12 void eat（）{}；------------------------吃的方法函数

13 void sleep（）{}；----------------------睡觉的方法函数

14 int score；----------------------------学生分数属性

15 void study（）{}；----------------------学生学习的方法函数

16 }

从以上的代码段可以看出，“Person”类和“Student”类有很多参数相同。可以说，“Student”类包含了“Person”类所有的参数，这种情况，就需要使用继承。上面的代码可以修改为：

01 class Person

02 {

03 int age；

04 int height；

05 void eat（）{}；

06 void sleep（）{}；

07 }

08 class Student extends Person

09 {

10 int score；

11 void study（）{}；

12 }

上面的代码使“Student”类继承了“Person”类，并拥有了“Person”类的所有成员。“Student”类虽然代码很少，但其包括了“Person”类的所有成员和方法。

【实例9.1】下面学习一段继承类的引用实例。

01 class inhert

02 {

03 int a；

04 void hi（）

05 {

06 System.out.println（"大家好，我是有关继承的程序段"）；

07 }

08 }

09 public class inhert1 extends inhert

10 {

11 public static void main（String[]args）

12 {

13 inhert ob=new inhert（）；

14 ob.a=10；

15 ob.hi（）；

16 }

17 }

【代码说明】第1～8行定义了类inhert，其具备一个变量a和一个方法hi（）。第14行分别设置变量的值和调用方法。

【运行效果】

大家好，我是有关继承的程序段

通过上述代码可以实现继承，并调用子类，但是父类和子类的对象是如何形成的呢？这就涉及构造器的使用，下一节会详细地介绍。

9.3　构造器的使用

一个类会用什么方式来创建一个对象？回答是：构造器。那什么是构造器？如何使用构造器？本节将详细地回答这两个问题。一定要注意，构造器贯穿了整个Java程序开发，是开发中不可缺少的理念。

9.3.1　什么是构造器

也有很多书籍把构造器称为构造函数，下面将详细地讲解构造器的知识。

前面学习过类，类就是一类事务的模板。如果要用这个模板来构造新对象，就要使用构造器在类中构造对象。举个构造器的例子，代码段如下：

class Person

{

Person（）{}；

}

“Person（）{}”就是类“Person”的一个构造器，使用它来构造对象。在实际开发中，往往不需要书写构造器，因为它是系统默认的，可写可不写。但系统默认的构造器不带参数，如果需要带参数，就必须自己书写构造器。

构造器的名称与类的名称一样，这就是构造器能区别于类内其他函数的标志。再举个有关构造器的例子：

01 class Color

02 {

03 Color（）{}；-----------------可以省略不写

04 Color（int red）{}；

05 Color（int red, int green）{}；

06 }

如果要使用构造器构造对象，就需要使用new关键字，如下面的代码段：

01 Color col=new Color（）；

02 Color col=new Color（int red）；

03 Color col=new Color（int red, int green）；

这个例子使用了3个不同的构造器，可以构造出3个不同的对象：不带参数、带一个参数和带两个参数，这就是构造器重载。

根据上面所述，对构造器的概念总结如下：

❑构造器与类名相同（包括大小写）。

❑一个类可以有多个不同的构造器。

❑构造器没有返回值，但不用写void关键字。

❑构造器总是和new运算符一起被调用。

通过以上的学习，思考一个问题：子类的对象形成与父类有什么联系？

9.3.2　继承中构造器的初始化

创建类对象时，系统会调用构造器对其所属成员进行初始化，那么针对那些继承自父类的成员又该如何初始化呢？

实际上，在创建子类对象时，先执行父类的构造器，然后执行子类的构造器，最后完成对象的创建。正如先存在父母，然后才有子女一样。在创建子类对象时，会先调用父类构造器，初始化继承自父类的成员，随后，调用子类构造器初始化子类的成员。

【实例9.2】举个有关构造器的例子，代码如下所示。

01 class Person //父类

02 {

03 int a；

04 Person（） //父类的构造器

05 {

06 a=10；

07 System.out.println（"这个是父类的构造器"）；

08 }

09 }

10 public class men extends Person //子类

11 {

12 int b；

13 men（） //子类的构造器

14 {

15 //super（） //隐藏的

16 b=20；

17 System.out.println（"这个是子类的构造器"）；

18 }

19 public static void main（String[]args）

20 {

21 men m=new men（）；

22 System.out.println（m.a+""+m.b）；

23 }

24 }

【代码说明】在创建“men”子类时，会先调用父类的构造器，正如运行的结果一样，先显示“这个是父类的构造器”，然后显示“这个是子类的构造器”。

【运行效果】

这个是父类的构造器

这个是子类的构造器

10 20

9.3.3　替代父类和本身的方式

上述代码中有一个“super（）”函数，本节将详细介绍此函数的意义。“super”代表父类，而“super（）”则代表父类的构造器，可以使用“super”代替父类中的属性或方法。提到用“super”来代替父类，则会想到如何使用相似的方法来代替类本身呢？

在Java中，使用“this”来代替引用对象自身，同样“this（）”用来代替对象的构造器。

【实例9.3】下面的例子可以验证这种理论。

01 class Person

02 {

03 int a；

04 Person（）

05 {

06 a=1；

07 }

08 Person（int a）

09 {

10 this.a=a；

11 }

12 }

13 public class men1 extends Person

14 {

15 int b；

16 men1（int a, int b）

17 {

18 super（a）；

19 this.b=b；

20 }

21 public static void main（String[]args）

22 {

23 men1 m=new men1（10，20）；

24 System.out.println（m.a+""+m.b）；

25 }

26 }

【代码说明】

（1）当创建“men1”类的对象“m”时，先调用父类带参数的构造器，“Person（int a）{}”将变量“a”值传给父类的属性字段“a”。

（2）调用子类的带两个参数的构造器，在子类构造器中，将两个参数中的一个参数传给父类构造器，于是“a”的值就是子类对象中那个要传给父类的值，即“men1 m=new men1（10，20）”中的参数10。

【运行效果】

10 20

通过以上的例子，应该能很清楚地知道“this”和“super”的特殊用法，在定义构造器时，应将调用父类构造器的代码“super（）”置于调用父类构造器的方法内部的顶端，因为必须首先初始化继承自父类的成员。

9.3.4　Java中的单继承性

Java是单继承的，在说明单继承概念之前，先看看什么是多继承。多继承是指某个类可以继承多个类，但这会导致继承出现混乱，所以Java规定一个类只能继承一个父类，这就是单继承。

单继承也有它的不足之处，即如果一个类继承另一个类，就不可能再继承其他的类。如何解决这个问题呢？后面的章节中将会详细介绍。

9.4　继承中的覆盖现象

【实例9.4】继承是子类拥有父类所有的资源，先来看下面一个例子：

01 ///定义了一个父类

02 class parent

03 {

04 int a；

05 void f（int a）{……}；

06 private int g（int a, int b）{……}

07 }

08 ///定义了一个子类，继承上面的父类

09 ///在子类中覆盖了父类的g方法

10 class child extends parent

11 {

12 int b；

13 void f（int a）{……}

14 public int g（int a, int b）{……}

15 }

【代码说明】“public”控制符的权限比“private”大，针对同一方法函数，在子类中的权限比父类中大。

【运行效果】这个程序不算完整，没有运行结果，读者可以自己设计需要打印的内容。

注意　子类的访问控制符权限只能等于或大于父类。

9.5　类之间的关系

类与类之间最常见的关系主要有以下三种：

❑依赖（或uses-a）。

❑聚合（或has-a）。

❑继承（或is-a）。

下面以在线订单系统为例，来详细地讲述这三种关系的概念。

这个系统的主要功能是：注册用户可以登录到网上书店并选购图书。在线填写订单，并支付购书款。书店确认已经收到购书款时，按用户留下的地址邮寄图书。可以在这个系统中建立几个类，有图书（book）、账户（account）、订单（order）、地址（address）等，如图9.4所示。

图　9.4　在线订单系统结构图

9.5.1　依赖

依赖关系是类中最常见的关系，例如订单类（order）需要访问用户账户类（account），所以在订单类中需要引用账户类，即订单类依赖账户类，但图书类不需要依赖账户类。

如果修改账户类，会影响到订单类。依赖的实质就是类中的方法可以操作另一个类的实例。在实际程序设计中，建议尽量减少相互依赖类的数量，如图9.5所示。

9.5.2　聚合

图　9.5　在线订单系统依赖关系图

因为订单需要指明订购什么图书，这就涉及图书类，即包含了图书类。聚合与依赖关系的不同在于，订单类可以不拥有所有账户类对象，但是必须拥有所有图书类对象，因为图书类的对象是订单的主要目的，如图9.6所示。

图　9.6　在线订单系统聚合关系图

9.5.3　继承

继承就是一个类能调用另一个类的所有方法和属性，并在当前类中不需要再重新定义这些方法和属性。继承的关系如图9.2所示。

9.6　继承层次图

类可以实现多层继承。多层继承就是一个类的子类还可以有子类，其原理如图9.7所示。

通过继承层次图，可以更加清楚地理解类之间的关系。随着以后的学习，读者会接触更多的类，而且类之间的相互关系也会越来越复杂。所以，读者可以尝试画一些类的继承层次图，便于更深入地理解类之间的相互关系。

图　9.7　动物类的类层次图

9.7　如何设计好继承

对于如何设计好类的继承，笔者根据长期的开发经验，有以下几点建议：

❑把通用操作与方法放到父类中，因为一个父类可以有好几个子类。如果是通用的操作，放到父类中，带来的好处是多方面的：一是避免代码重复，二是避免了人为因素导致不一致。

❑不要使用受保护字段，也就是protected字段。

❑尽管类的继承给开发带来了好处和方便，但如果不希望自己的类再被扩展，也就是不希望再产生子类时，可在类的声明之前加上final关键字，这样此类就不能再被继承。

9.8　常见疑难解答

9.8.1　Java不支持多继承，如何处理一个类继承多个父类的情况

答：在C++中支持一个类的多继承，而在Java中不支持，那么遇到需要继承多个类的情况时，Java通过实现接口的方式来解决这个问题。至于接口的概念，后面章节会详细讲述。

9.8.2　如果出现了带参数的构造器，可否不在代码中写出来

答：不行。因为系统默认的构造器是不带参数的，如果带参数的构造器不在代码中书写出来，会造成整个程序的混乱。

9.9　小结

本章主要介绍了类的标准特性：继承。面向对象之所以称为一种流行的编程思想，就因为一个类被定义后，可以被所有人使用，而且，还可以继承这个类，延伸出更多相似功能的类。类很重要的一点就是构造器，本章也介绍了构造器的使用。本章最后介绍了类之间的关系，读者一定要自己设计一个类，并理清类和继承类之间的层次。

9.10　习题

1.构造方法有什么特征？起到什么作用？

2.Java是否支持多继承？

3.private声明的方法和属性会不会被继承？

4.重载和覆盖有什么区别？

第10章　多态

本章要介绍多态的概念和多态的用法。实际讲解时，仍然会围绕一个如何理清编程思路的问题，讲述如何使用多态。同时通过对大量程序实例的分析，让读者领会实际开发中如何实现类的多态。多态也是面向对象程序语言的核心知识，读者一定要熟悉前面的知识，然后再学习本章的内容。

本章重点：

❑什么是多态。

❑动态绑定和静态绑定。

❑超类。

10.1　多态的产生

程序员在开发过程中，需要在代码中编写很多方法函数。其实很多方法函数的形式差不多，例如，现实开发中可能会出现下列代码。

01 ///这里是带一个参数的print方法

02 void print1（int x）

03 {

04 System.out.println（2*x）；

05 }

06 ///这里是带两个参数的print方法

07 void print2（int x, int y）

08 {

09 System.out.println（2*x+y）；

10 }

上面的例子中，其实两个方法函数的形式都一样，只不过参数的形式不同。如果所有的方法函数都像上面的实例一样，那么整个程序中会出现很多的方法函数名字。对于程序员来说，在调用方法函数时，很容易出错。

【实例10.1】下面来举一个实际的程序代码段。

01 ///创建一个学生类

02 ///创建了一个print方法

03 ///创建了一个带两个整型参数的print方法

04 ///创建了一个带三个整型参数的print方法

05 ///创建了一个具有一个double型数据的方法

06 ///创建了一个具有两个double型数据的方法

07 ///创建了一个具有三个double型数据的方法

08 ///以上所有方法均遵从多态机制

09 public class student

10 {

11 int x；

12 int y；

13 int z；

14 void print1（int x）

15 {

16 System.out.println（2*x）；

17 }

18 void print2（int x, int y）

19 {

20 System.out.println（2*x+y）；

21 }

22 void print3（int x, int y, int z）

23 {

24 System.out.println（2*x+y*z）；

25 }

26 void print4（double a）

27 {

28 System.out.println（2*a）；

29 }

30 void print5（double a, double b）

31 {

32 System.out.println（2*a+b）；

33 }

34 void print6（double a, double b, double c）

35 {

36 System.out.println（2*a+b+c）；

37 }

38 public static void main（String[]args）

39 {

40 student st=new student（）；

41 st.print1（2）；

42 st.print2（2，3）；

43 st.print3（2，3，5）；

44 st.print4（1.1）；

45 st.print5（1.1，2.2）；

46 st.print6（1.1，2.2，3.3）；

47 }

48 }

【代码说明】在上面的程序段中，第14～37行有6个输出函数，当要调用时，很容易搞错。这种情况下，Java语言就引入了一个概念：多态。下一节将详细介绍什么是多态。

【运行效果】

4 7

19 2.2

4.4

7.7

10.2　多态的概念

多态就是拥有多种形态。在Java语言中，多态主要是拥有相同的形式，但不同的参数却实现不同的功能。

【实例10.2】这很像前面学习过的重载，下面先看一个实例。

01 ///创建一个学生类

02 ///在学生类中创建几个带不同参数的构造器

03 public class student

04 {

05 student（）

06 {}

07 student（string name）

08 {}

09 student（string name, string code）

10 {}

11 }

【代码说明】以上实例中有3个构造器的重载函数，这3个函数有着相同的形态。但是它们根据参数的不同，实现的功能也不同。这就是多态，所以说重载其实具有多态性，简单地说，重载是多态的一种形式。

【运行效果】这里仅是复习重载，没有任何输出结果。

10.2.1　使用多态编写程序

【实例10.3】为了作个比较，把10.1节中的实例使用多态的形式重新编写。

01 ///创建一个学生类

02 ///创建了一个print方法

03 ///创建了一个带两个整型参数的print方法

04 ///创建了一个带三个整型参数的print方法

05 ///创建了一个具有一个double型数据的方法

06 ///创建了一个具有两个double型数据的方法

07 ///创建了一个具有三个double型数据的方法

08 ///以上所有的方法均遵从多态机制

09 public class student1

10 {

11 int x；

12 int y；

13 int z；

14 void print（int x）

15 {

16 System.out.println（2*x）；

17 }

18 void print（int x, int y）

19 {

20 System.out.println（2*x+y）；

21 }

22 void print（int x, int y, int z）

23 {

24 System.out.println（2*x+y*z）；

25 }

26 void print（double a）

27 {

28 System.out.println（2*a）；

29 }

30 void print（double a, double b）

31 {

32 System.out.println（2*a+b）；

33 }

34 void print（double a, double b, double c）

35 {

36 System.out.println（2*a+b+c）；

37 }

38 public static void main（String[]args）

39 {

40 student1 st=new student1（）；

41 st.print（2）；

42 st.print（2，3）；

43 st.print（2，3，5）；

44 st.print（1.1）；

45 st.print（1.1，2.2）；

46 st.print（1.1，2.2，3.3）；

47 }

48 }

【代码说明】第14～37行定义了6个相同名称的print（）方法，但它们都具备不同的参数类型或参数个数。第41～46行调用这些方法，系统通过参数类型和参数个数来判断究竟调用哪个方法。

【运行效果】

4 7

19 2.2

4.4

7.7

以上参数涉及的是基本数据类型，下面将讲述参数是对象类型的数据。对象数据的操作通过操纵对象句柄来实现。程序操纵对象句柄，即内存地址，而实际对象是不会被改变的。

在父类中声明并在子类中被覆盖的方法，可以实现多态调用。编译器会自动寻找到正确的类型。如果在父类中没有声明的方法，在子类中声明了，此方法就不能实现多态。

10.2.2　覆盖的应用

【实例10.4】通过下面一个实例，来学习覆盖的应用。

01 public class student2

02 {

03 void print（）

04 {

05 System.out.println（"这是小明的同学"）；

06 }

07 public static void main（String[]args）

08 {

09 student2 st=new student2（）；

10 student3 st1=new student3（）；

11 st.print（）；

12 st1.print（）；

13 }

14 }

15 class student3 extends student2

16 {

17 void print（）

18 {

19 System.out.println（"这是小明的同学1"）；

20 System.out.println（"他很优秀的"）；

21 }

22 }

【代码说明】从上面的程序可以看出，子类重新定义了父类的“print（）”方法。子类的覆盖方法与父类相同，只是方法体不一样。

【运行效果】

这是小明的同学

这是小明的同学1

他很优秀的

10.2.3　重载与覆盖的实例对比

【实例10.5】重载则是方法的参数不一样，下面是有关重载与覆盖的比较。

01 ///创建一个学生的主运行类

02 ///将三个带不同参数的方法运行

03 public class student4

04 {

05 void print（）

06 {

07 System.out.println（"这是我的同学"）；

08 }

09 void print（String name）

10 {

11 System.out.println（"这是小明的同学"+name）；

12 }

13 public static void main（String[]args）

14 {

15 student4 st=new student4（）；

16 student5 st1=new student5（）；

17 st.print（）；

18 st.print（"tom"）；

19 st1.print（）；

20 }

21 }

22 ///学生类5继承学生类4

23 class student5 extends student4

24 {

25 void print（）

26 {

27 System.out.println（"这是小明的同学"）；

28 System.out.println（"他很优秀的"）；

29 }

30 }

【代码说明】以上程序段主要说明了重载和覆盖的一个区别，“void print（）”与“void print（string name）”属于重载，而父类的“print（）”和子类的“print（）”属于覆盖。

说明　多态有两种表现形式（重载和覆盖）。

【运行效果】

这是我的同学

这是小明的同学tom

这是小明的同学

他很优秀的

10.2.4　覆盖的多态性

覆盖为什么也具有多态性？因为父类的方法在子类中被重写。多态就是拥有多种形态，子类和父类的方法名称相同，只不过完成的功能不一样，所以说覆盖也具有多态性。

【实例10.6】通过上面的讲述，读者应该对多态有了很清晰的了解。下面再看一个多态的实例，主要是针对对象型的数据。

01 ///一个主运行类

02 ///创建一个父亲类型并且指向子类对象的对象句柄

03 ///运行这个句柄所指对象的方法

04 public class test

05 {

06 public static void main（String[]args）

07 {

08 father f=new son（）；

09 f.print（）；

10 }

11 }

12 ///创建一个父亲类

13 class father

14 {

15 public void print（）

16 {

17 System.out.println（"这是父亲的函数"）；

18 }

19 }

20 ///让儿子继承父亲类

21 class son extends father

22 {

23 public void print（）

24 {

25 System.out.println（"这是儿子的函数"）；

26 }

27 public void print1（）

28 {

29 System.out.println（"这是儿子的另一个函数"）；

30 }

31 }

【代码说明】以上程序段最关键的语句是第8行的“father f=new son（）”，其将父类对象句柄指向了子类对象，实际上操作的还是子类对象，只不过将对象句柄声明为父类的数据类型。“f.print（）”由编译器根据实际情况选择了子类的“print（）”函数，所以输出的是子类的函数。

【运行效果】

这是儿子的函数

【实例10.7】将这个程序段修改后，再观察这个程序段的运行结果。

01 //一个主运行类

02 ///创建一个父亲类型并且指向子类对象的对象句柄

03 ///运行这个句柄所指对象的方法

04 public class test1

05 {

06 public static void main（String[]args）

07 {

08 father f=new son（）；

09 f.print1（）；

10 }

11 }

12 class father

13 {

14 public void print（）

15 {

16 System.out.println（"这是父亲的函数"）；

17 }

18 }

19 class son extends father

20 {

21 public void print（）

22 {

23 System.out.println（"这是儿子的函数"）；

24 }

25 public void print1（）

26 {

27 System.out.println（"这是儿子的另一个函数"）；

28 }

29 }

【代码说明】这个程序没有通过编译，因为“f”是父类的数据类型，那么在父类中没有“print1（）”方法，只有“print（）”方法。又由于这个句柄指向子类对象，所以其只能操作子类中覆盖父类的方法。

【运行效果】编译错误，没有结果。

如果父类引用指向一个子类的对象，那么通过父类引用，只能调用父类所定义的允许继承的方法。如果子类重写了继承父类的方法，那么会调用子类中的方法。这些操作涉及一个隐含的知识点，就是值和地址。下一节将详细介绍。

10.2.5　传值引用和传址引用

传值引用主要是针对基本数据类型而言。所谓传值引用，就是在进行变量的传递过程中，传递的是变量实际的值，是一个新的拷贝，一个变量值不会影响另一个变量值的改变。

【实例10.8】这是一个值传递的实例程序。

01 public class test2

02 {

03 public static void main（String[]args）

04 {

05 int a=10；

06 int b=a；

07 System.out.println（"在重新给a赋值前a的值："+a+""+"b的值："+b）；

08 a=30；

09 System.out.println（"在重新给a赋值后a的值："+a+""+"b的值："+b）；

10 }

11 }

【代码说明】第5～6行定义了两个整型变量a和b，第7行第一次输出两个变量的值。第8行改变变量a的值，第9行再次输出两个变量的值。

【运行效果】

在重新给a赋值前a的值：10 b的值是10

在重新给a赋值前a的值：30 b的值是10

从上面的程序段可以看出，“a”的值改变，并没有对“b”产生任何影响，这就是传值引用的特点。

传址引用主要是针对对象操作，它传递的是一个对象句柄的拷贝。也就形成了多个变量操作一个对象的局面，任何一个针对句柄操作的变量，都会影响到其他的变量。

【实例10.9】这是一个对象传址的实例。

01 public class test3

02 {

03 int x=2；

04 public static void main（String[]args）

05 {

06 test3 t=new test3（）；

07 test3 t1=t；

08 System.out.println（"测试前的数据："）；

09 System.out.println（"输出两个数据值："）；

10 System.out.println（"t.x="+t.x）；

11 System.out.println（"t1.x="+t1.x）；

12 System.out.println（"测试后的数据："）；

13 t.x=3；

14 System.out.println（"输出两个数据值："）；

15 System.out.println（"t.x="+t.x）；

16 System.out.println（"t1.x="+t1.x）；

17 }

18 }

【代码说明】第3行定义了类test3中的一个变量x，并设置了初始值为2。第6～7行创建两个对象t和t1，第8～11行初次输出两个对象中x的值，此时值没有变化。第13行设置对象t中的变量x为3，此时如果输出对象t1的x值，则可以看到也发生了变化。

【运行效果】

测试前的数据：

输出两个数据值：

t.x=2；

t1.x=2；

测试后的数据：

输出两个数据值：

t.x=3；

t1.x=3

从上面的程序段可以看出，一旦“t.x”的值发生变化，“t1.x”的值也紧随着改变，这就是传址引用的特点。

10.3　通过实例熟悉多态用法

【实例10.10】通过前面的几节描述，已经了解了多态的实质。本节将通过实例巩固其概念和用法，为以后实际开发奠定良好的基础。这个实例先构造一个类，然后让后面的类继承前面的类，最后再将对象输出。先看这个实例的流程，如图10.1所示。

图　10.1　一个综合实例的流程图

在这个实例中，创建一个父学生类，再创建一个继承的子学生类。创建一个父学生类的对象句柄，用其指向子学生类的对象。最后，将使用这个对象句柄来引用父类中没有的方法。这会是什么样的结果呢？

///这是一个主运行类

///通过构造器构造出sc, sm, sc1这三个对象

///通过set设置器来设置三个对象的参数

///通过tostring方法来让三个对象以字符串形式输出

public class studenttest

{

public static void main（String[]args）

{

studentclass sc=new studentclass（）；

studentmen sm=new studentmen（）；

studentclass sc1=new studentmen（）；

sc.set（"王浩"，"1"，"男"）；

sc.set（90.5）；

sm.set（"张杰"，"2"，"男"）；

sm.set（99.0）；

sm.set（"班长"）；

System.out.println（sc.toString（））；

System.out.println（sm.toString（））；

sc1.set（"赵丽"，"3"，"女"）；

sc1.set（100）；

sc1.set（"学习委员"）；

System.out.println（sc1.toString（））；

}

}

///这是一个学生类

///通过设置器设置了姓名、　学号、　性别、成绩等参数

///通过访问器可以获得这些参数的值

///通过tostring方法可以让对象以字符串形式输出

///这里有两个方法，方法名称一样。但是参数不一样。其实就是一个多态的概念

class studentclass

{

String name；

String code；

String sexy；

double achievement；

public void set（String name, String code, String sexy）

{

this.name=name；

this.code=code；

this.sexy=sexy；

}

public void set（double achievement）

{

this.achievement=achievement；

}

public String getname（）

{

return name；

}

public String getcode（）

{

return code；

}

public String getsexy（）

{

return sexy；

}

public double getachi（）

{

return achievement；

}

public String toString（）

{

String infor="学生　姓名："+name+""+"　学号："+code+""+"　性别："+sexy+""+"成绩："+achievement；

return infor；

}

}

///这也是一个学生类，不过它继承了上面那个学生的类

///通过设置器设置了职务，在这里就没有设置姓名、　学号、　性别、成绩等参数。因为它继承了上面的类的参数

///通过访问器可以获得职务这个参数的值

///通过tostring方法可以让对象以字符串形式输出

class studentmen extends studentclass

{

private String duty；

public void set（String duty）

{

this.duty=duty；

}

public String getduty（）

{

return duty；

}

public String toString（）

{

String infor="学生　姓名："+name+""+"　学号："+code+""+"　性别："+sexy+""+"职务：

"+duty+""+"成绩："+achievement；

return infor；

}

}

【代码说明】这个程序段没有通过编译，问题就出现在对象多态的概念上。“studentclass sc1=new studentmen（）”通过使用“studentclass”类的对象句柄来操作子类“studentmen”的对象，根据前面的介绍，这个句柄只能操作父类中有而被子类覆盖的方法，所以不能使用sc1.set（“学习委员”）这一句。

【运行效果】如果将此句去掉，这个程序段的运行结果为：

学生　姓名：王浩　学号：1　性别：男成绩：90.5

学生　姓名：张杰　学号：2　性别：男职务：班长成绩：99.0

学生　姓名：赵丽　学号：3　性别：女职务：null成绩：100.0

整个程序段中包含了很多知识点：重载、覆盖和继承等，希望读者能体会整个程序的编程思路和代码的精华之处。

10.4　绑定

所谓绑定，顾名思义就是将某个东西与另外一个东西捆绑在一起。在Java中，绑定就是对象方法的调用。准确地说，就是对象句柄与方法的绑定。绑定分为静态绑定和动态绑定。

10.4.1　静态绑定

当声明一个方法为“private”、“static”、“final”，或者声明一个构造器时，编译器清楚地知道是调用哪个方法，不存在与实际类型不匹配的现象，这就称为静态绑定。静态绑定不存在多态的问题。

10.4.2　动态绑定

动态绑定只用于程序运行的过程中，其会根据程序传递的参数不同，而调用不同的方法，这种绑定只有在程序运行期间才会发生，即动态绑定有着不确定性。动态绑定存在多态的问题。

10.5　超类

在讲述什么是父类的时候，提到父类又称为超类。本节将重点讲述超类，并详细介绍超类中使用频繁的方法“equals”。

10.5.1　什么是超类

Java语言是一门面向对象的程序设计语言，其类库中所有的类都从Object类中继承而来，即Object类是Java类库中所有类的父类，但在书写类的时候，通常不必这样写：

class student extends Object

系统自动认为Object类是“student”类的父类，由于Object类是所有类的父类，所以有时可以使用Object类型的变量，来代表任何类型的对象。举例如下：

Object obj=new student（）；

在讲述符号运算符时，提到过转型运算符，当需要将一个数据类型转变为一个对象型数据时，可以将数据类型直接转换成Object类型的变量。如下例所示：

Code code=（object）x；

这就将一个变量强行转换成对象型数据，当然也可以像下面的例子一样。

Code code=（Code）x

将一个变量强行转换成“Code”类型的对象数据。在进行转换的过程中注意一个问题：将子类实例赋给父类变量时，系统会自动完成转换。程序员无须去理会其中的类型转换过程，但是将父类实例赋给子类变量时，必须进行类型转换。

10.5.2　equals方法的使用

equals方法在Object类中，用于测试一个对象与另一个对象是否相等。对对象来说，判断两个对象是否指向同一个内存区域。其实也可以这样说，字符串处理中的“equals”方法覆盖了Object类中的“equals”方法。

在Java中，每一个类都有自己的“equals”方法，这些方法都是子类覆盖的方法。“equals”方法具有自己独特的一些性质。

❑自反性：当一个对象型变量obj是一个非空对象引用时，obj.equals（obj）的结果是真。

❑对称性：两个对象型变量obj1、obj2，如果obj1.equals（obj2）的结果是真，那么obj2.equals（obj1）的结果也必定是真。

❑传递性：3个对象型变量obj1、obj2、obj3，如果obj1.equals（obj2）的结果是真，同时obj2.equals（obj3）的结果也是真，那么obj1.equals（obj3）的结果必定是真。

❑一致性：对象型变量在不发生变化的前提下，多次调用obj1.equals（obj2）的结果都是一样的。

❑对于任何非空的对象型变量obj, obj.equals（null）的结果应该是false。

至于equals方法的使用，其实在前面章节中都举过例子，这里就不再示例。

10.5.3　通用编程

通用编程是什么意思呢？其实在前面讲述超类的时候，也向读者提起过，任何类型的实例都可以使用Object这个超类的变量来替代，因为Object类是所有类的父类。根据多态的原理，可以使用父类的类型代替子类，但是不能用子类的类型代替父类，因为父类中有的方法函数，子类中都有，而子类中的方法函数，父类中不一定都有。

10.6　常见疑难解答

10.6.1　动态和静态编译是什么

答：允许对对象进行不同的操作，但具体的操作却取决于对象的类型。

程序在编译的时候，什么函数对哪个对象执行什么操作都已经确定，换句话说称作静态编译。多态是动态编译，动态编译就是在程序执行的过程中，根据不同对象类型有不同的绑定，其通过一个方法接口，实现多个不同的实现过程。这依赖于编译时编译器对同一个方法不同参数的识别。

10.6.2　绑定与多态的联系是什么

答：将方法的调用连到方法本身被称为绑定。当绑定发生在程序运行之前，被称作前绑定（earlybinding），而在程序运行的时候，根据对象的类型来决定绑定方法被称为后绑定，也叫运行时绑定（run-time binding）或动态绑定（dynamic binding）。

Java的所有方法都采用后绑定。通常情况下，人们常常会将多态性同Java的那些非面向对象的特性混淆。需要特别注意的是“不是后绑定的，就不是多态性”，不用考虑是不是该采用后绑定，这一切都是自动的。

10.6.3　多态与重载的区别是什么

这是让初学者甚至普通程序员都很难明确的问题。重载是在一个类里，名字相同但参数不同的方法。多态是为了避免在父类里大量重载，而引起代码臃肿且难于维护的解决方案。多态有两种表现形式：重载和覆盖。

10.7　小结

本章介绍了多态，读者一定要先明白多态的概念，然后通过实例熟悉多态的用法。类有很多方法，读者要明白每个方法的绑定方式，有静态绑定和动态绑定。本章最后介绍了超类，这在面向对象的方法中非常关键，超类告诉我们顶层的类是什么。

10.8　习题

1.说出“==”和“equals”方法的区别。

2.说出传值引用和传址引用的区别。

3.静态绑定是否存在多态的问题？

A.存在

B.不存在

4.Java类库中所有的类都从______类中继承而来。

A.Object

B.Class

C.Public

第11章　接口与内部类

接口是什么？接口有什么作用？如何使用接口？这些都是本章需要解决的问题。本章将详细地讲述接口的概念，并以实战结合的方式，学习这些抽象概念。本章还会介绍内部类的相关知识，包括内部类的种类和内部类的使用环境等。

本章重点：

❑接口的定义、实现。

❑内部类的使用。

❑接口和内部类的意义。

11.1　接口

接口同继承、多态一样，都是Java语言的特色。它贯穿了整个Java程序开发，是对继承的很好补充，其原因下面会详细地讲述。

11.1.1　接口概念的引入

为什么在买USB电脑鼠标的时候，不需要问电脑配件的商家，USB鼠标是什么型号的。也不需要询问是满足什么要求。原因就是USB接口是统一的、固定不变的一种型号，是一种规范。所有的厂家都会按照这个规范，来制造USB接口的鼠标。这个规范说明制作该USB类型的鼠标应该做些什么，但并不说明如何做。

而Java程序设计中的接口，也是一种规范。这个接口定义了类应该做什么，但不关心如何做，即接口中只有方法名，没有方法体。

11.1.2　接口的概念

从专业的角度讲，接口只是说明类应该做什么，但并不指定应该如何去做。在实际开发过程中，通过类来实现接口。接口只有方法名，没有方法体，实现接口就是让其既有方法名又有方法体。下面就举个有关接口的模型。

接口

{

应该做的事情一

{……}

应该做的事情二

{……}

应该做的事情三

{……}

}

这个例子只是声明了要做什么事情，但没有说明如何做，需要一个类去实现它，将它的方法体完善。

11.1.3　接口的声明

接口的声明很简单，使用关键字“interface”来声明。接口的形式跟类很相似，但要记住接口是接口，类是类，两者不能混为一谈。接口是要求类如何做的一套规范。

【实例11.1】下面将举一个实例，来演示如何声明接口。

01 ///创建一个学校的接口

02 ///在这个接口中，创建了很多个方法，并且这些方法没有方法体

03 public interface school

04 {

05 void setschoolname（）；

06 void setclassname（）；

07 void setstudentname（）；

08 void setstudentcode（）；

09 void setstudentsexy（）；

10 void setstudentbirthday（）；

11 void setfamilyaddress（）；

12 String getschoolname（）；

13 String getclassname（）；

14 String getstudentname（）；

15 String getstudentcode（）；

16 String getstudentsexy（）；

17 String getstudentbirthday（）；

18 String getfamilyaddress（）；

19 }

【代码说明】上面的代码，演示了如何声明一个接口，可以看出整个接口中，只有几个设置器（第5～11行）和访问器（第12～18行）的方法名称，并没有真正实现方法。

【运行效果】没有主函数main，无法运行。

注意　接口的声明是“public”，有时候也可以省略。

11.1.4　接口的实现

【实例11.2】接口的用处就是让类通过实现它，来执行一定的功能。下面通过实例演示接口的实现功能，在看实例之前，先看看这个实例的流程，如图11.1所示。

注意　类实现接口时要使用implements关键字。

首先看看学校接口的设计。

01 ///声明一个学校接口，来告诉程序需要做些什么

02 ///其中包括了很多方法。但是都没有去实现，即都没有函数体

03 interface school

04 {

05 void setschoolname（String schoolname）；

06 void setclassname（String schoolclassname）；

07 void setname（String name）；

08 void setcode（String code）；

09 void setsexy（String sexy）；

10 void setbirthday（String birthday）；

11 void setfamilyaddress（String familyaddress）；

12 String getschoolname（）；

13 String getclassname（）；

14 String getname（）；

15 String getcode（）；

16 String getsexy（）；

17 String getbirthday（）；

18 String getfamilyaddress（）；

19 }

图　11.1　有关接口的一个实例

下面再来设计学生类，其代码如下：

20 ///创建一个类，让它实现学校这个接口

21 ///通过设置器来设置各个参数

22 ///通过访问器来获得对象的参数

23 ///通过tostring方法来让对象以字符串形式输出

24 class student implements school

25 {

26 private String schoolname；

27 private String classname；

28 private String studentname；

29 private String studentcode；

30 private String studentsexy；

31 private String studentbirthday；

32 private String familyaddress；

33 public void setschoolname（String schoolname）

34 {

35 this.schoolname=schoolname；

36 }

37 public void setclassname（String classname）

38 {

39 this.classname=classname；

40 }

41 public void setname（String studentname）

42 {

43 this.studentname=studentname；

44 }

45 public void setcode（String studentcode）

46 {

47 this.studentcode=studentcode；

48 }

49 public void setsexy（String studentsexy）

50 {

51 this.studentsexy=studentsexy；

52 }

53 public void setbirthday（String studentbirthday）

54 {

55 this.studentbirthday=studentbirthday；

56 }

57 public void setfamilyaddress（String familyaddress）

58 {

59 this.familyaddress=familyaddress；

60 }

61 public String getschoolname（）

62 {

63 return schoolname；

64 }

65 public String getclassname（）

66 {

67 return classname；

68 }

69 public String getname（）

70 {

71 return studentname；

72 }

73 public String getcode（）

74 {

75 return studentcode；

76 }

77 public String getsexy（）

78 {

79 return studentsexy；

80 }

81 public String getbirthday（）

82 {

83 return studentbirthday；

84 }

85 public String getfamilyaddress（）

86 {

87 return familyaddress；

88 }

89 public String tostring（）

90 {

91 String infor="学校　名称："+schoolname+""+"班级名称："+classname+""+"学生姓

92名："+studentname+""+"学号："+studentcode+""+"性别："+studentsexy+""+"出生年月：

93"+studentbirthday+""+"家庭地址："+familyaddress；

94 return infor；

95 }

96 }

下面再来设计教师类，其代码如下所示。

97 ///让教师类实现学校这个接口

98 ///通过设置器来设置各个参数

99 ///通过访问器来获得各个参数

100 ///通过tostring方法来让对象以字符串形式输出

101 class teacher implements school

102 {

103 private String schoolname；

104 private String classname；

105 private String teachername；

106 private String teachercode；

107 private String teachersexy；

108 private String teacherbirthday；

109 private String familyaddress；

110 public void setschoolname（String schoolname）

111 {

112 this.schoolname=schoolname；

113 }

114 public void setclassname（String classname）

115 {

116 this.classname=classname；

117 }

118 public void setname（String teachername）

119 {

120 this.teachername=teachername；

121 }

122 public void setcode（String teachercode）

123 {

124 this.teachercode=teachercode；

125 }

126 public void setsexy（String teachersexy）

127 {

128 this.teachersexy=teachersexy；

129 }

130 public void setbirthday（String teacherbirthday）

131 {

132 this.teacherbirthday=teacherbirthday；

133 }

134 public void setfamilyaddress（String familyaddress）

135 {

136 this.familyaddress=familyaddress；

137 }

138 public String getschoolname（）

139 {

140 return schoolname；

141 }

142 public String getclassname（）

143 {

144 return classname；

145 }

146 public String getname（）

147 {

148 return teachername；

149 }

150 public String getcode（）

151 {

152 return teachercode；

153 }

154 public String getsexy（）

155 {

156 return teachersexy；

157 }

158 public String getbirthday（）

159 {

160 return teacherbirthday；

161 }

162 public String getfamilyaddress（）

163 {

164 return familyaddress；

165 }

166 public String tostring（）

167 {

168 String infor="学校名称："+schoolname+""+"班级名称："+classname+""+"教师姓名：

169"+teachername+""+"教师工号："+teachercode+""+"性别："+teachersexy+""+"出生年月：

170"+teacherbirthday+""+"家庭地址："+familyaddress；

171 return infor；

172 }

173 }

主运行程序的代码如下所示。

174 ///主运行函数

175 ///从学生类中创建出几个对象

176 ///从教师类中创建出几个对象

177 ///通过设置器设置各个参数，在对象内部隐

178 ///藏着访问器，来访问这些参数

179 ///以字符串形式输出这些对象的字符串

180 public class schooltest

181 {

182 public static void main（String[]args）

183 {

184 student st1=new student（）；

185 student st2=new student（）；

186 teacher te1=new teacher（）；

187 teacher te2=new teacher（）；

188 st1.setschoolname（"重庆大学"）；

189 st1.setclassname（"计算机二班"）；

190 st1.setname（"王浩"）；

191 st1.setcode（"951034"）；

192 st1.setsexy（"男"）；

193 st1.setbirthday（"1975-07-21"）；

194 st1.setfamilyaddress（"上海市浦东新区"）；

195 st2.setschoolname（"重庆大学"）；

196 st2.setclassname（"计算机三班"）；

197 st2.setname（"赵丽"）；

198 st2.setcode（"951068"）；

199 st2.setsexy（"女"）；

200 st2.setbirthday（"1975-10-09"）；

201 st2.setfamilyaddress（"北京海淀区"）；

202 te1.setschoolname（"四川大学"）；

203 te1.setclassname（"计算机二班"）；

204 te1.setname（"孙敏"）；

205 te1.setcode（"00123"）；

206 te1.setsexy（"女"）；

207 te1.setbirthday（"1968-04-20"）；

208 te1.setfamilyaddress（"重庆市沙坪坝区"）；

209 te2.setschoolname（"四川大学"）；

210 te2.setclassname（"机械系三班"）；

211 te2.setname（"赵为民"）；

212 te2.setcode（"11233"）；

213 te2.setsexy（"男"）；

214 te2.setbirthday（"1961-02-13"）；

215 te2.setfamilyaddress（"成都市区"）；

216 System.out.println（st1.tostring（））；

217 System.out.println（st2.tostring（））；

218 System.out.println（te1.tostring（））；

219 System.out.println（te2.tostring（））；

220 }

221 }

【代码说明】本例代码比较长，但都非常简单。第3～19行定义接口school，并规范了14种方法。第24～96行定义了类student，其实现了接口school，也就是在类中实现了接口定义的14种方法，然后还定义了自己的tostring（）方法，输出学生信息。第101～173行定义了类teacher，也实现了接口school，同样实现了接口中的14种方法，然后也定义了自己的tostring（）方法，输出教师信息。第180～221行是主运行程序，其中定义了4个对象，然后调用tostring（）方法输出信息。

【运行效果】

学校　名称：重庆大学　班级名称：计算机二班学生　姓名：王浩　学号：951034　性别：男出　生年月：1975-07-21　家庭地址：上海市浦东新区

学校　名称：重庆大学　班级名称：计算机三班学生　姓名：赵丽　学号：951068　性别：女出　生年月：1975-10-09　家庭地址：北京海淀区

学校　名称：四川大学　班级名称：计算机二班　教师姓名：孙敏　教师工号：00123　性别：女出　生年月：1968-04-20　家庭地址：重庆市沙坪坝区

学校　名称：四川大学　班级名称：机械系三班　教师姓名：赵为民　教师工号：11233　性别：男出　生年月：1961-02-13　家庭地址：成都市区

举这个例子的目的就是了解接口的用处。在这个程序段中，将接口作为一种规范，当要提取一个学生类时，就用学生类来实现学校这个接口。当要提取一个教师类时，教师类也实现学校这个接口。

接口的用处在于让整个程序段中相同类型的类有一个统一的规范，这样看到接口的定义，就知道要实现它的类的功能。在类实现接口时，需要注意以下几点：

❑声明类需要实现指定的接口。

❑提供接口中所有方法的定义。

11.1.5　接口的多重实现

前面提到过接口能够补充继承的不足，现在讲解如何补充。继承必须是单继承的，即一个类继承另一个类后，这个类就不能继承其他类。而接口则无所谓，一个类可以实现一个接口，也可以同时实现另一个接口。

【实例11.3】使用接口为编程提供了很大的方便，可以把上面的程序段修改一下。为了能更好地理解这个程序，先看看程序的流程，如图11.2所示。

图　11.2　多重实现的流程图

首先是第一个学校接口的设计，代码如下所示。

01 interface school //创建一个接口

02 {

03 void setschoolname（String schoolname）；

04 void setclassname（String schoolclassname）；

05 void setname（String name）；

06 void setcode（String code）；

07 void setsexy（String sexy）；

08 void setbirthday（String birthday）；

09 void setfamilyaddress（String familyaddress）；

10 }

接着第二个学校接口的设计，代码如下所示。

11 interface school1 //创建另一个接口

12 {

13 String getschoolname（）；

14 String getclassname（）；

15 String getname（）；

16 String getcode（）；

17 String getsexy（）；

18 String getbirthday（）；

19 String getfamilyaddress（）；

20 }

下面学习如何设计学生类，详细代码如下所示。

21 ///创建一个类，让它实现学校这两个接口

22 ///通过设置器来设置各个参数

23 ///通过访问器来获得对象的参数

24 ///通过tostring方法来让对象以字符串形式输出

25 class student implements school, school1 //一个类实现两个不同的接口

26 {

27 private String schoolname；

28 private String classname；

29 private String studentname；

30 private String studentcode；

31 private String studentsexy；

32 private String studentbirthday；

33 private String familyaddress；

34 public void setschoolname（String schoolname）

35 {

36 this.schoolname=schoolname；

37 }

38 public void setclassname（String classname）

39 {

40 this.classname=classname；

41 }

42 public void setname（String studentname）

43 {

44 this.studentname=studentname；

45 }

46 public void setcode（String studentcode）

47 {

48 this.studentcode=studentcode；

49 }

50 public void setsexy（String studentsexy）

51 {

52 this.studentsexy=studentsexy；

53 }

54 public void setbirthday（String studentbirthday）

55 {

56 this.studentbirthday=studentbirthday；

57 }

58 public void setfamilyaddress（String familyaddress）

59 {

60 this.familyaddress=familyaddress；

61 }

62 public String getschoolname（）

63 {

64 return schoolname；

65 }

66 public String getclassname（）

67 {

68 return classname；

69 }

70 public String getname（）

71 {

72 return studentname；

73 }

74 public String getcode（）

75 {

76 return studentcode；

77 }

78 public String getsexy（）

79 {

80 return studentsexy；

81 }

82 public String getbirthday（）

83 {

84 return studentbirthday；

85 }

86 public String getfamilyaddress（）

87 {

88 return familyaddress；

89 }

90 public String tostring（）

91 {

92 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"学生　姓名：

93"+studentname+""+"　学号："+studentcode+""+"　性别："+studentsexy+""+"出　生年月：

94"+studentbirthday+""+"　家庭地址："+familyaddress；

95 return infor；

96 }

97 }

设计教师类的代码如下所示。

98 ///让教师类实现学校这两个接口

99 ///通过设置器来设置各个参数

100 ///通过访问器来获得各个参数

101 ///通过tostring方法来让对象以字符串形式输出

102 class teacher implements school, school1 //一个类实现两个不同的接口

103 {

104 private String schoolname；

105 private String classname；

106 private String teachername；

107 private String teachercode；

108 private String teachersexy；

109 private String teacherbirthday；

110 private String familyaddress；

111 public void setschoolname（String schoolname）

112 {

113 this.schoolname=schoolname；

114 }

115 public void setclassname（String classname）

116 {

117 this.classname=classname；

118 }

119 public void setname（String teachername）

120 {

121 this.teachername=teachername；

122 }

123 public void setcode（String teachercode）

124 {

125 this.teachercode=teachercode；

126 }

127 public void setsexy（String teachersexy）

128 {

129 this.teachersexy=teachersexy；

130 }

131 public void setbirthday（String teacherbirthday）

132 {

133 this.teacherbirthday=teacherbirthday；

134 }

135 public void setfamilyaddress（String familyaddress）

136 {

137 this.familyaddress=familyaddress；

138 }

139 public String getschoolname（）

140 {

141 return schoolname；

142 }

143 public String getclassname（）

144 {

145 return classname；

146 }

147 public String getname（）

148 {

149 return teachername；

150 }

151 public String getcode（）

152 {

153 return teachercode；

154 }

155 public String getsexy（）

156 {

157 return teachersexy；

158 }

159 public String getbirthday（）

160 {

161 return teacherbirthday；

162 }

163 public String getfamilyaddress（）

164 {

165 return familyaddress；

166 }

167 public String tostring（）

168 {

169 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

170"+teachername+""+"　教师工号："+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

171"+teacherbirthday+""+"　家庭地址："+familyaddress；

172 return infor；

173 }

174 }

主运行程序将上面设计的类通过对象的形式输出。其详细代码如下所示。

175 ///主运行函数

176 ///从学生类中创建出几个对象

177 ///从教师类中创建出几个对象

178 ///通过设置器设置各个参数，在对象内部隐

179 ///藏着访问器，来访问这些参数

180 ///以字符串形式输出这些对象的字符串

181 public class schooltest1

182 {

183 public static void main（String[]args）

184 {

185 student st1=new student（）；

186 student st2=new student（）；

187 teacher te1=new teacher（）；

188 teacher te2=new teacher（）；

189 st1.setschoolname（"重庆大学"）；

190 st1.setclassname（"计算机二班"）；

191 st1.setname（"王浩"）；

192 st1.setcode（"951034"）；

193 st1.setsexy（"男"）；

194 st1.setbirthday（"1975-07-21"）；

195 st1.setfamilyaddress（"上海市浦东新区"）；

196 st2.setschoolname（"重庆大学"）；

197 st2.setclassname（"计算机三班"）；

198 st2.setname（"赵丽"）；

199 st2.setcode（"951068"）；

200 st2.setsexy（"女"）；

201 st2.setbirthday（"1975-10-09"）；

202 st2.setfamilyaddress（"北京海淀区"）；

203 te1.setschoolname（"四川大学"）；

204 te1.setclassname（"计算机二班"）；

205 te1.setname（"孙敏"）；

206 te1.setcode（"00123"）；

207 te1.setsexy（"女"）；

208 te1.setbirthday（"1968-04-20"）；

209 te1.setfamilyaddress（"重庆市沙坪坝区"）；

210 te2.setschoolname（"四川大学"）；

211 te2.setclassname（"机械系三班"）；

212 te2.setname（"赵为民"）；

213 te2.setcode（"11233"）；

214 te2.setsexy（"男"）；

215 te2.setbirthday（"1961-02-13"）；

216 te2.setfamilyaddress（"成都市区"）；

217 System.out.println（st1.tostring（））；

218 System.out.println（st2.tostring（））；

219 System.out.println（te1.tostring（））；

220 System.out.println（te2.tostring（））；

221 }

222 }

【代码说明】第1～10行和第11～20行分别定义了两个接口，不过它们的内容组合在一起就是上一个案例的接口school。第25行和第102行分别定义了类stduent和teacher，它们都实现了这两个接口，接口之间以逗号间隔。其他内容都与上例相同。

【运行效果】

学校　名称：重庆大学　班级名称：计算机二班学生　姓名：王浩　学号：951034　性别：男出　生年月：1975-07-21　家庭地址：上海市浦东新区

学校　名称：重庆大学　班级名称：计算机三班学生　姓名：赵丽　学号：951068　性别：女出　生年月：1975-10-09　家庭地址：北京海淀区

学校　名称：四川大学　班级名称：计算机二班　教师姓名：孙敏　教师工号：00123　性别：女出　生年月：1968-04-20　家庭地址：重庆市沙坪坝区

学校　名称：四川大学　班级名称：机械系三班　教师姓名：赵为民　教师工号：11233　性别：男出　生年月：1961-02-13　家庭地址：成都市区

在上面的程序段中，将一个接口分成了两个。然后用一个类同时实现两个接口，运行结果依然不变。如果是继承就不允许这样，因为一个类只能继承一个类，不能继承多个类，这就是接口的多重实现。

11.1.6　接口的属性

接口不是一个类，正因为其不是一个类，所以不能使用关键字“new”生成一个接口的实例。虽然这样，还是可以声明一个接口变量，如“school sc”。

如果要生成一个接口的实例，可以让接口变量指向一个已经实现了此接口的类的对象，如下面的例子。

school sc=new student（）；

另外，在接口中，不能声明实例字段及静态方法，但可以声明常量。其实接口不一定要有方法，也可以全部是常量。这在后面的章节中，随着应用的加深，读者会看到和体会到。

11.1.7　接口的继承

接口从某些方面具有类的一些特性，如有方法、有属性，那么是否像类一样可以继承？回答是肯定的。接口的继承和类的继承一样，也是用关键字“extends”来实现。

【实例11.4】下面先看一个有关接口继承的实例。实例的流程如图11.3所示。

图　11.3　接口继承的流程图

首先看看学校接口的设计，代码如下所示。

01 ///创建一个接口

02 interface school

03 {

04 void setschoolname（String schoolname）；

05 void setclassname（String schoolclassname）；

06 String getschoolname（）；

07 String getclassname（）；

08 }

设计另一个学校接口，代码如下所示。

09 ///这个接口继承了上面那个接口

10 interface school1 extends school

11 {

12 void setname（String name）；

13 void setcode（String code）；

14 void setsexy（String sexy）；

15 void setbirthday（String birthday）；

16 void setfamilyaddress（String familyaddress）；

17 String getname（）；

18 String getcode（）；

19 String getsexy（）；

20 String getbirthday（）；

21 String getfamilyaddress（）；

22 }

设计学生类，代码如下所示。

23 ///创建一个类，让它实现学校这个接口

24 ///通过设置器来设置各个参数

25 ///通过访问器来获得对象的参数

26 ///通过tostring方法来让对象以字符串形式输出

27 class student implements school1 //学生类实现子接口

28 {

29 private String schoolname；

30 private String classname；

31 private String studentname；

32 private String studentcode；

33 private String studentsexy；

34 private String studentbirthday；

35 private String familyaddress；

36 public void setschoolname（String schoolname）

37 {

38 this.schoolname=schoolname；

39 }

40 public void setclassname（String classname）

41 {

42 this.classname=classname；

43 }

44 public void setname（String studentname）

45 {

46 this.studentname=studentname；

47 }

48 public void setcode（String studentcode）

49 {

50 this.studentcode=studentcode；

51 }

52 public void setsexy（String studentsexy）

53 {

54 this.studentsexy=studentsexy；

55 }

56 public void setbirthday（String studentbirthday）

57 {

58 this.studentbirthday=studentbirthday；

59 }

60 public void setfamilyaddress（String familyaddress）

61 {

62 this.familyaddress=familyaddress；

63 }

64 public String getschoolname（）

65 {

66 return schoolname；

67 }

68 public String getclassname（）

69 {

70 return classname；

71 }

72 public String getname（）

73 {

74 return studentname；

75 }

76 public String getcode（）

77 {

78 return studentcode；

79 }

80 public String getsexy（）

81 {

82 return studentsexy；

83 }

84 public String getbirthday（）

85 {

86 return studentbirthday；

87 }

88 public String getfamilyaddress（）

89 {

90 return familyaddress；

91 }

92 public String tostring（）

93 {

94 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"学生　姓名：

95"+studentname+""+"　学号："+studentcode+""+"　性别："+studentsexy+""+"出　生年月：

96"+studentbirthday+""+"　家庭地址："+familyaddress；

97 return infor；

98 }

99 }

设计教师类，详细代码如下。

100 ///创建一个类，让它实现学校这个接口

101 ///通过设置器来设置各个参数

102 ///通过访问器来获得对象的参数

103 ///通过tostring方法来让对象以字符串形式输出

104 class teacher implements school1

105 {

106 private String schoolname；

107 private String classname；

108 private String teachername；

109 private String teachercode；

110 private String teachersexy；

111 private String teacherbirthday；

112 private String familyaddress；

113 public void setschoolname（String schoolname）

114 {

115 this.schoolname=schoolname；

116 }

117 public void setclassname（String classname）

118 {

119 this.classname=classname；

120 }

121 public void setname（String teachername）

122 {

123 this.teachername=teachername；

124 }

125 public void setcode（String teachercode）

126 {

127 this.teachercode=teachercode；

128 }

129 public void setsexy（String teachersexy）

130 {

131 this.teachersexy=teachersexy；

132 }

133 public void setbirthday（String teacherbirthday）

134 {

135 this.teacherbirthday=teacherbirthday；

136 }

137 public void setfamilyaddress（String familyaddress）

138 {

139 this.familyaddress=familyaddress；

140 }

141 public String getschoolname（）

142 {

143 return schoolname；

144 }

145 public String getclassname（）

146 {

147 return classname；

148 }

149 public String getname（）

150 {

151 return teachername；

152 }

153 public String getcode（）

154 {

155 return teachercode；

156 }

157 public String getsexy（）

158 {

159 return teachersexy；

160 }

161 public String getbirthday（）

162 {

163 return teacherbirthday；

164 }

165 public String getfamilyaddress（）

166 {

167 return familyaddress；

168 }

169 public String tostring（）

170 {

171 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

172"+teachername+""+"　教师工号："+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

173"+teacherbirthday+""+"　家庭地址："+familyaddress；

174 return infor；

175 }

176 }

主运行类的代码如下所示。

177 ///主运行函数

178 ///从学生类中创建出几个对象

179 ///从教师类中创建出几个对象

180 ///通过设置器设置各个参数，在对象内部隐

181 ///藏着访问器，来访问这些参数

182 ///以字符串形式输出这些对象的字符串

183 public class schooltest2

184 {

185 public static void main（String[]args）

186 {

187 student st1=new student（）；

188 student st2=new student（）；

189 teacher te1=new teacher（）；

190 teacher te2=new teacher（）；

191 st1.setschoolname（"重庆大学"）；

192 st1.setclassname（"计算机二班"）；

193 st1.setname（"王浩"）；

194 st1.setcode（"951034"）；

195 st1.setsexy（"男"）；

196 st1.setbirthday（"1975-07-21"）；

197 st1.setfamilyaddress（"上海市浦东新区"）；

198 st2.setschoolname（"重庆大学"）；

199 st2.setclassname（"计算机三班"）；

200 st2.setname（"赵丽"）；

201 st2.setcode（"951068"）；

202 st2.setsexy（"女"）；

203 st2.setbirthday（"1975-10-09"）；

204 st2.setfamilyaddress（"北京海淀区"）；

205 te1.setschoolname（"四川大学"）；

206 te1.setclassname（"计算机二班"）；

207 te1.setname（"孙敏"）；

208 te1.setcode（"00123"）；

209 te1.setsexy（"女"）；

210 te1.setbirthday（"1968-04-20"）；

211 te1.setfamilyaddress（"重庆市沙坪坝区"）；

212 te2.setschoolname（"四川大学"）；

213 te2.setclassname（"机械系三班"）；

214 te2.setname（"赵为民"）；

215 te2.setcode（"11233"）；

216 te2.setsexy（"男"）；

217 te2.setbirthday（"1961-02-13"）；

218 te2.setfamilyaddress（"成都市区"）；

219 System.out.println（st1.tostring（））；

220 System.out.println（st2.tostring（））；

221 System.out.println（te1.tostring（））；

222 System.out.println（te2.tostring（））；

223 }

224 }

【代码说明】代码第1～8行定义了接口school，第10～22行定义了接口school1，其继承自接口school。第27行和第104行定义了student类和teacher类，都只实现接口school1，但其实现接口也包含接口school中定义的方法。

【运行效果】

学校　名称：重庆大学　班级名称：计算机二班学生　姓名：王浩　学号：951034　性别：男出　生年月：1975-07-21　家庭地址：上海市浦东新区

学校　名称：重庆大学　班级名称：计算机三班学生　姓名：赵丽　学号：951068　性别：女出　生年月：1975-10-09　家庭地址：北京海淀区

学校　名称：四川大学　班级名称：计算机二班　教师姓名：孙敏　教师工号：00123　性别：女出　生年月：1968-04-20　家庭地址：重庆市沙坪坝区

学校　名称：四川大学　班级名称：机械系三班　教师姓名：赵为民　教师工号：11233　性别：男出　生年月：1961-02-13　家庭地址：成都市区

11.1.8　接口的意义

接口不仅仅是一种规范，还是一种编程的思路。接口的所有方法和属性都代表了后面将要设计的类的基本思路，这些方法就代表着这个程序的需求，所以掌握好接口，对学好Java程序开发非常关键。

11.2　内部类

内部类就是在一个类的内部再创建一个类。下面介绍如何使用内部类编写程序代码，并了解内部类在编写代码的过程中，为程序员提供了哪些方便和优点。

内部类的好处：

❑内部类的对象能够访问创建它的对象的所有方法和属性，包括私有数据。

❑对于同一个包中的其他类来说，内部类是隐形的。

❑匿名内部类可以很方便地定义回调。

❑使用内部类可以很方便地编写事件驱动的程序。

下面将这些特点贯穿整节，通过实例来讲述。

11.2.1　使用内部类来访问对象

【实例11.5】下面将举个有关内部类的实例。在分析这个实例之前，先了解这个实例的流程，如图11.4所示。

图　11.4　内部类访问对象的实例

首先看看如何设计学生类，代码如下所示。

01 ///创建一个学生类

02 ///在学生类中，创建姓名、　学号、　性别等参数

03 ///使用设置器来设置参数

04 ///使用访问器来获取参数值

05 public class students

06 {

07 private String name；

08 private String code；

09 private String sexy；

10 private String birthday；

11 private String address；

12 public void setname（String name）

13 {

14 this.name=name；

15 }

16 public void setcode（String code）

17 {

18 this.code=code；

19 }

20 public void setsexy（String sexy）

21 {

22 this.sexy=sexy；

23 }

24 public void setbirthday（String birthday）

25 {

26 this.birthday=birthday；

27 }

28 public void setaddress（String address）

29 {

30 this.address=address；

31 }

32 public String getname（）

33 {

34 return name；

35 }

36 public String getcode（）

37 {

38 return code；

39 }

40 public String getsexy（）

41 {

42 return sexy；

43 }

44 public String getbirthday（）

45 {

46 return birthday；

47 }

48 public String getaddress（）

49 {

50 return address；

51 }

52 public String tostring（）

53 {

54 String infor="学生　姓名："+name+""+"　学号："+code+""+"　性别："+sexy+""+"出　生年月：

55"+birthday+""+"　家庭地址："+address；

56 return infor；

57 }

58 public void setstudentcourse（String[]courses）

59 {

60 new course（courses）；

61 }

62 }

再设计一个内部类，代码如下所示。

63 ///内部类的创建，把内部类作为外部类的一个成员

64 65 private class course

66 {

67 private String[]courses；

68 private int coursenum；

69 ///内部类的构造器

70 public course（String[]course）

71 {

72 courses=course；

73 coursenum=course.length；

74 getinfo（）；

75 }

76 ///获得课程数组中的课程

77 private void get（）

78 {

79 for（int i=0；i＜coursenum；i++）

80 {

81 System.out.print（""+courses[i]）；

82 }

83 }

84 ///按字符串形式输出

85 void getinfo（）

86 {

87 System.out.println（"学生　姓名："+students.this.name+"学生　学号：

88 "+students.this.code+"一共选择了："+coursenum+"门科，分别是："）；

89 get（）；

90 }

91 }

在主运行方法内部实现输出，详细代码如下所示。

92 ///在主运行方法中，通过学生类的方法来访问学生类的内部类courses

93 public static void main（String[]args）

94 {

95 String[]courses={"语文"，"数学"，"英语"，"化学"}；

96 students st=new students（）；

97 st.setname（"王浩"）；

98 st.setcode（"200123"）；

99 st.setsexy（"男"）；

100 st.setaddress（"北京海淀区"）；

101 System.out.println（st.tostring（））；

102 st.setstudentcourse（courses）；

103 }

【代码说明】这里的代码一定要注意，第63～91行定义了内部类和第92～103行的主运行方法都应该在类students的{}内，本例是为了讲解方便，将代码分成了三段。

【运行效果】

学生　姓名：王浩　学号：200123　性别：男出　生年月：null　家庭地址：北京海淀区

学生　姓名：王浩学生　学号：200123一共选择了：4门科，分别是：

语文数学英语化学

类的访问控制符在前面章节中讲过，只能有“public”和“default”两种。那为什么在这里的内部类会出现private呢？

作为一个单独的类，的确只能有“public”和“default”两种访问控制符，但是作为内部类，就可以使用“private”控制符。当内部类设置为“private”，包含此内部类的外部类的方法才可以访问它。在这个程序段里，“students”类中的方法可以访问这个内部类。

内部类如何创建对象呢？其实可以像一般的类一样，直接使用“new”关键字来创建。在上面的实例中，使用一个方法setstudentcourse（）来创建内部类的实例对象。内部类的构造器中包含了这个内部类要实现的所有方法。

注意

当类创建出对象之前，首先会访问构造器，会运行构造器中的所有方法。这样，就相当于直接访问了内部类的私有方法，这种方法可以将对象中所有方法一并实现。

11.2.2　局部内部类

本小节通过对比局部变量，来学习局部内部类。局部变量就是在某个类的方法中定义的变量，它的作用范围就在这个方法体内。同样局部内部类就是在类的方法中定义的一个内部类，它的作用范围也在这个方法体内。

【实例11.6】把上面的实例修改一下，学习局部内部类的使用。这个实例的流程如图11.5所示。

图　11.5　局部内部类的实例流程图

首先设计一个学生类，代码如下所示。

01 ///这是一个学生类

02 ///通过设置器来设置对象参数

03 ///通过访问器来获得对象参数

04 ///通过tostring方法让对象以字符串形式输出

05 public class students1

06 {

07 private String name；

08 private String code；

09 private String sexy；

10 private String birthday；

11 private String address；

12 public void setname（String name）

13 {

14 this.name=name；

15 }

16 public void setcode（String code）

17 {

18 this.code=code；

19 }

20 public void setsexy（String sexy）

21 {

22 this.sexy=sexy；

23 }

24 public void setbirthday（String birthday）

25 {

26 this.birthday=birthday；

27 }

28 public void setaddress（String address）

29 {

30 this.address=address；

31 }

32 public String getname（）

33 {

34 return name；

35 }

36 public String getcode（）

37 {

38 return code；

39 }

40 public String getsexy（）

41 {

42 return sexy；

43 }

44 public String getbirthday（）

45 {

46 return birthday；

47 }

48 public String getaddress（）

49 {

50 return address；

51 }

52 public String tostring（）

53 {

54 String infor="学生　姓名："+name+""+"　学号："+code+""+"　性别："+sexy+""+"出　生年月：

55"+birthday+""+"　家庭地址："+address；

56 return infor；

57 }

58 }

在前面设计的外部类的方法中，设计一个内部类，代码如下所示。

59 ///将课程类作为外部类中的一个方法中的成员

60 61

public void setstudentcourse（String[]courses）

62 {

63 class course

64 {

65 private String[]courses；

66 private int coursenum；

67 public course（String[]course）

68 {

69 courses=course；

70 coursenum=course.length；

71 getinfo（）；

72 }

73 private void get（）

74 {

75 for（int i=0；i＜coursenum；i++）

76 {

77 System.out.print（""+courses[i]）；

78 }

79 }

80 private void getinfo（）

81 {

82 System.out.println（"学生　姓名："+students1.this.name+"学生　学号：

83"+students1.this.code+"一共选择了："+coursenum+"门科，分别是："）；

84 get（）；

85 }

86 }

87 ///创建了一个内部类的对象，随着包含这个内部类的外部方法一起运行

88 new course（courses）；

89 }

在主运行程序中，实现对象的输出，代码如下所示。

90 ///在主运行方法中，使用学生类对象的方法来访问局部类courses

91 public static void main（String[]args）

92 {

93 String[]courses={"语文"，"数学"，"英语"，"化学"}；

94 students1 st=new students1（）；

95 st.setname（"王浩"）；

96 st.setcode（"200123"）；

97 st.setsexy（"男"）；

98 st.setaddress（"北京海淀区"）；

99 System.out.println（st.tostring（））；

100 st.setstudentcourse（courses）；

101 }

【代码说明】这个程序从上一节的实例程序修改过来，把内部类放到了“students”类的一个方法体内，这样的内部类就是局部内部类。输出结构与上例相同。

说明　代码第59～101行的内容其实都是在类students1的{}内。

【运行效果】

学生　姓名：王浩　学号：200123　性别：男出　生年月：null　家庭地址：北京海淀区

学生　姓名：王浩学生　学号：200123一共选择了：4门科，分别是：语文数学英语化学

局部内部类是定义在外部类的方法中，与局部变量类似，在局部内部类前不加修饰符“public”和“private”，其范围为定义它的代码块。局部内部类不仅可以访问外部类实例变量，还可以访问外部类的局部常量，但要求外部类的局部变量是“final”的。其实，以上做法相当于是为内部类添加了一个属性，这个属性就是外部类的“final”局部变量。在类外不可直接访问局部内部类，以保证局部内部类对外是不可见的，只有在方法中才能调用其局部内部类。

11.2.3　静态内部类

当一个内部类不需要引用它的外部类的方法、属性值时，可以将这个类设置为“static”，这就是静态内部类。既然是静态的，包含它的类要引用它时，就可以不必创建对象，直接引用。在静态内部类中只能访问外部类的静态成员。构造静态内部类对象，不再需要构造外部类对象。

11.2.4　匿名内部类

在编写程序代码时，不一定要给内部类取一个名字，可以直接以对象名来代替。在图形化编程的事件监控器代码中，会大量使用匿名内部类，这样可以大大地简化代码的编写，并增强了代码的可读性。

11.3　常见疑难解答

11.3.1　匿名类如何在程序中使用

答：匿名类是一种特殊的局部内部类，用来继承一个类或者实现一个接口。匿名内部类不能定义构造方法。在编译的时候由系统自动起名Out$1.class。如果一个对象编译时的类型是接口，那么其运行的类型是实现这个接口的类，因为匿名内部类无构造方法，所以其使用范围非常有限。

11.3.2　接口与继承有什么区别

答：接口在本质上就是一个特殊的类。在语法上跟继承有着很大的差别。

❑属性：接口中的所有属性都是公开静态常量，继承则无所谓。

❑方法：接口中的所有方法都是公开抽象方法，继承中所有的方法不一定都是抽象方法。

❑接口方法：接口没有构造器，继承有构造器。

11.4　小结

本章讲解了接口和内部类，通过引入接口概念，进一步实现了面向对象的编程思想。学习完本章后，读者应该能够说出接口和内部类的概念，并能写出简单的接口和内部类。多动手是学习Java的好方法，本章的这些实例比前面各章的实例都长，读者心理上不要产生畏惧，因为代码都很简单，动手测试后就知道了。

11.5　习题

1.接口支持多重实现吗？

2.接口中定义了方法，是否实现该接口的类都必须实现该方法？

A.是

B.不是

3.内部类是否必须有一个名字？

A.必须

B.无须

第12章　抽象

在Java语言中，有两个很重要的概念：抽象和抽象类。抽象是一种方法，是Java语言编程的基本点。它将所有具体的事务，抽象为一些共同的事务。抽象类是一种类。本章将围绕如何建立编程思路这一个基本点，结合大量的程序实例来分析什么是抽象和抽象类。

本章重点：

❑什么是抽象。

❑抽象类的定义和使用。

❑抽象和接口的区别。

12.1　抽象的概念

本节将详细讲述什么是抽象行为，在讲述抽象行为之前，会先介绍什么是抽象，然后介绍抽象的一些基本应用。

12.1.1　什么是抽象

抽象就是将拥有共同方法和属性的对象提取出来，提取后，重新设计一个更加通用、更加大众化的类，这个类称为抽象类。抽象类是从一个客户要求信息中提取出一个类。下面先看一个有关抽象的例子，再来理解抽象的概念。

为汽车销售公司开发一个信息系统软件。这个汽车销售公司拥有不同厂家的汽车，例如大众、通用、福特等。

分析：面对这样一个客户信息，首先要给这个汽车销售公司的所有汽车建立一个类，此类能充分地表现出各个不同品牌汽车的特点。这项工作就是一个抽象的过程，例如，汽车有轮子、离合器、车灯、方向盘等，这些都是所有不同品牌汽车的共同特征。这些特征能够充分证明这是一部汽车，至于是什么汽车则不属于抽象的范围。

【实例12.1】下面来看一个实例，通过实战了解具体的抽象概念。

01 public class studenttest1

02 {

03 public static void main（String[]args）

04 {

05 student st=new student（"张杰"，"200111"，"20"，"男"）；

06 }

07 }

08 class student

09 {

10 String name；

11 String code；

12 String age；

13 String sexy；

14 public student（String name, String code, String age, String sexy）

15 {

16 this.name=name；

17 this.code=code；

18 this.age=age；

19 this.sexy=sexy；

20 System.out.println（"这个学生的学生　姓名："+name+""+"　学号："+code+""+"　年龄：

21"+age+""+"　性别："+sexy）；

22 }

23 }

【代码说明】上面这个程序段第8～22行抽象出一个学生类，它并不管是哪个学校的学生、哪个城市的学生、哪个国家的学生。而第10～13行的这些属性，都代表了一个学生应该拥有的特性。至于其他的属性，例如学校、班级、班主任等，可以通过继承创建另一个子类。

【运行效果】

这个学生的学生　姓名：张杰　学号：200111　年龄：20　性别：男

通过上面的讲述，可以知道抽象就是提取所有对象的共性，取出共性的过程。下面就通过图12.1所示了解抽象分析的过程。

一个程序的开发，就是利用前面讲述的继承、接口、多态等概念，实现应用代码。下面就通过实例，来进一步地熟悉抽象在程序开发中的一些编程思路和应用。

12.1.2　抽象的综合实例

图　12.1　抽象分析图

【实例12.2】本节将列举一个包含前面很多知识点的综合实例，其中有接口、抽象、继承等概念。下面先了解这个综合实例的流程，如图12.2所示。

图　12.2　抽象综合实例的流程图

上面流程图所对应的代码如下所示。

01 ///这是一个主运行类

02 ///通过构造函数创建f1和f2两个花的对象

03 ///通过设置器分别设置两个对象的参数

04 ///通过tostring输出对象

05 public class flowertest

06 {

07 public static void main（String[]args）

08 {

09 flower1 f1=new flower1（）；

10 flower1 f2=new flower1（）；

11 f1.setcolor（"红色"）；

12 f1.setname（"牡丹"）；

13 f1.setcountry（"中国"）；

14 f1.setcity（"沈阳"）；

15 f2.setcolor（"黄色"）；

16 f2.setname（"玫瑰"）；

17 f2.setcountry（"美国"）；

18 f2.setcity（"拉斯维加斯"）；

19 System.out.println（f1.tostring（））；

20 System.out.println（f2.tostring（））；

21 }

22 }

23 ///这是一个花的接口

24 ///其中的方法没有被实现

25 interface flower

26 {

27 void setcolor（String color1）；

28 void setname（String name）；

29 String getcolor（）；

30 String getname（）；

31 }

32 ///这是一个花的类，实现了上面的那个接口

33 ///通过设置器来设置对象的参数

34 ///通过访问器来获得对象的参数

35 ///通过tostring方法来让对象以字符串的形式输出

36 class flowers implements flower

37 {

38 private String color1；

39 private String name；

40 public void setcolor（String color1）

41 {

42 this.color1=color1；

43 }

44 public void setname（String name）

45 {

46 this.name=name；

47 }

48 public String getcolor（）

49 {

50 return color1；

51 }

52 public String getname（）

53 {

54 return name；

55 }

56 public String tostring（）

57 {

58 String information="花的名称是："+name+"；"+""+"花的颜色是："+color1；

59 return information；

60 }

61 }

62 ///这是一个花的类，继承了上面的那个类

63 ///通过设置器来设置对象的参数

64 ///通过访问器来获得对象的参数

65 ///通过tostring方法来让对象以字符串的形式输出

66 class flower1 extends flowers

67 {

68 private String country；

69 private String city；

70 public void setcountry（String country）

71 {

72 this.country=country；

73 }

74 public void setcity（String city）

75 {

76 this.city=city；

77 }

78 public String getcountry（）

79 {

80 return country；

81 }

82 public String getcity（）

83 {

84 return city；

85 }

86 public String tostring（）

87 {

88 String information=super.tostring（）+""+"这种花出自的国家："+country+"；

89 "+""+"出自的城市："+city；

90 return information；

91 }

92 }

【代码说明】第5～22行是主运行程序，第25～31行定义一个接口flower，第36～61行定义了一个类flowers实现接口flower。第66～92行定义了一个类flower1继承类flowers。

【运行效果】

花的名称是：牡丹；花的颜色是：红色这种花出自的国家：中国；出自的城市：沈阳

花的名称是：玫瑰；花的颜色是：黄色这种花出自的国家：美国；出自的城市：拉斯维加斯

这个程序通过抽象的思维方式，将花类抽象成只有两个属性的类，一个是颜色，一个是名称。如果要涉及个性，就使用继承的关系，或覆盖的方法来创建其他的类。其实抽象只是一个思路，不是一个具体的概念。养成抽象的思维方式，对于开发工作十分重要。

12.2　抽象类

什么是抽象类呢？抽象类就是使用关键字“abstract”来修饰的类。本节将通过理论与实际程序相结合的方式，来讲述抽象类的使用和特点。

12.2.1　什么是抽象类

前面介绍过抽象，抽象就是从具体到通性的方法。而抽象类就是指具有共同特性的类，其通过关键字“abstract”表示。这些类不能完全说明一个对象的属性，既然是这样，那为什么需要抽象类？

具有一个抽象或多个抽象方法的类，本身就要被定义为抽象类。所谓的抽象方法，就是带关键字“abstract”的方法。抽象类不只有抽象方法，也可以有具体的方法，一个类中只要有一个抽象方法，那么这个类就是抽象类。

抽象类是可以继承的，如果子类没有实现抽象类的全部抽象方法，那么子类也是抽象类。如果实现了抽象类的全部抽象方法，那么子类就不是抽象类。含有抽象方法的类一定是抽象类，但抽象类不一定含有抽象方法，也可以全部都是具体的方法。

抽象类不能被实例化，即不能使用关键字“new”来生成实例对象，但可以声明一个抽象类的变量指向具体子类的对象。

回到刚才的问题，为什么要有抽象类？抽象类的好处在于，当有的方法在父类中不想实现时，可以不用实现。下一节将通过一个综合实例来学习抽象类的使用。

12.2.2　抽象类的实例

【实例12.3】本节使用一个综合实例，来了解抽象类如何在实际开发中使用，以及使用抽象类的好处在哪里。先来分析这个实例的流程，如图12.3所示。

图　12.3　抽象类实例的流程图

首先研究学校抽象类的设计，代码如下所示。

01 ///创建一个抽象类

02 ///schoolname指学校　名称

03 ///classname指　班级名称

04 ///location指地址

05 ///set是设置器，用来赋值给对象

06 ///由于在这个类中存在抽象方法，所以这个类就是一个抽象类

07 abstract class school

08 {

09 String schoolname；

10 String classname；

11 String location；

12 public void setschoolname（String schoolname）//通过设置器来设置学校　名称、　班级名称

13 {

14 this.schoolname=schoolname；

15 }

16 public void setclassname（String classname）

17 {

18 this.classname=classname；

19 }

20 public String getschoolname（）

21 {

22 return schoolname；

23 }

24 public String getclassname（）

25 {

26 return classname；

27 }

28 abstract void setlocation（String location）；

29 abstract String getlocation（）；

30 public String tostring（）

31 {

32 String infor="学校　名称："+schoolname+"；"+""+"　班级名称"+classname；

33 return infor；

34 }

35 }

接下来，设计学生类并且继承上面的抽象类，代码如下。

36 ///创建一个学生类来继承这个抽象类，这个类中设置了学生　姓名、　学号

37 ///　性别、出　生年月、　家庭地址等参数。且使用设置器和访问器来编写它

38 ///studentname指学生　姓名

39 ///studentcode指学生　学号

40 ///studentsexy指学生　性别

41 ///studentbirthday指学生出　生年月

42 ///studentaddress指学生　家庭地址

43 ///set是类中的设置器

44 ///get是类中的访问器

45 class student1 extends school

46 {

47 String studentname；

48 String studentcode；

49 String studentsexy；

50 String studentbirthday；

51 String studentaddress；

52 void setlocation（String location）

53 {

54 this.location=location；

55 }

56 String getlocation（）

57 {

58 return location；

59 }

60 void setstudentname（String studentname）

61 {

62 this.studentname=studentname；

63 }

64 void setstudentcode（String studentcode）

65 {

66 this.studentcode=studentcode；

67 }

68 void setstudentsexy（String studentsexy）

69 {

70 this.studentsexy=studentsexy；

71 }

72 void setstudentbirthday（String studentbirthday）

73 {

74 this.studentbirthday=studentbirthday；

75 }

76 void setstudentaddress（String studentaddress）

77 {

78 this.studentaddress=studentaddress；

79 }

80 String getstudentname（）

81 {

82 return studentname；

83 }

84 String getstudentcode（）

85 {

86 return studentcode；

87 }

88 String getstudentsexy（）

89 {

90 return studentsexy；

91 }

92 String getstudentbirthday（）

93 {

94 return studentbirthday；

95 }

96 String getstudentaddress（）

97 {

98 return studentaddress；

99 }

100 public String tostring（）

101 {

102 String infor=super.tostring（）+"学校地址"+location+""+"学生　姓名

103"+studentname+"；"+""+"　学号："+studentcode+"；"+""+"　性别："+studentsexy+"；

104"+""+"出生日期："+studentbirthday+"；"+""+"　家庭地址："+studentaddress；

105 return infor；

106 }

107 }

下面再看看第二个学生类的设计，代码如下。

108 ///在这个类中，拥有抽象方法。所以这个类也是一个抽象类。同时也设置了姓名、

109 ///　学号、　性别、出　生年月、　家庭地址的参数

110 ///这个类是一个抽象类的学生类

111 ///studentname指学生　姓名

112 ///studentcode指学生　学号

113 ///studentsexy指学生　性别

114 ///studentbirthday指学生出　生年月

115 ///studentaddress指学生　家庭地址

116 ///set是类中的设置器

117 ///get是类中的访问器

118 abstract class student2 extends school

119 {

120 String studentname；

121 String studentcode；

122 String studentsexy；

123 String studentbirthday；

124 String studentaddress；

125 abstract void setlocation（String location）；

126 abstract String getlocation（）；

127 void setstudentname（String studentname）

128 {

129 this.studentname=studentname；

130 }

131 void setstudentcode（String studentcode）

132 {

133 this.studentcode=studentcode；

134 }

135 void setstudentsexy（String studentsexy）

136 {

137 this.studentsexy=studentsexy；

138 }

139 void setstudentbirthday（String studentbirthday）

140 {

141 this.studentbirthday=studentbirthday；

142 }

143 void setstudentaddress（String studentaddress）

144 {

145 this.studentaddress=studentaddress；

146 }

147 String getstudentname（）

148 {

149 return studentname；

150 }

151 String getstudentcode（）

152 {

153 return studentcode；

154 }

155 String getstudentsexy（）

156 {

157 return studentsexy；

158 }

159 String getstudentbirthday（）

160 {

161 return studentbirthday；

162 }

163 String getstudentaddress（）

164 {

165 return studentaddress；

166 }

167 public String tostring（）

168 {

169 String infor=super.tostring（）+"学生　姓名："+studentname+"；"+""+"　学号：

170"+studentcode+"；"+""+"　性别："+studentsexy+"；"+""+"出生日期："+studentbirthday+"；

171"+""+"　家庭地址："+studentaddress；

172 return infor；

173 }

174 }

再看第三个学生类的设计代码，如下所示。

175 ///创建一个类来继承上面的一个抽象类，来实现那个抽象类中的抽象方法。这个类中无抽象方法，

176 ///也没有声明为抽象类。这个类就不是抽象类

177 ///这个类是一个抽象类的学生类

178 ///studentname指学生　姓名

179 ///studentcode指学生　学号

180 ///studentsexy指学生　性别

181 ///studentbirthday指学生出　生年月

182 ///studentaddress指学生　家庭地址

183 ///set是类中的设置器

184 ///get是类中的访问器

185 class student3 extends student2

186 {

187 void setlocation（String location）

188 {

189 this.location=location；

190 }

191 String getlocation（）

192 {

193 return location；

194 }

195 public String tostring（）

196 {

197 String infor=super.tostring（）+"学校地址："+location+"；"+""；

198 return infor；

199 }

200 }

最后，看看以上所有的类，在主运行类中如何实现输出功能，代码如下所示。

201 ///这是一个主运行类

202 ///s1，s3是为学生类创建出来的两个不同的对象

203 ///set指设置器

204 ///通过tostring参数将对象中的参数一一以字符串形式输出

205 public class abstracttest

206 {

207 public static void main（String[]args）

208 {

209 student1 s1=new student1（）；

210 student3 s3=new student3（）；

211 s1.setschoolname（"重庆大学"）；

212 s1.setclassname（"计算机三班"）；

213 s1.setlocation（"沙坪坝"）；

214 s1.setstudentname（"王浩"）；

215 s1.setstudentcode（"95012"）；

216 s1.setstudentsexy（"男"）；

217 s1.setstudentbirthday（"1976-07-14"）；

218 s1.setstudentaddress（"重庆市解放碑"）；

219 s3.setschoolname（"四川大学"）；

220 s3.setclassname（"机械系一班"）；

221 s3.setlocation（"成都市"）；

222 s3.setstudentname（"董洁"）；

223 s3.setstudentcode（"33012"）；

224 s3.setstudentsexy（"女"）；

225 s3.setstudentbirthday（"1974-08-21"）；

226 s3.setstudentaddress（"成都市区"）；

227 System.out.println（s1.tostring（））；

228 System.out.println（s3.tostring（））；

229 }

230 }

【代码说明】第118～174行的类“student2”是个抽象类，因为它没有实现抽象类“school”的全部方法，而“student1”和“student3”则不是抽象类，因为它实现了抽象类的全部方法。

【运行效果】

学校　名称：重庆大学；　班级名称计算机三班学校地址沙坪坝学生　姓名王浩；　学号：95012；

性别：男；出生日期：1976-07-14；　家庭地址：重庆市解放碑

学校　名称：四川大学；　班级名称机械系一班学生　姓名：董洁；　学号：33012；　性别：女；

出生日期：1974-08-21；　家庭地址：成都市区学校地址：成都市；

抽象类的另一个用处是：在抽象类中定义一个统一的、通用的抽象方法，这个方法允许子类去覆盖，这样就能保证整个程序的一致性。

12.3　抽象与接口的区别

接口和抽象在很多方面相似，下面列出其共同点。

❑都不能创建实例对象，因为它们都是抽象的。

❑虽然不能直接通过关键字“new”创建对象实例，但可以声明变量，通过变量指向子类或实现类的对象，来创建对象实例。

两者也有不同点，如下所示。

❑Java不支持多重继承，即一个子类只能有一个父类，但一个子类可以实现多个接口。

❑接口内不能有实例字段，只能有静态变量，抽象类可以拥有实例字段。

❑接口内方法自动设置为“public”的，抽象类中的方法必须手动声明访问控制符。

12.4　常见疑难解答

12.4.1　抽象类和接口在概念上有什么区别

答：声明方法而不去实现它的类被称作抽象类，它用于创建一个体现某些基本行为的类，并为该类声明方法，但不能在该类中实现这些方法的情况。不能创建abstract类的实例，但可以创建一个变量，其类型是一个抽象类，并让它指向具体子类的一个实例，不能有抽象构造函数或抽象静态方法。abstract类的子类为其父类中的所有抽象方法提供实现，否则它们也是抽象类。

接口是抽象类的变体，在接口中，所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口只可定义“static final”成员变量，接口的实现与子类相似，但该实现类不能在接口定义中继承行为。

当类实现特殊接口时，需要实现这种接口的所有方法，然后，它可以在实现该接口的类的任何对象上调用接口的方法。抽象类允许使用接口名作为引用变量的类型，引用可以转换到接口类型或从接口类型转换，“instanceof”运算符可决定某对象的类是否实现了接口。

12.4.2　如何从设计理念上看待抽象类和接口

答：上面主要从语法定义和编程的角度论述了“abstract class”和“interface”的区别，这些层面的区别是比较低层次的、非本质的。这里将从另一个层面：“abstract class”和“interface”所反映出的设计理念，来分析二者的区别。作者认为，从这个层面进行分析才能理解二者的本质所在。

前面已经提到过，“abstract class”在Java语言中体现了一种继承关系。要想使得继承关系合理，父类和派生类之间必须存在“is a”关系，即父类和派生类在概念本质上应该是相同的。对于“interface”来说则不然，其并不要求“interface”的实现者和“interface”定义在概念本质上是一致的，仅仅是实现了“interface”定义的契约而已。为了使论述便于理解，下面将通过一个简单的实例进行说明。

考虑这样一个例子。假设有一个关于“Door”的抽象概念，该“Door”具有执行两个动作：“open”和“close”。此时可通过“abstract class”或者“interface”来定义一个表示该抽象概念的类型。定义方式分别如下所示：

使用抽象类来定义Door：

01 abstract class Door

02 {

03 abstract void open（）；

04 abstract void close（）；

05 }

使用接口定义“Door”：

01 interface Door

02 {

03 void open（）；

04 void close（）；

05 }

其他具体的“Door”类型可以继承“abstract class”方式定义的“Door”，或者使用“interface”方式定义的“Door”。看起来使用“abstract class”和“interface”没有大的区别，如果现在要求“Door”还要具有报警的功能，该如何设计针对该例子的类结构呢？本例主要是为了展示“abstract class”和“interface”反映在设计理念上的区别，并从设计理念层面对这些不同的方案进行分析。

【解决方案一】

简单的在“Door”的定义中增加一个“alarm”方法，如下所示：

01 abstract class Door

02 {

03 abstract void open（）；

04 abstract void close（）；

05 abstract void alarm（）；

06 }

或者

01 interface Door

02 {

03 void open（）；

04 void close（）；

05 void alarm（）；

06 }

那么具有报警功能的“AlarmDoor”的定义方式如下：

01 class AlarmDoor extends Door

02 {

03 void open（）{……}

04 void close（）{……}

05 void alarm（）{……}

06 }

或者

01 class AlarmDoor implements Door

02 {

03 void open（）{……}

04 void close（）{……}

05 void alarm（）{……}

06 }

这种方法违反了面向对象设计的一个核心原则ISP（Interface Segregation Priciple）。在“Door”的定义中，把Door概念本身固有的行为方法和另外一个概念“报警器”的行为方法混在了一起。如果是这样，那些仅仅依赖于“Door”这个概念的模块，会因为“报警器”这个概念的改变而改变，反之亦然。

【解决方案二】

既然open、close和alarm属于两个不同的概念，根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有：这两个概念都使用“abstract class”方式定义；两个概念都使用“interface”方式定义；一个概念使用“abstract class”方式定义，另一个概念使用“interface”方式定义。

显然，由于Java语言不支持多重继承，所以两个概念都使用“abstract class”方式定义是不可行的。后面两种方式都可行，但是对它们的选择，应该反映出对于问题领域中的概念本质的理解，对于设计意图的反映是否正确、合理。下面一一来分析。

如果两个概念都使用“interface”方式来定义，就反映出下面问题：

❑可能没有理解清楚问题领域，“AlarmDoor”在概念本质上到底是“Door”，还是报警。

❑如果对于问题领域的理解没有问题，比如：通过对于问题领域的分析发现“AlarmDoor”在概念本质上和“Door”是一致的。那么，在实现时就没有能够正确的揭示设计意图，因为在这两个概念的定义上（均使用interface方式定义）反映不出上述含义。

❑如果对于问题领域的理解是“AlarmDoor”，在概念本质上是“Door”，同时它具有报警的功能。该如何明确地反映出上述意思呢？“abstract class”在Java语言中表示一种继承关系，所以对于Door这个概念，应该使用“abstract class”方式来定义。另外，“AlarmDoor”又具有报警功能，说明它又能够完成报警概念中定义的行为，所以报警概念可以通过“interface”方式定义。

01 Class Door

02 {

03 abstract void open（）；

04 abstract void close（）；

05 }

06 interface Alarm

07 {

08 void alarm（）；

09 }

10 class AlarmDoor extends Door implements Alarm

11 {

12 void open（）{……}

13 void close（）{……}

14 void alarm（）{……}

15 }

如果认为“AlarmDoor”在概念本质上是报警器，同时又具有“Door”的功能，那么上述的定义方式就要反过来。

“abstract class”和“interface”是Java中两种定义抽象类的方式，它们之间有很大的相似性。程序员对它们的选择，又反映出对于问题领域中的概念本质的理解，对于设计的意图是否正确、合理，因为它们表现了概念间的不同关系，虽然都能实现需求的功能。

12.5　小结

这几章介绍的都是面向对象技术的一些特性，每章都会介绍一些新的概念，如果读者以前没有接触过这些特性，那么一定要先明白这些概念，然后针对每个特性自己都能编写一个简单的实例。如果这些读者都能做到了，那才可以继续后面的学习，如果这些读者还不明白，尽量重复做一些案例，深入理解这些特性。

12.6　习题

1.抽象类可以被实例化吗？抽象方法可以在普通类中吗？

2.如果不希望一个类被继承，或者不希望一个方法被覆写，用什么关键字？

3.简述接口与抽象的区别。

第13章　线程

线程是什么？线程有什么作用？线程具备提高执行速度的特点，在应用程序中使用非常广泛，例如现实生活中的网络聊天功能。本章将讲述线程的基本概念，学习如何使用线程进行程序代码的编写。

本章重点：

❑线程的使用环境。

❑创建并使用线程。

❑线程的让步、同步等操作。

13.1　线程的概念

在讲述线程的概念之前，先介绍什么是进程。这两个概念仅仅只有一字之差，但代表的是两个完全不同的概念。线程也是网络编程必须具备的知识点。

13.1.1　进程及其使用环境

在讲进程之前，先介绍什么是程序。程序是计算机指令的集合，它以文件形式存储在磁盘上，而进程就是一个执行中的程序，而每一个进程都有其独立的内存空间和系统资源。

进程就是一个运行的程序，Windows操作系统是支持多进程的操作系统，即同一时间可以执行多个程序，每个程序是在自己独立的内存空间内，使用自己被分配到的系统资源。其实，这种说法并不准确，一个CPU在某个时刻，实际上只能运行一个程序，即一个进程。所谓的支持多进程，其实就是CPU在交替轮流执行多个程序，例如，利用Windows操作系统可以一边听歌曲、一边上网等。

13.1.2　线程及其使用环境

线程是运行程序（进程）中单个顺序的小程序，一个进程可以由多个线程组成，而这多个线程共享同一个存储空间，这使得线程间的通信比较容易。在一个多进程的程序中，如果要切换到另一个进程，需要改变地址空间的位置。然而在多线程的程序中，就不会出现这种情况，因为它们位于同一个内存空间内，只需改变运行的顺序即可。

多线程指单个程序可通过同时运行多个不同线程，以执行不同任务。所谓同时，也要依据CPU。如果是多个CPU，则并发运行，如果是一个CPU，则根据系统具体情况，执行多个线程。

通过本节的介绍，读者对线程和进程有了简单的认识，下面将通过理论与实例结合的方法，讲述如何在程序中，利用线程的优点来编写程序代码。

13.2　线程的创建

在Java语言中，可通过系统提供的编程接口去创建线程。

13.2.1　如何创建线程

创建线程的方法一般有两种。

1.通过Runnable接口的方式创建线程

在Java中，线程是一种对象，但不是所有的对象都可以称为线程，只有实现了Runnable接口的类，才可以称为线程。下面先看看Runnable接口的定义。

public interface Runnable

{

public abstract void run（）；

}

Runnable接口只有一个抽象方法“run（）”，要实现这个接口，只要实现这个抽象方法就可以。只有实现了这个接口的类，才有资格称为线程。创建线程的结构如下：

Thread t=new Thread（runnable对象）；

runnable对象是指实现了Runnable接口类的对象。当线程执行时，runnable对象中的“run（）”方法会被调用。如果想要运行上面创建的线程，还需要调用一个Thread类的方法。

t.start（）；

【实例13.1】下面举个有关创建线程的实例。

01 ///创建两个线程，让其交替运行

02 public class threadtest

03 {

04 public static void main（String[]args）

05 {

06 compute c=new compute（）；

07 compute1 c1=new compute1（）；

08 Thread t=new Thread（c）；

09 Thread t1=new Thread（c1）；

10 t.start（）；

11 t1.start（）；

12 }

13 }

14 ///这是一个线程类

15 ///这个类主要是用来通过循环语句输出数字

16 class compute implements Runnable

17 {

18 int i=0；

19 public void run（）

20 {

21 for（int i=0；i＜10；i++）

22 {

23 System.out.println（i）；

24 }

25 }

26 }

27 ///这是一个线程类

28 ///这个类主要是用来通过循环语句输出字符串语句

29 class compute1 implements Runnable

30 {

31 public void run（）

32 {

33 for（int i=0；i＜10；i++）

34 {

35 System.out.println（"这个数字是："+i）；

36 }

37 }

38 }

【代码说明】第16～38行创建了两个线程compute和compute1，它们都实现接口Runnable。第6～7行分别创建这两个线程的对象，然后第10～11行开始运行线程。

【运行效果】

0 这个数字是：0

1 这个数字是：1

2 这个数字是：2

3 这个数字是：3

4 这个数字是：4

5 这个数字是：5

6 这个数字是：6

7 这个数字是：7

8 这个数字是：8

9 这个数字是：9

这个程序段中，创建了两个线程，不过读者多次运行后可能结果会有不同。那为什么会输出不同的结果呢？

因为在程序中存在一个执行问题。在Java技术中，线程通常是抢占式的，其不需要时间片的分配。抢占式调度模式指许多线程处于可以运行状态，即等待状态，但实际只有一个线程在运行。该线程一直运行到它终止，或者另一个具有更高优先级变成可运行状态。在后一种情况下，低优先级线程被高优先级线程抢占且获得运行机会。

说明

如果把上面的程序再运行一次，可能结果又不一样了，因为它的抢占方式，在目前这个程序段里是无法控制的。

Java调度器支持不同优先级线程抢占方式，不支持相同优先级线程时间片轮换。但如果所在的系统支持时间片轮换，它也会支持。如何才能输出自己想要的结果？后面会学习如何解决这个问题。

2.通过继承Thread类来创建线程

其实Thread类本身也实现了Runnable接口，所以只要让一个类能够继承Thread类，并覆盖“run（）”方法，也就创建了线程。

【实例13.2】下面把前面举过的例子用这种方法重新编写。

01 ///创建一个主运行类

02 ///在主运行方法中，创建两个线程，让其交替运行

03 public class threadtest1

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 compute1 t1=new compute1（）；

09 t.start（）；

10 t1.start（）；

11 }

12 }

13 ///创建compute线程类

14 ///在这个线程类中，通过循环语句输出1～10十个整型数据

15 class compute extends Thread

16 {

17 int i=0；

18 public void run（）

19 {

20 for（int i=0；i＜10；i++）

21 {

22 System.out.println（i）；

23 }

24 }

25 }

26 ///创建compute1线程类

27 ///在这个线程类中，通过循环语句输出说明性语句

28 class compute1 extends Thread

29 {

30 public void run（）

31 {

32 for（int i=0；i＜10；i++）

33 {

34 System.out.println（"这个数字是："+i）；

35 }

36 }

37 }

【代码说明】第15～37行创建了两个线程，它们都继承自Thread类。第7～10行创建两个线程对象，并开始运行线程。

【运行效果】

0 这个数字是：0

1 这个数字是：1

2 这个数字是：2

3 这个数字是：3

4 这个数字是：4

5 这个数字是：5

6 这个数字是：6

7 这个数字是：7

8 这个数字是：8

9 这个数字是：9

说明　读者的运行结果可能不同，但没有关系。

创建方式虽然不一样，但运行效果仍然一样。下一节将通过一个稍微复杂的综合实例，让读者对线程的概念有一个更好的认识。通过这个综合实例，也可以复习前面讲过的一些知识，如接口、继承、多态、覆盖等，因为这些知识点在程序开发中一直会出现。

13.2.2　通过实例熟悉如何创建线程

【实例13.3】下面通过一个综合实例，来看看如何创建线程。针对下面的实例，先来看看它的流程，如图13.1所示。

图　13.1　老师和学生线程的创建

下面分步骤讲解这个综合程序实例。先定义一个学校的接口，代码如下所示。

01 ///创建一个学校的接口

02 ///定义了三个方法

03 interface school

04 {

05 void set（String schoolname, String grade）；

06 String getschool（）；

07 String getgrade（）；

08 }

接下来将看看学生线程类的设计，代码如下所示。

09 ///创建一个学生类，并且实现了学校这个接口，让其成为线程类

10 ///schoolname是学校　名称

11 ///grade是　所在年级

12 ///studentname是学生　姓名

13 ///studentcode是学生　学号

14 ///studentsexy是　性别

15 ///studentcourse是所学课程

16 ///studentavg是学生课程平均分

17 ///set是设置器

18 ///get是访问器

19 ///tostring是让对象能以字符串形式输出的方法

20 class student implements Runnable, school

21 {

22 String schoolname；

23 String grade；

24 String studentname；

25 String studentcode；

26 String studentsexy；

27 String studentcourse；

28 String studentavg；

29 public student（String studentname, String studentcode, String studentsexy, String

30 studentcourse）

31 {

32 this.studentname=studentname；

33 this.studentcode=studentcode；

34 this.studentsexy=studentsexy；

35 this.studentcourse=studentcourse；

36 this.tostring（）；

37 }

38 public void set（String schoolname, String grade）

39 {

40 this.schoolname=schoolname；

41 this.grade=grade；

42 }

43 public String getschool（）

44 {

45 return schoolname；

46 }

47 public String getgrade（）

48 {

49 return grade；

50 }

51 public void run（）

52 {

53 int i=1；

54 int avg=85；

55 for（；i＜13；i++）

56 {

57 System.out.println（"这个学生的平均成绩是："+i+"月"+（avg+i*2））；

58 }

59 }

60 public String tostring（）

61 {

62 String information="学校　名称："+schoolname+"；"+""+"所读年级："+grade+"；

63"+""+"学生　姓名："+studentname+"；"+""+"学生　学号："+studentcode+"；"+""+"学生　性别：

64"+studentsexy+"；"+""+"所读专业："+studentcourse+"；"+""+"学生平均分"+studentavg；

65 return information；

66 }

67 }

再看看教师类的设计，代码如下所示。

68 ///创建一个老师类并且实现上面定义的那个接口，以及让它成为线程类

69 ///schoolname是学校　名称

70 ///grade是老师　所在年级

71 ///teachername是老师姓名

72 ///teachercode是老师工号

73 ///teachersexy是老师　性别

74 ///teachercourse是所教的课程

75 ///teachersalary是薪水

76 ///set是设置器

77 ///get是访问器

78 ///tostring是让对象能以字符串形式输出的方法

79 class teacher implements Runnable, school

80 {

81 String schoolname；

82 String grade；

83 String teachername；

84 String teachercode；

85 String teachersexy；

86 String teachercourse；

87 String teachersalary；

88 public teacher（String teachername, String teachercode, String teachersexy, String

89 teachercourse）

90 {

91 this.teachername=teachername；

92 this.teachercode=teachercode；

93 this.teachersexy=teachersexy；

94 this.teachercourse=teachercourse；

95 this.tostring（）；

96 }

97 public void set（String schoolname, String grade）

98 {

99 this.schoolname=schoolname；

100 this.grade=grade；

101 }

102 public String getschool（）

103 {

104 return schoolname；

105 }

106 public String getgrade（）

107 {

108 return grade；

109 }

110 public void run（）

111 {

112 int i=1；

113 int teachersalary=2000；

114 for（；i＜13；i++）

115 {

116 System.out.println（"这个老师的薪水是："+i+"月"+（teachersalary+i*300））；

117 }

118 }

119 public String tostring（）

120 {

121 String information="学校　名称："+schoolname+"；"+""+"所教年级："+grade+"；

122"+""+"　教师姓名："+teachername+"；"+""+"　教师工号："+teachercode+"；"+""+"教师　性别：

123"+teachersexy+"；"+""+"所教课程："+teachercourse+"；"+""+"教师薪水"+teachersalary；

124 return information；

125 }

126 }

最后，主运行程序实现这些对象输出的代码如下所示。

127 ///主运行类

128 ///t是老师类的一个对象

129 ///th和th1是一个线程对象

130 ///set设置器赋值给了每一个对象

131 ///两个start方法代表两个线程的运行

132 public class thread1

133 {

134 public static void main（String[]args）

135 {

136 teacher t=new teacher（"董洁"，"22334"，"女"，"英语"）；

137 t.set（"上海师范学院"，"三年级"）；

138 Thread th=new Thread（t）；

139 student s=new student（"张俊"，"978003"，"男"，"计算机"）；

140 s.set（"北京大学"，"大四"）；

141 Thread th1=new Thread（s）；

142 th.start（）；

143 th1.start（）；

144 }

145 }

【代码说明】代码第3～8行定义了接口school，第20～67行定义了学生类student，其实现了两个接口Runnable和school。第79～126行定义了教师类teacher，也是实现了两个接口。

注意　一个类可以实现两个或多个接口，但一个类不能继承两个或两个以上的类。

【运行效果】

这个老师的薪水是：1月2300

这个学生的平均成绩是：1月87

这个老师的薪水是：2月2600

这个学生的平均成绩是：2月89

这个老师的薪水是：3月2900

这个学生的平均成绩是：3月91

这个老师的薪水是：4月3200

这个学生的平均成绩是：4月93

这个老师的薪水是：5月3500

这个学生的平均成绩是：5月95

这个老师的薪水是：6月3800

这个学生的平均成绩是：6月97

这个老师的薪水是：7月4100

这个学生的平均成绩是：7月99

这个老师的薪水是：8月4400

这个学生的平均成绩是：8月101

这个老师的薪水是：9月4700

这个学生的平均成绩是：9月103

这个老师的薪水是：10月5000

这个学生的平均成绩是：10月105

这个老师的薪水是：11月5300

这个学生的平均成绩是：11月107

这个老师的薪水是：12月5600

这个学生的平均成绩是：12月109

这个程序段从某种意义上说，是不可行的，因为它没有按照程序员的意图来输出。本例的主要目的是学习如何创建一个线程，并且从程序中可以看到一个接口的实现过程。

13.3　线程的使用

前面介绍了如何创建线程，但从前面的实例可以看到，其输出结果不尽人意。为什么呢？因为在线程的应用中存在很多新的知识点，需要大家去学习。本节将会逐个进行解释。

13.3.1　线程的优先级

线程的执行是一种抢占方式，优先级高的比优先级低的要获得更多的执行时间，如果想让一个线程比其他线程有更多的时间运行，可以通过设置线程的优先级解决。

如一个线程创建后，可通过在线程中调用“setPriority（）”方法，来设置其优先级。具体方法如下：

public final void setPriority（int newPriority）；

newPriority是一个1～10之间的正整数，数值越大，优先级别越高。系统有一些常数值，如下所示。

❑public final static int MIN_PRIORITY=1：表示最低优先级。

❑public final static int MAX_PRIORITY=10：表示最高优先级。

❑public final static int NORM_PRIORITY=5：表示默认优先级。

【实例13.4】下面来看一个实例，通过实例查看如果设置了线程的优先级后，程序的输出会有什么不同。

01 ///创建一个主运行类

02 ///在主运行方法中，创建两个线程，让其交替运行

03 public class thread2

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 compute1 t1=new compute1（）；

09 t.setPriority（10）；

10 t1.setPriority（1）；

11 t.start（）；

12 t1.start（）；

13 try

14 {

15 Thread.sleep（5000）；//等待5秒

16 }

17 catch（InterruptedException e）{}

18 }

19 }

20 ///创建compute线程类

21 ///在这个线程类中，通过循环语句输出1～10十个整型数据

22 class compute extends Thread

23 {

24 int i=0；

25 public void run（）

26 {

27 for（int i=0；i＜10；i++）

28 {

29 System.out.println（i）；

30 }

31 }

32 }

33 ///创建compute1线程类

34 ///在这个线程类中，通过循环语句输出说明语句

35 class compute1 extends Thread

36 {

37 public void run（）

38 {

39 for（int i=0；i＜10；i++）

40 {

41 System.out.println（"这个数字是："+i）；

42 }

43 }

44 }

【代码说明】因为机器运行的速度太快了，为了让程序显示的效果更加突出，代码第13～16行使用了sleep（）方法，让程序等待5秒。第9～10行是分别设置两个线程的优先级。

【运行效果】

0 1

2 3

4 5

6 7

8 9

这个数字是：0

这个数字是：1

这个数字是：2

这个数字是：3

这个数字是：4

这个数字是：5

这个数字是：6

这个数字是：7

这个数字是：8

这个数字是：9

此时的输出变得很有规律，因为程序中将输出数字的线程的优先级设为最高了，而输出汉字的线程是系统最低优先级1，所以程序执行时，会给数字输出线程更多的时间执行。

说明　结果可能随机器的运算能力不同而不同。

13.3.2　线程的休眠与唤醒

上面的程序段使用了sleep（）方法，这里涉及线程的休眠和唤醒功能。那什么是线程的休眠和唤醒呢？下面将分别来讲述。

1.线程的休眠

线程的休眠，是指线程暂时处于等待的一种状态。通俗地说，就是线程暂时停止运行了。要达到这种功能需要调用Thread类的“sleep（）”方法。“sleep（）”方法可以使线程在指定的时间，处于暂时停止的状态，等到指定时间结束后，暂时停止状态就会结束，然后继续执行没有完成的任务。sleep（）方法的方法结构如下所示。

public static native void sleep（long millis）throws interruptedExcption

“millis”参数是指线程休眠的毫秒数。上述代码涉及抛出异常的问题，由于目前还没有开始讲述抛出异常，所以只要知道这个是抛出异常即可。

【实例13.5】下面看一个有关休眠的实例。

01 ///创建一个主运行类

02 ///在主运行方法中，创建两个线程，让其交替运行

03 public class thread3

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 compute1 t1=new compute1（）；

09 t.start（）；

10 t1.start（）；

11 }

12 }

13 ///创建compute线程类

14 ///在这个线程类中，通过循环语句输出1～10十个整型数据

15 ///通过sleep让线程休眠1秒

16 class compute extends Thread

17 {

18 int i=0；

19 public void run（）

20 {

21 for（int i=0；i＜10；i++）

22 {

23 System.out.println（i）；

24 try

25 {

26 sleep（1000）；

27 }

28 catch（Exception e）{}

29 }

30 }

31 }

32 ///创建compute1线程类

33 ///在这个线程类中，通过循环语句输出说明语句

34 ///通过sleep让线程休眠1秒

35 class compute1 extends Thread

36 {

37 public void run（）

38 {

39 for（int i=0；i＜10；i++）

40 {

41 System.out.println（"这个数字是："+i）；

42 try

43 {

44 sleep（1000）；

45 }

46 catch（Exception e）{}

47 }

48 }

49 }

【代码说明】看看这个程序的输出结果，是否已经达到设计这个程序的初衷了，为什么会有这种效果呢？那是因为程序让两个线程分别在输出一条后，暂时休眠，等待另一个输出。这样第1个线程休眠时，第2个线程输出。第2个线程休眠时，第1个线程又输出，但经过一段时间后，还是会有不同的现象出现。

【运行效果】

0 这个数字是：0

1 这个数字是：1

2 这个数字是：2

3 这个数字是：3

这个数字是：4

4 这个数字是：5

5 6

这个数字是：6

这个数字是：7

7 8

这个数字是：8

9 这个数字是：9

如果让刚才的程序改变休眠时间，那么输出结果会是什么样呢？

【实例13.6】现在修改一下这个例子。

01 ///创建一个主运行类

02 ///在主运行方法中，创建两个线程，让其交替运行

03 public class thread4

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 compute1 t1=new compute1（）；

09 t.start（）；

10 t1.start（）；

11 }

12 }

13 ///创建compute线程类

14 ///在这个线程类中，通过循环语句输出1～10十个整型数据

15 ///通过sleep让线程休眠1秒

16 class compute extends Thread

17 {

18 int i=0；

19 public void run（）

20 {

21 for（int i=1；i＜10；i++）

22 {

23 System.out.println（i）；

24 try

25 {

26 sleep（1000）；

27 }

28 catch（Exception e）{}

29 }

30 }

31 }

32 ///创建compute1线程类

33 ///在这个线程类中，通过循环语句输出说明语句

34 ///通过sleep让线程休眠2秒

35 class compute1 extends Thread

36 {

37 public void run（）

38 {

39 for（int i=0；i＜=4；i++）

40 {

41 System.out.println（"这个数字是："+（2*i+1））；

42 try

43 {

44 sleep（2000）；

45 }

46 catch（Exception e）{}

47 }

48 }

49 }

【代码说明】这个程序段，将第2个线程休眠时间变为第1个线程休眠时间的两倍。那么输出结果会是第1个线程输出两条后，第2个线程输出一条吗？答案是：不一定。

【运行效果】

1 这个数字是：1

2 这个数字是：3

3 4

5 这个数字是：5

6 7

这个数字是：7

8 这个数字是：9

9

当一个线程处于休眠状态，如果开始设置了休眠时间是1000毫秒，但是想在休眠了500毫秒时，让它继续执行，如何办呢？此时可以使用线程的唤醒功能。

2.线程的唤醒

线程的唤醒是指，使线程从休眠等待状态进入可执行状态，可以调用方法“interrupt（）”实现。

【实例13.7】下面将举一个有关唤醒的实例。

01 ///这是一个主运行类

02 ///创建一个线程对象，让其运行

03 public class thread5

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 t.start（）；

09 }

10 }

11 ///创建一个线程类，在这个类中，通过休眠来输出不同结果

12 class compute extends Thread

13 {

14 int i=0；

15 public void run（）

16 {

17 System.out.println（"在工作中，不要打扰"）；

18 try

19 {

20 sleep（1000000）；

21 }

22 catch（Exception e）

23 {

24 System.out.println（"哦，电话来了"）；

25 }

26 }

27 }

【代码说明】第20行使线程休眠1000000毫秒，并没有唤醒它。第8行开始运行线程。

【运行效果】

在工作中，不要打扰

【实例13.8】在这个程序中没有唤醒代码，那么程序将停留1000秒后，再输出“哦，电话来了”这句话。如果加了唤醒语句，代码如下。

01 //这是一个主运行类

02 ///创建一个线程对象，通过唤醒功能，使其输出结果发生变化

03 public class thread6

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 t.start（）；

09 t.interrupt（）；

10 }

11 }

12 ///创建一个线程类，在这个类中，通过休眠，让线程运行输出不同的结果

13 class compute extends Thread

14 {

15 int i=0；

16 public void run（）

17 {

18 System.out.println（"在工作中，不要打扰"）；

19 try

20 {

21 sleep（1000000）；

22 }

23 catch（Exception e）{System.out.println（"哦，电话来了"）；}

24 }

25 }

【代码说明】使用了第9行的唤醒语句后。在输出“在工作中，不要打扰”后休眠，然后又立即会被唤醒输出“哦，电话来了”。

【运行效果】

在工作中，不要打扰

哦，电话来了

13.3.3　线程让步

所谓线程让步，就是使当前正在运行的线程对象退出运行状态，让其他线程运行，其方法是通过调用yield（）来实现。这个方法不能将运行权让给指定的线程，只是允许这个线程把运行权让出来，至于给谁，这就是抢占功能的事情了。

【实例13.9】现在列举一个实例。

01 ///这是一个主运行类

02 ///在主运行方法中，通过创建两个线程对象，让其交替执行

03 public class thread7

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 compute1 t1=new compute1（）；

09 t.start（）；

10 t1.start（）；

11 }

12 }

13 ///创建一个线程类

14 ///通过循环语句来输出十个整型数据

15 ///通过让步程序让此线程停止运行

16 class compute extends Thread

17 {

18 int i=0；

19 public void run（）

20 {

21 for（int i=0；i＜10；i++）

22 {

23 System.out.println（i）；

24 yield（）；

25 }

26 }

27 }

28 ///创建一个线程类

29 ///通过循环语句来输出说明语句

30 class compute1 extends Thread

31 {

32 public void run（）

33 {

34 for（int i=0；i＜10；i++）

35 {

36 System.out.println（"这个数字是："+i）；

37 }

38 }

39 }

【代码说明】第16～39行定义了两个线程，其中第24行表示第一个线程会出现让步的情况。第9～10行开始运行线程。

【运行效果】

0 这个数字是：0

这个数字是：1

这个数字是：2

这个数字是：3

这个数字是：4

这个数字是：5

这个数字是：6

这个数字是：7

1 这个数字是：8

这个数字是：9

2 3

4 5

6 7

8 9

从运行效果来看，第1个线程比第2个线程运行的几率要小，因为它总是放弃运行权。

13.3.4　线程同步

本节将详细地讲述为什么要使用线程同步，并且通过现实生活中程序开发的实例，来说明线程同步的用法。

前面讲述过，线程的运行权通过一种叫抢占的方式获得。一个程序运行到一半时，突然被另一个线程抢占了运行权，此时这个线程数据处理了一半，而另一个线程也在处理这个数据，那么会出现重复操作数据的现象，最终整个系统将会混乱。

【实例13.10】下面先看一个实例，通过这个实例，了解如果线程不是同步的，将会出现什么样的结果。

01 ///这是一个主运行类

02 ///在主运行方法中，通过创建两个线程对象，让其交替执行

03 public class thread8

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（'a'）；

08 compute t1=new compute（'b'）；

09 t.start（）；

10 t1.start（）；

11 }

12 }

13 ///创建一个线程类

14 ///在这个线程类中，使用循环语句输出字符

15 class compute extends Thread

16 {

17 char ch；

18 compute（char ch）

19 {

20 this.ch=ch；

21 }

22 public void print（char ch）

23 {

24 for（int i=0；i＜10；i++）

25 {

26 System.out.print（ch）；

27 }

28 }

29 public void run（）

30 {

31 print（ch）；

32 System.out.println（）；

33 }

34 }

【代码说明】第7～8创建两个线程，它们输出不同的字符，从结果可以看出，这两个字符是交替输出的。

【运行效果】

abababababababababab

【实例13.11】从这个程序可能看不出线程同步，如果将上面的程序段修改后，再来看看会出现什么样的变化。

01 ///这是一个主运行类

02 ///在主运行方法中，通过创建两个线程对象，让其交替执行

03 public class thread9

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（'a'）；

08 compute t1=new compute（'b'）；

09 t.start（）；

10 t1.start（）；

11 }

12 }

13 ///创建一个线程类

14 ///在这个线程类中，使用循环语句输出字符

15 class compute extends Thread

16 {

17 char ch；

18 compute（char ch）

19 {

20 this.ch=ch；

21 }

22 public void print（char ch）

23 {

24 for（int i=0；i＜10；i++）

25 {

26 System.out.print（ch）；

27 }

28 }

29 public void run（）

30 {

31 for（int i=1；i＜10；i++）

32 {

33 print（ch）；

34 System.out.println（）；

35 }

36 }

37 }

【代码说明】以上的程序段，可以看出两个线程循环输出时，出现抢占现象，于是整个输出就会混乱，一会儿输出“a”，一会儿输出“b”。

【运行效果】

abababababababababab

abababababababababab

abababababababababab

abababababababababab

abababababababababab

abababababababababab

abababababababababab

abababababababababab

abababababababababab

如何解决这个问题呢？这就涉及线程的同步。在Java语言中，解决同步问题的方法有两种：一种是同步块，一种是同步方法。

1.同步块

同步块是使具有某个对象监视点的线程，获得运行权限的一种方法，每个对象只能在拥有这个监视点的情况下，才能获得运行权限。举个例子，一个圆桌，有4个人吃饭，但是只有一个勺子，4人中只有一个人能吃饭，并且这个人必须是拥有勺子的人，而这个勺子就相当于同步块中的监视点。

同步块的结构如下：

synchronized（someobject）

{

代码段

}

“someobject”是一个监视点对象，可以是实际存在的，也可以是假设的。在很多程序段中，这个监视点对象都是假设的。其实这个监视点就相当于一把锁，给一个线程上了锁，那么其他线程就会被拒之门外，就无法得到这把锁。直到这个线程执行完了，才会将这个锁交给其他线程。其他的线程得到锁后，将自己的程序锁住，再将其他线程拒之门外。

【实例13.12】将前面的程序修改后，再来看看输出结果。

01///这是一个主运行类

02 ///在主运行方法中，通过创建三个线程对象，让其交替执行

03 public class thread10

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（'a'）；

08 compute t1=new compute（'b'）；

09 compute t2=new compute（'c'）；

10 t.start（）；

11 t1.start（）；

12 t2.start（）；

13 }

14 }

15 ///创建一个线程类

16 ///在这个线程类中，使用循环语句输出字符

17 ///在run方法中，使用同步块来给线程加一把锁

18 class compute extends Thread

19 {

20 char ch；

21 static Object obj=new Object（）；

22 compute（char ch）

23 {

24 this.ch=ch；

25 }

26 public void print（char ch）

27 {

28 for（int i=1；i＜10；i++）

29 {

30 System.out.print（ch）；

31 }

32 }

33 34

public void run（）

35 {

36 synchronized（obj）

37 {

38 for（int i=1；i＜10；i++）

39 {

40 print（ch）；

41 System.out.println（）；

42 }

43 }

44 }

45 }

【代码说明】第36行使用了“synchronized（）”方法同步线程，所以每个线程都是先完成再执行下一个线程。

【运行效果】

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

aaaaaaaaa

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

bbbbbbbbb

ccccccccc

ccccccccc

ccccccccc

ccccccccc

ccccccccc

ccccccccc

ccccccccc

ccccccccc

ccccccccc

在运行程序中添加一个监视点，那么将锁内的程序段执行完后，就会自动打开锁，再由另外两个线程抢占这个锁。然后反复执行这个同步块中的程序，这样一个线程执行完后，才会执行另一个线程。对于多线程操作同一个数据，就不会出现混乱的现象。

【实例13.13】下面再看一个有关同步块的程序段例子。

01///这是一个主运行类

02 ///在主运行方法中，通过创建三个线程对象，让其交替执行

03 public class thread11

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 new Thread（t）.start（）；

09 new Thread（t）.start（）；

10 new Thread（t）.start（）；

11 }

12 }

13 ///创建一个线程类

14 ///在这个线程类中，使用循环语句输出字符

15 ///在run方法中，使用同步块来给线程加一把锁

16 class compute extends Thread

17 {

18 int i=10；

19 static Object obj=new Object（）；

20 public void print（）

21 {

22 System.out.println（Thread.currentThread（）.getName（）+"："+i）；

23 i--；

24 }

25 public void run（）

26 {

27 while（i＞0）

28 {

29 synchronized（obj）

30 {

31 print（）；

32 }

33 try

34 {

35 sleep（1000）；

36 }

37 catch（Exception e）{}

38 }

39 }

40 }

【代码说明】从上面的程序段可以看出，3个线程操作同一个数，通过使用同步块，使得整个处理过程变得很有条理。线程1处理完数字10后，线程2处理数字9，之后，线程3再处理数字8，这样循环下去，使得每个线程都能单独地处理同一数据，而不受其他线程的影响。

【运行效果】

Thread-1：10

Thread-2：9

Thread-3：8

Thread-2：7

Thread-3：6

Thread-1：5

Thread-2：4

Thread-3：3

Thread-1：2

Thread-2：1

Thread-3：0

Thread-1：-1

2.同步方法

同步方法就是对整个方法进行同步。它的结构是：

synchronized void f（）

{

代码

}

【实例13.14】下面使用同步方法来修改上面的程序段。

01 ///这是一个主运行类

02 ///在主运行方法中，通过创建三个线程对象，让其交替执行

03 public class thread12

04 {

05 public static void main（String[]args）

06 {

07 compute t=new compute（）；

08 new Thread（t）.start（）；

09 new Thread（t）.start（）；

10 new Thread（t）.start（）；

11 }

12 }

13 ///创建一个线程类

14 ///在这个线程类中，使用循环语句输出字符

15 ///在run方法中，使用同步方法来给线程加一把锁

16 class compute extends Thread

17 {

18 int i=10；

19 static Object obj=new Object（）；

20 synchronized void print（）

21 {

22 System.out.println（Thread.currentThread（）.getName（）+"："+i）；

23 i--；

24 }

25 public void run（）

26 {

27 while（i＞0）

28 {

29 print（）；

30 try

31 {

32 sleep（1000）；

33 }

34 catch（Exception e）{}

35 }

36 }

37 }

【代码说明】第20行使用了同步化关键字synchronized来同步方法print（），第32行使用了休眠方法sleep（），第8～10行开始运行3个进程。

【运行效果】

Thread-1：10

Thread-2：9

Thread-3：8

Thread-3：7

Thread-1：6

Thread-2：5

Thread-3：4

Thread-1：3

Thread-2：2

Thread-3：1

Thread-1：0

Thread-2：-1

从上面的结果可以看出，使用同步块和同步方法的输出结果都是一样的。讲述了线程的所有知识点后，下面将使用一个生产者和消费者的综合模型实例，巩固多线程编程的方法。

说明

从结果显示来看，可能与读者机器上的结果不同，这也是线程的一个特色，每次运行结果都可能不同。

13.4　实例分析

本节将分析线程的一些比较实用的实例，通过这些实例和流程图，希望读者能够掌握线程的精髓。

13.4.1　生产者与消费者的模拟程序设计及分析

下面将使用生产者和消费者的模拟程序来编写代码，其条件是：

1个汉堡包店，有1个厨师和1个营业员，厨师负责做汉堡，营业员负责卖汉堡，当然还有1个存放汉堡的箱子。厨师不停地做汉堡，做好了就放在箱子里面，每次客人来的时候，营业员会从箱子里面取出1个汉堡卖掉。假设前提是客人每隔1秒来1个，也就是说营业员1秒卖1个汉堡，而厨师3秒做1个汉堡。

【实例13.15】目前总共只能做10个汉堡，箱子中已经有了5个汉堡，请编写一个程序代码来显示这个买卖的关系。

下面先看看程序的流程，如图13.2所示。

为了更好地掌握这个程序代码编制，下面将分步骤讲述这个代码。

图　13.2　生产者和消费者线程模拟程序

（1）设计汉堡包盒子类，将其作为监视点，代码如下所示。

01 ///创建一个汉堡包盒子的类

02 ///totalmaterial是指所有的能做汉堡包的材料

03 ///box是一个监视器对象

04 ///sales是指销售了多少个汉堡包

05 ///production是指总共有多少个汉堡包

06 class ham //把装汉堡包的盒子作为监视器类

07 {

08 static Object box=new Object（）；

09 static int totalmaterial=10；

10 static int sales=0；

11 static int production=5；

12 }

（2）设计厨师类，代码如下所示。

13 ///这是一个厨师的线程类

14 ///make方法使用了一个同步块，在这个函数里会不断地生产汉堡包

15 ///run方法就是线程需要运行的内容

16 ///使用循环语句来保证在汉堡包材料用完之前，不断地生产汉堡包

17 ///使用判断语句判断只要有汉堡包，厨师就通知营业员可以卖了

18 class hmaker extends Thread //厨师线程类

19 {

20 public void make（）

21 {

22 synchronized（ham.box）

23 {

24 （ham.production）++；

25 try

26 {

27 ham.box.notify（）；

28 }

29 catch（Exception e）{}

30 }

31 }

32 public void run（）

33 {

34 while（ham.production＜ham.totalmaterial）

35 {

36 if（ham.production＞0）

37 {

38 System.out.println（"厨师"+getName（）+"："+"汉堡来了（总共

39 "+（ham.production-ham.sales）+"个）"）；

40 }

41 try

42 {

43 sleep（3000）；

44 }

45 catch（Exception e）{}

46 make（）；

47 }

48 }

49 }

（3）看看主运行程序的设计代码，如下所示。

50 ///这是一个营业员的线程类

51 ///sell函数是营业员卖汉堡包的方法

52 ///run方法就是线程需要运行的内容

53 ///使用条件判断语句来判断汉堡包盒子里面是否还有汉堡包

54 ///在run方法内使用循环语句来使得营业员在盒子里有汉堡包的情况下不断地卖

55 class hassistant extends Thread

56 {

57 public void sell（）

58 {

59 if（ham.production==0）

60 {

61 System.out.println（"营业员：顾客朋友们，请稍微等一下，汉堡没了！"）；

62 }

63 try

64 {

65 ham.box.wait（）；

66 }

67 catch（Exception e）{}

68 ham.sales++；

69 System.out.println（"营业员：顾客好，汉堡上来了，（总共卖了"+ham.sales+"个）"）；

70 }

71 public void run（）

72 {

73 while（ham.sales＜ham.production）

74 {

75 try

76 {

77 sleep（1000）；

78 }

79 catch（Exception e）{}

80 sell（）；

81 }

82 }

83 }

84 public class thread13

85 {

86 public static void main（String[]args）

87 {

88 hmaker maker=new hmaker（）；

89 hassistant assistant=new hassistant（）；

90 maker.setName（"甲"）；

91 maker.start（）；

92 assistant.start（）；

93 }

94 }

【代码说明】首先根据同步的原理，第22行将存放汉堡的箱子作为监控点，为什么使用它作监控点呢？因为无论厨师还是营业员，都操作这个共同对象。第18～83行建立一个厨师类和一个营业员类。

厨师类能做到汉堡的产品数量，应该小于总材料的数量。在这个前提条件下，如果产品不等于零，就让厨师告诉一声：汉堡上来了，总共有多少个。接下来开始3秒做1个汉堡，做好了后，再通知一声：汉堡上来了，总共几个。另外，只要箱子里的汉堡不等于零，就通知营业员可以卖了。而营业员类，主要是销售汉堡，每1秒卖1个，如果箱子里面的汉堡等于零，就通知顾客：汉堡没了，需要等待。经过这些分析，重新阅读上面的程序，了解每个类的原理。

【运行效果】

厨师甲：汉堡来了（总共5个）

营业员：顾客好，汉堡上来了，（总共卖了1个）

营业员：顾客好，汉堡上来了，（总共卖了2个）

厨师甲：汉堡来了（总共4个）

营业员：顾客好，汉堡上来了，（总共卖了3个）

营业员：顾客好，汉堡上来了，（总共卖了4个）

营业员：顾客好，汉堡上来了，（总共卖了5个）

厨师甲：汉堡来了（总共2个）

营业员：顾客好，汉堡上来了，（总共卖了6个）

营业员：顾客好，汉堡上来了，（总共卖了7个）

厨师甲：汉堡来了（总共1个）

厨师甲：汉堡来了（总共2个）

13.4.2　多消费者的模拟程序设计及分析

【实例13.16】本节将接着上面的实例，再增加条件：增加一个营业员，即现在是两个营业员在销售汉堡，而一个厨师在做汉堡。

先来看看程序的流程，如图13.3所示。

图　13.3　多消费者线程模拟程序

为了能够更加清晰地理解这个程序，下面将分步骤讲述。

（1）设计箱子类，代码如下所示。

01 ///创建一个汉堡包盒子的类

02 ///totalmaterial是指所有的能做汉堡包的材料

03 ///box是一个监视器对象

04 ///sale1，sale2是指销售了多少个汉堡包

05 ///production是指总共有多少个汉堡包

06 class ham

07 {

08 static Object box=new Object（）；

09 static int totalmaterial=10；

10 static int sales1=0；

11 static int sales2=0；

12 static int production=5；

13 }

（2）设计厨师类，代码如下所示。

14 ///这是一个厨师的线程类

15 ///make方法使用了一个同步块，在这个函数里会不断地生产汉堡包

16 ///run方法就是线程需要运行的内容

17 ///使用循环语句来保证在汉堡包材料用完之前，不断地生产汉堡包

18 ///使用判断语句判断只要有汉堡包，厨师就通知营业员可以卖了

19 class hmaker extends Thread

20 {

21 public void make（）

22 {

23 synchronized（ham.box）

24 {

25 （ham.production）++；

26 System.out.println（"厨师"+getName（）+"："+"汉堡来了（总共

27 "+（ham.production-ham.sales1-ham.sales2）+"个）"）；

28 try

29 {

30 ham.box.notify（）；

31 }

32 catch（Exception e）{}

33 }

34 }

35 public void run（）

36 {

37 while（ham.production＜ham.totalmaterial）

38 {

39 make（）；

40 try

41 {

42 sleep（3000）；

43 }

44 catch（Exception e）{}

45 }

46 }

47 }

（3）修改营业员类的设计，其代码如下所示。

48 ///这是一个营业员的线程类

49 ///sell1、sell2函数都是营业员卖汉堡包的方法

50 ///run方法就是线程需要运行的内容

51 ///使用条件判断语句来判断汉堡包盒子里面是否还有汉堡包

52 ///在run方法内使用循环语句来使得营业员在盒子里面有汉堡包的情况下不断地卖

53 class hassistant extends Thread

54 {

55 public void sell1（）

56 {

57 if（ham.production==（ham.sales1+ham.sales2））

58 {

59 System.out.println（"营业员"+getName（）+"：顾客朋友们，请稍微等一下，汉堡没了！"）；

60 61

ham.sales1=0；

62 ham.production=0；

63 try

64 {

65 ham.box.wait（）；

66 }

67 catch（Exception e）{}

68 }

69 （ham.sales1）++；

70 System.out.println（"营业员"+getName（）+"：顾客好，汉堡上来了，（总共卖了"

71 +ham.sales1+"个）"）；

72 }

73 public void sell2（）

74 {

75 if（ham.production==（ham.sales1+ham.sales2））

76 {

77 System.out.println（"营业员"+getName（）+"：顾客朋友们，请稍微等一下，汉堡没

78 了！"）；

79 ham.sales2=0；

80 ham.production=0；

81 try

82 {

83 ham.box.wait（）；

84 }

85 catch（Exception e）{}

86 }

87 （ham.sales2）++；

88 System.out.println（"营业员"+getName（）+"：顾客好，汉堡上来了，（总共卖了"

89 +ham.sales2+"个）"）；

90 }

91 public void run（）

92 {

93 while（（ham.sales1+ham.sales2）＜ham.production）

94 {

95 sell1（）；

96 try

97 {

98 sleep（1000）；

99 }

100 catch（Exception e）{}

101 }

102 while（（ham.sales1+ham.sales2）＜ham.production）

103 {

104 try

105 {

106 sleep（1000）；

107 }

108 catch（Exception e）{}

109 sell2（）；

110 }

111 }

112 }

（4）线程类在主运行程序中运行，详细代码如下所示。

113 ///这是主运行类

114 ///maker对象是指厨师对象

115 ///assistant1是指第一个营业员对象

116 ///assistant2是指第二个营业员对象

117 ///set方法是通过设置名字不同而区分不同的营业员

118 ///start方法是线程的开始

119 public class thread14

120 {

121 public static void main（String[]args）

122 {

123 hmaker maker=new hmaker（）；

124 hassistant assistant1=new hassistant（）；

125 hassistant assistant2=new hassistant（）；

126 assistant1.setName（"甲"）；

127 assistant2.setName（"乙"）；

128 maker.setName（"甲"）；

129 maker.start（）；

130 assistant1.start（）；

131 assistant2.start（）；

132 }

133 }

【代码说明】这个程序涉及两个营业员，其实多几个营业员倒是不难，只是要注意此时是两个人卖汉堡。那么厨师做出来的汉堡，就必须与两个营业员的销售总量进行比较，这一点也是整个程序比前一小节要多考虑的问题。

【运行效果】

厨师甲：汉堡来了（总共6个）

营业员甲：顾客好，汉堡上来了，（总共卖了1个）

营业员乙：顾客好，汉堡上来了，（总共卖了2个）

营业员甲：顾客好，汉堡上来了，（总共卖了3个）

营业员乙：顾客好，汉堡上来了，（总共卖了4个）

营业员甲：顾客好，汉堡上来了，（总共卖了5个）

营业员乙：顾客好，汉堡上来了，（总共卖了6个）

厨师甲：汉堡来了（总共1个）

营业员甲：顾客好，汉堡上来了，（总共卖了7个）

厨师甲：汉堡来了（总共1个）

厨师甲：汉堡来了（总共2个）

厨师甲：汉堡来了（总共3个）

13.4.3　多生产者的模拟程序设计及分析

【实例13.17】本节的实例将让条件变得更加复杂，设计了两个厨师和两个营业员。针对这种复杂的现象，先来看看程序的流程，如图13.4所示。

为了能够更加清晰地理解这个程序，下面将分步骤实现这个实例程序。

（1）箱子类的设计代码如下所示。

01 ///创建一个汉堡包盒子的类

02 ///totalmaterial1、totalmaterial2是指所有的能做汉堡包的材料

03 ///box1、box2是一个监视器对象

04 ///sales1、sales2是指销售了多少个汉堡包

05 ///production1、production2是指总共有多少个汉堡包

06 class ham

07 {

08 static Object box1=new Object（）；

09 static Object box2=new Object（）；

10 static int totalmaterial1=10；

11 static int totalmaterial2=10；

12 static int sales11=0；

13 static int sales12=0；

14 static int sales21=0；

15 static int sales22=0；

16 static int production1=5；

17 static int production2=5；

18 }

图　13.4　多生产者和多消费者线程模拟程序

（2）第1个厨师类的设计，代码如下：

19 ///这是一个厨师的线程类

20 ///make方法使用了一个同步块，在这个函数里会不断地生产汉堡包。这个厨师生产的是A类汉

21 ///堡包

22 ///run方法就是线程需要运行的内容

23 ///使用循环语句来保证在汉堡包材料用完之前，不断地生产汉堡包

24 ///使用判断语句判断只要有汉堡包，厨师就通知营业员可以卖了

25 class hmaker1 extends Thread

26 {

27 public void make（）

28 {

29 synchronized（ham.box1）

30 {

31 （ham.production1）++；

32 System.out.println（"厨师"+getName（）+"："+"汉堡来了（总共"

33 +（ham.production1-ham.sales11-ham.sales12）+"个A类汉堡）"）；

34 }

35 try

36 {

37 ham.box1.notify（）；

38 }

39 catch（Exception e）{}

40 }

41 public void run（）

42 {

43 while（ham.production1＜ham.totalmaterial1）

44 {

45 make（）；

46 try

47 {

48 sleep（3000）；

49 }

50 catch（Exception e）{}

51 }

52 if（ham.production1==ham.totalmaterial1）

53 {

54 System.out.println（"所有的材料用完了！"）；

55 }

56 }

57 }

（3）第2个厨师类的设计，其代码如下：

58 ///这是一个厨师的线程类

59 ///make方法使用了一个同步块，在这个函数里会不断地生产汉堡包。这个厨师生产的是B类汉

60 ///堡包

61 ///run方法就是线程需要运行的内容

62 ///使用循环语句来保证在汉堡包材料用完之前，不断地生产汉堡包

63 ///使用判断语句判断只要有汉堡包，厨师就通知营业员可以卖了

64 class hmaker2 extends Thread

65 {

66 public void make（）

67 {

68 synchronized（ham.box2）

69 {

70 （ham.production2）++；

71 System.out.println（"厨师"+getName（）+"："+"汉堡来了（总共

72 "+（ham.production2-ham.sales21-ham.sales22）+"个B类汉堡）"）；

73 }

74 try

75 {

76 ham.box2.notify（）；

77 }

catch（Exception e）{}

78 }

79 public void run（）

80 {

81 while（ham.production2＜ham.totalmaterial2）

82 {

83 make（）；

84 try

85 {

86 sleep（3000）；

87 }

88 catch（Exception e）{}

89 }

90 if（ham.production2==ham.totalmaterial2）

91 {

92 System.out.println（"所有的材料用完了！"）；

93 }

94 }

95 }

（4）营业员类的设计，其代码如下。

96 ///这是一个营业员的线程类

97 ///sell1、sell2函数都是营业员卖汉堡包的方法

98 ///run方法就是线程需要运行的内容

99 ///使用条件判断语句来判断汉堡包盒子里面是否还有汉堡包

100 ///在run方法内使用循环语句来使得营业员在盒子里有汉堡包的情况下不断地卖

101 class hassistant extends Thread

102 {

103 public void sell1（）

104 {

105 if（ham.production1==（ham.sales11+ham.sales12））

106 {

107 System.out.println（"营业员"+getName（）+"：顾客朋友们，请稍微等一下，A型汉堡

108没了！"）；

109 ham.sales11=0；

110 ham.sales12=0；

111 ham.production1=0；

112 try

113 {

114 ham.box1.wait（）；

115 }

116 catch（Exception e）{}

117 }

118 else

119 {

120 if（ham.production1＞（ham.sales11+ham.sales12））

121 {

ham.sales11++；

122 ham.sales21++；

123 System.out.println（"营业员"+getName（）+"：顾客好，汉堡上来了，（总共卖了

124 A类汉堡"+ham.sales11+"个），总共卖了B类汉堡"+ham.sales21+"个）"）；

125 }

126 }

127 }

128 129

public void sell2（）

130 {

131 if（ham.production2==（ham.sales21+ham.sales22））

132 {

133 System.out.println（"营业员"+getName（）+"：顾客朋友们，请稍微等一下，B型汉堡

134 没了！"）；

135 ham.sales21=0；

136 ham.sales22=0；

137 ham.production2=0；

138 try

139 {

140 ham.box2.wait（）；

141 }

142 catch（Exception e）{}

143 }

144 else

145 {

146 if（ham.production2＞（ham.sales21+ham.sales22））

147 {

148 ham.sales12++；

149 ham.sales22++；

150 System.out.println（"营业员"+getName（）+"：顾客好，汉堡上来了，（总共卖了

151 A类汉堡"+ham.sales12+"个，总共卖了B类汉堡"+ham.sales22+"个"）；

152 }

153 }

154 }

155 public void run（）

156 {

157 while（（ham.sales12+ham.sales11）＜ham.production1）

158 {

159 try

160 {

161 sleep（1000）；

162 }

163 catch（Exception e）{}

164 sell1（）；

165 }

166 while（（ham.sales12+ham.sales22）＜ham.production2）

167 {

168 try

169 {

170 sleep（2000）；

171 }

172 catch（Exception e）{}

173 sell2（）；

174 }

175 System.out.println（"还剩下A类汉堡：

176 "+（ham.production1-ham.sales11-ham.sales12）+"个"）；

177 System.out.println（"还剩下B类汉堡：

178 "+（ham.production2-ham.sales21-ham.sales22）+"个"）；

179 }

180 }

（5）线程类在主运行程序中的代码如下。

181 ///这是主运行类

182 ///maker1对象是指厨师对象

183 ///maker2对象是指另一个厨师对象

184 ///assistant1是指第一个营业员对象

185 ///assistant2是指第二个营业员对象

186 ///set方法是通过设置名字不同而区分不同的营业员

187 ///start方法是线程的开始

188 public class thread41

189 {

190 public static void main（String[]args）

191 {

192 hmaker1 maker1=new hmaker1（）；

193 hmaker2 maker2=new hmaker2（）；

194 hassistant assistant1=new hassistant（）；

195 hassistant assistant2=new hassistant（）；

196 maker1.setName（"甲"）；

197 maker2.setName（"乙"）；

198 assistant1.setName（"甲"）；

199 assistant2.setName（"乙"）；

200 maker1.start（）；

201 maker2.start（）；

202 assistant1.start（）；

203 assistant2.start（）；

204 }

205 }

【代码说明】从上面代码中可以看出，无论多少个厨师类，或者无论多少个营业员类，都不难，只需将每个类继承或实现成线程类，让它们在特殊的情况下交替执行。

【运行效果】

厨师甲：汉堡来了（总共6个A类汉堡）

厨师乙：汉堡来了（总共6个B类汉堡）

营业员甲：顾客好，汉堡上来了，（总共卖了A类汉堡1个，总共卖了B类汉堡1个）

营业员乙：顾客好，汉堡上来了，（总共卖了A类汉堡2个，总共卖了B类汉堡2个）

营业员甲：顾客好，汉堡上来了，（总共卖了A类汉堡3个，总共卖了B类汉堡3个）

营业员乙：顾客好，汉堡上来了，（总共卖了A类汉堡4个，总共卖了B类汉堡4个）

厨师甲：汉堡来了（总共3个A类汉堡）

厨师乙：汉堡来了（总共3个B类汉堡）

营业员甲：顾客好，汉堡上来了，（总共卖了A类汉堡5个，总共卖了B类汉堡5个）

营业员乙：顾客好，汉堡上来了，（总共卖了A类汉堡6个，总共卖了B类汉堡6个）

营业员甲：顾客好，汉堡上来了，（总共卖了A类汉堡7个，总共卖了B类汉堡7个）

营业员乙：顾客朋友们，请稍微等一下，A型汉堡没了！

厨师甲：汉堡来了（总共1个A类汉堡）

厨师乙：汉堡来了（总共1个B类汉堡）

营业员甲：顾客好，汉堡上来了，（总共卖了A类汉堡1个，总共卖了B类汉堡1个）

营业员乙：顾客朋友们，请稍微等一下，B型汉堡没了！

还剩下A类汉堡：0个

还剩下B类汉堡：0个

营业员甲：顾客朋友们，请稍微等一下，B型汉堡没了！

还剩下A类汉堡：0个

还剩下B类汉堡：0个

厨师甲：汉堡来了（总共1个A类汉堡）

厨师乙：汉堡来了（总共1个B类汉堡）

厨师乙：汉堡来了（总共2个B类汉堡）

厨师甲：汉堡来了（总共2个A类汉堡）

厨师乙：汉堡来了（总共3个B类汉堡）

厨师甲：汉堡来了（总共3个A类汉堡）

厨师甲：汉堡来了（总共4个A类汉堡）

厨师乙：汉堡来了（总共4个B类汉堡）

厨师甲：汉堡来了（总共5个A类汉堡）

厨师乙：汉堡来了（总共5个B类汉堡）

厨师甲：汉堡来了（总共6个A类汉堡）

厨师乙：汉堡来了（总共6个B类汉堡）

厨师甲：汉堡来了（总共7个A类汉堡）

厨师乙：汉堡来了（总共7个B类汉堡）

厨师乙：汉堡来了（总共8个B类汉堡）

厨师甲：汉堡来了（总共8个A类汉堡）

厨师甲：汉堡来了（总共9个A类汉堡）

厨师乙：汉堡来了（总共9个B类汉堡）

所有的材料用完了！

厨师乙：汉堡来了（总共10个B类汉堡）

在编写线程的程序时，有很重要的一点，就是弄清楚交替执行的每个线程的执行条件，就像上面程序中所说，如果汉堡包没了，那么营业员就会对顾客说稍等。

13.5　常见疑难解答

13.5.1　Java中线程与线程之间怎么通信

答：共享一个变量，并对此变量的访问进行同步，因为它们共享一个内存空间，所以相比之下，它比进程之间通信要简单容易的多。

13.5.2　什么是进程的死锁和饥饿

进程的死锁和饥饿在本章中没有讲述，主要是考虑到它涉及的线程知识会比较复杂。此处将简单地介绍一下它们的基本概念，有兴趣的读者可以自行查阅其他资料。

什么是饥饿？饥饿是由于别的并发进程的激活，而导致持久占有所需资源。饥饿是一个异步过程，预测的时间内不能被激活，最常遇到的线程的两个缺陷是死锁和饥饿。

当一个或多个进程，在一个给定的任务中，协同作用、互相干涉，而导致一个或者更多进程永远等待下去，死锁就发生了。

与此类似，当一个进程永久性地占有资源，使得其他进程得不到该资源时，就发生了饥饿。

首先看死锁问题。考虑一个简单的例子，假如到ATM机上取钱，但是却看到如下的信息“现在没有现金，请稍等再试”，若需要钱，就等了一会儿再试，但是又看到同样的信息。与此同时，一辆运款装甲车正等待着把钱放进ATM中，但是运款装甲车到不了ATM取款机，因为前面的汽车挡着道。客户只有取到钱，才会离开原地。这种情况下，就发生了死锁。在饥饿的情形下，系统不处于死锁状态中，因为有一个进程仍在处理之中，只是其他进程永远得不到执行的机会。

在什么样的环境下，会导致饥饿的发生，这没有预先设计好的规则，而一旦发生下面四种情况之一，就会导致死锁的发生。

❑相互排斥：一个线程或者进程永远占有共享资源，例如，独占该资源。

❑循环等待：进程A等待进程B，而后者又在等待进程C，而进程C又在等待进程A。

❑部分分配：资源被部分分配，例如，进程A和B都需要访问一个文件，并且都要用到打印机，进程A获得了文件资源，进程B获得了打印机资源，但是两个进程不能获得全部的资源。

❑缺少优先权：一个进程访问了某个资源，但是一直不释放该资源，即使该进程处于阻塞状态。

如果上面4种情形都不出现，系统就不会发生死锁。当其中一个进程判断出，它得不到所需要的第2个资源，就释放已经得到的第1个资源，那么第2个进程就可以获得两个资源，并能运行下去。

13.5.3　什么时候会涉及线程程序

答：在说明这个问题之前，先举一个例子，一个厨师既要炒菜，又要切菜。两个动作是间断性地进行，即一会儿要炒菜，一会儿要切菜，此时要编写程序，就要使用线程。

总结一下：当遇到一个对象要做出多个动作，并且多个动作又是穿插在一起时，就要使用线程的概念来编写程序。前面提到的厨师和营业员的实例，针对汉堡包这个对象，一会儿要制作汉堡包，一会要卖汉堡包，当汉堡包做好了，厨师就通知营业员卖，当卖完了营业员又要通知厨师做，这样制作和卖就穿插在一起。像上面提到的这种情况，就可以使用线程来帮助程序完成设计目的。

在网络编程中，网络上不同的用户操作一个对象时，也可以借助线程来完成程序。所以说，线程在网络编程方面有着不可小看的作用。

13.6　小结

线程是学习Java很关键的一步，我们在操作电脑的时候，可以同时打开一个Word、一个音乐播放器、一个网站等，同时进行的这些步骤就可以称为多线程。本章通过Java关于线程的设计，详细介绍了线程的休眠与唤醒、线程的同步等。希望通过这些技术，读者能了解什么时候使用线程。

13.7　习题

1.可通过继承________来创建线程类。

2.可实现________接口创建线程类。

3.线程的执行是有序的吗？

4.________关键字可以约束线程同步？

5.怎么做可以尽最大可能防止死锁？

6.线程有哪些状态？

第14章　封装

本章介绍Java程序设计中的另一个特点：封装。封装有什么作用？封装有什么特点？如何将其应用到实际编程中去？针对以上的种种问题，本章将从封装的基本概念讲起，让读者学习封装的实际作用。其实封装就是将一些内容包装起来，用来保证一些类或方法的安全。

本章重点：

❑认识封装。

❑了解使用封装的步骤。

❑如何设计封装。

14.1　封装简介

封装，确切地说是一种编程思路，掌握这种编程思路对于一个程序设计来说，是至关重要的。封装也是程序设计中最能够体现出程序安全性的概念。所谓封装，就是将某些东西封装在一个容器内，不让外界接触它。在程序设计中，封装就是将某些方法和属性封装到某个类中，使得外界的类无法访问它们。

其实在前面的实例中，已经看到过有关封装的例子，不过，当时没有介绍封装的概念。

【实例14.1】下面将举一个有关封装的例子，看看究竟什么是封装。

01 ///这是一个主运行类

02 ///构造了多个对象

03 ///输出多个对象的字符串形式

04 public class thread42

05 {

06 public static void main（String[]args）

07 {

08 flower f1=new flower（）；

09 flower f2=new flower（）；

10 flower f3=new flower（）；

11 f1.setname（"牡丹"）；

12 f1.setcolor（"红色"）；

13 f1.setlocation（"云南"）；

14 f2.setname（"玫瑰"）；

15 f2.setcolor（"黄色"）；

16 f2.setlocation（"北京"）；

17 f3.setname（"月季"）；

18 f3.setcolor（"蓝色"）；

19 f3.setlocation（"上海"）；

20 System.out.println（f1.tostring（））；

21 System.out.println（f2.tostring（））；

22 System.out.println（f3.tostring（））；

23 }

24 }

25 ///这是一个花类

26 ///通过设置多个有关花类的私有变量

27 ///通过设置器来设置花对象的属性

28 ///通过访问器来获得花对象的属性

29 ///通过tostring方法让对象以字符串形式输出

30 class flower

31 {

32 private String name；

33 private String color1；

34 private String location；

35 public void setname（String name）

36 {

37 this.name=name；

38 }

39 public void setcolor（String color1）

40 {

41 this.color1=color1；

42 }

43 public void setlocation（String location）

44 {

45 this.location=location；

46 }

47 public String getname（）

48 {

49 return name；

50 }

51 public String getcolor（）

52 {

53 return color1；

54 }

55 public String getlocation（）

56 {

57 return location；

58 }

59 public String tostring（）

60 {

61 String information="花的名称："+name+"；"+""+"花的颜色："+color1+"；"+""+"花的出产地：

62"+location+""；

63 return information；

64 }

65 }

【代码说明】第30～65行定义了一个类flower，第8～10行定义了3个对象。第11～19行设置对象的各个属性，第20～22行输出结果。

【运行效果】

花的名称：牡丹；花的颜色：红色；花的出产地：云南

花的名称：玫瑰；花的颜色：黄色；花的出产地：北京

花的名称：月季；花的颜色：蓝色；花的出产地：上海

以上的程序段中，flower类中所有的方法都是public，而所有的变量都是private。可以通过对象句柄来访问“flower”类中的所有方法。那么是否可以访问其中的变量？

【实例14.2】下面看看这个程序修改后的程序段。

说明　这里主程序变化了，类没有变化。以下代码仅给出主程序，类参考上一个实例的代码。

01 public class thread44

02 {

03 public static void main（String[]args）

04 {

05 flower f1=new flower（）；

06 flower f2=new flower（）；

07 flower f3=new flower（）；

08 f1.name="牡丹"；

09 f2.name="玫瑰"；

10 f3.name="月季"；

11 f1.color1="红色"；

12 f2.color1="黄色"；

13 f3.color1="蓝色"；

14 f1.location="云南"；

15 f2.location="北京"；

16 f3.location="上海"；

17 System.out.println（f1.tostring（））；

18 System.out.println（f2.tostring（））；

19 System.out.println（f3.tostring（））；

20 }

21 }

【代码说明】在这个程序段中，出现了编译错误。是因为flower类中，所有的变量都是私有变量，也就是被类给封装了。只有类中的方法才可以访问它们，外部不可能看到，更不可能访问它们，这样类的内部变量就会显得很安全。

【运行效果】这里显示编译时提示的错误信息。

thread41.java：8：name可以在flower中访问private

f1.name="牡丹"；

^

thread41.java：9：name可以在flower中访问private

f2.name="玫瑰"；

^

thread41.java：10：name可以在flower中访问private

f3.name="月季"；

^

thread41.java：11：color1可以在flower中访问private

f1.color1="红色"；

^

thread41.java：12：color1可以在flower中访问private

f2.color1="黄色"；

^

thread41.java：13：color1可以在flower中访问private

f3.color1="蓝色"；

^

thread41.java：14：location可以在flower中访问private

f1.location="云南"；

^

thread41.java：15：location可以在flower中访问private

f2.location="北京"；

^

thread41.java：16：location可以在flower中访问private

f3.location="上海"；

^

9错误

14.2　在程序设计中为什么要使用封装

类包括有属性变量和方法函数，有些函数不能让软件使用者知道其是如何实现的。有的属性变量也不需要软件使用者去修改它。这个时候，就需要使用封装，将某些方法函数和属性变量进行包装。而软件开发人员只能使用，不能修改它们。

【实例14.3】下面将举一个有关封装的实例。

01 ///这是一个主运行类

02 ///在主运行方法中，通过设置器设置参数值

03 ///将三个花对象以字符串形式输出

04 public class thread44

05 {

06 public static void main（String[]args）

07 {

08 flower f1=new flower（）；

09 flower f2=new flower（）；

10 flower f3=new flower（）；

11 f1.setname（"牡丹"）；

12 f1.setcolor（"红色"）；

13 f1.setlocation（"云南"）；

14 f2.setname（"玫瑰"）；

15 f2.setcolor（"黄色"）；

16 f2.setlocation（"北京"）；

17 f3.setname（"月季"）；

18 f3.setcolor（"蓝色"）；

19 f3.setlocation（"上海"）；

20 System.out.println（f1.tostring（））；

21 System.out.println（f2.tostring（））；

22 System.out.println（f3.tostring（））；

23 }

24 }

25 ///这是一个花类

26 ///通过private关键字设置多个有关花类的私有变量

27 ///通过设置器来设置花对象的属性

28 ///通过访问器来获得花对象的属性

29 ///通过tostring方法让对象以字符串形式输出

30 class flower

31 {

32 private String name；

33 private String color1；

34 private String location；

35 public void setname（String name）

36 {

37 this.name=name；

38 }

39 public void setcolor（String color1）

40 {

41 this.color1=color1；

42 }

43 public void setlocation（String location）

44 {

45 this.location=location；

46 }

47 public String getname（）

48 {

49 return name；

50 }

51 public String getcolor（）

52 {

53 return color1；

54 }

55 public String getlocation（）

56 {

57 return location；

58 }

59 private void print（）

60 {

61 System.out.println（"这个就是"+name）；

62 }

63 public String tostring（）

64 {

65 String information="花的名称："+name+"；"+""+"花的颜色："+color1+"；"+""+"花的出产地：

66"+location+""；

67 print（）；

68 return information；

69 }

70 }

【代码说明】在这个程序段的flower类中，第59行有一个私有方法“print（）”，这个方法函数不想让使用者知道其是如何操作数据的，只有“flower”类中的方法能访问它们，外部类访问它们，则会被拒绝。如果使用对象句柄去访问“print（）”方法，则会在编译时出错。代码在第67行中使用了“print（）”方法。

【运行效果】

这个就是牡丹

花的名称：牡丹；花的颜色：红色；花的出产地：云南

这个就是玫瑰

花的名称：玫瑰；花的颜色：黄色；花的出产地：北京

这个就是月季

花的名称：月季；花的颜色：蓝色；花的出产地：上海

下一小节学习如何封装稍微复杂的综合实例。

14.3　结合实例讲述如何设计封装

【实例14.4】本节通过这个综合的实例，让读者更加熟悉这种封装的程序设计思路。这个程序段是老师提出问题，让一个学生回答。先来了解这个程序的流程，如图14.1所示。

图　14.1　封装综合实例的流程图

首先建立学生类，代码如下所示。

01 ///创建一个学生回答问题类，并且让它

02 ///成为一个线程类

03 ///name指学生　姓名

04 ///age指学生的　年龄

05 ///school指学生所在学校

06 ///grade指学生　所在年级

07 ///year、month、days指年份、月份、日子

08 ///set是设置器

09 ///get是访问器

10 ///通过一个开关语句，来把几次不同的回答分成不同的情况

11 ///run方法是线程的运行方法

12 class studentanswer extends Thread

13 {

14 private String name；

15 private String age；

16 private String school；

17 private String grade；

18 private String year；

19 private String month；

20 private String days；

21 private int x；

22 public void set（String name, String age, String school, String grade, String year, String

23 month, String days）

24 {

25 this.name=name；

26 this.age=age；

27 this.school=school；

28 this.grade=grade；

29 this.year=year；

30 this.month=month；

31 this.days=days；

32 }

33 public void setint（int x）

34 {

35 this.x=x；

36 }

37 public String getname（）

38 {

39 return name；

40 }

41 public String getage（）

42 {

43 return age；

44 }

45 public String getschool（）

46 {

47 return school；

48 }

49 public String getgrade（）

50 {

51 return grade；

52 }

53 public String getyear（）

54 {

55 return year；

56 }

57 public String getmonth（）

58 {

59 return month；

60 }

61 public String getdays（）

62 {

63 return days；

64 }

65 public int getint（）

66 {

67 return x；

68 }

69 ///这是一个有关学生回答内容的方法

70 ///通过一个分支语句来控制回答的步骤

71 private void said（）

72 {

73 switch（x）

74 {

75 case 0：

76 System.out.println（name+"说：我名字叫"+name+"。"）；

77 break；

78 case 1：

79 System.out.println（name+"说：我在"+school+"读书。"）；

80 break；

81 case 2：

82 System.out.println（name+"说：我现在在读"+grade+"。"）；

83 break；

84 case 3：

85 System.out.println（name+"说：我今年"+age+"岁。"）；

86 break；

87 case 4：

88 System.out.println（name+"说：我学习计算机软件开发"+year+"年。"）；

89 break；

90 case 5：

91 System.out.println（name+"说：今年"+month+"月放假。"）；

92 break；

93 case 6：

94 System.out.println（name+"说：一般放假的天数是"+days+"天。"）；

95 break；

96 case 7：

97 System.out.println（name+"说：不客气。"）；

98 break；

99 }

100 }

101 public void run（）

102 {

103 said（）；

104 try

105 {

106 sleep（2000）；}

107 catch（Exception e）{}

108 }

109 }

再设计教师类，代码如下。

110 ///创建一个教师提问的线程类

111 ///name是指老师的姓名

112 ///set是设置器

113 ///get是访问器

114 ///通过一个开关语句，来把几次不同的回答分成不同的情况

115 ///run方法是线程的运行方法

116 class teacherask extends Thread

117 {

118 private String name；

119 private int x；

120 public void set（String name）

121 {

122 this.name=name；

123 }

124 public String getname（）

125 {

126 return name；

127 }

128 public void setint（int x）

129 {

130 this.x=x；

131 }

132 public int getint（）

133 {

134 return x；

135 }

136 ///这是一个老师提问的方法

137 ///通过分支语句来提供提问的步骤

138 private void said（）

139 {

140 switch（x）

141 {

142 case 0：

143 System.out.println（name+"说：你叫什么名字？"）；

144 break；

145 case 1：

146 System.out.println（name+"说：你在哪所学校读书？"）；

147 break；

148 case 2：

149 System.out.println（name+"说：你读什么系哪一个年级？"）；

150 break；

151 case 3：

152 System.out.println（name+"说：你今年多大了？"）；

153 break；

154 case 4：

155 System.out.println（name+"说：你学习计算机软件开发几年了？"）；

156 break；

157 case 5：

158 System.out.println（name+"说：你几月放假？"）；

159 break；

160 case 6：

161 System.out.println（name+"说：放假大概有多少天？"）；

162 break；

163 case 7：

164 System.out.println（name+"说：谢谢你回答我的问题。"）；

165 break；

166 }

167 }

168 public void run（） //让线程能够交叉运行

169 {

170 said（）；

171 try

172 {

173 sleep（2000）；

174 }

175 catch（Exception e）{}

176 }

177 }

最后通过主运行类，执行其对话的功能。

178 ///主运行类

179 ///sa1至sa8是学生回答类的八个对象

180 ///ta1至ta8是教师提问类的八个对象

181 ///stt[]是学生回答类对象集合

182 ///stt1是教师提问类对象集合

183 ///通过一个循环语句将对象集合中元素输出。并且是按线程运行方式交叉运行

184 public class thread45

185 {

186 public static void main（String[]args）throws Exception

187 {

188 studentanswer sa1=new studentanswer（）；//创建几个学生回答结果的对象

189 studentanswer sa2=new studentanswer（）；

190 studentanswer sa3=new studentanswer（）；

191 studentanswer sa4=new studentanswer（）；

192 studentanswer sa5=new studentanswer（）；

193 studentanswer sa6=new studentanswer（）；

194 studentanswer sa7=new studentanswer（）；

195 studentanswer sa8=new studentanswer（）；

196 teacherask ta1=new teacherask（）；//创建几个老师提问内容的对象

197 teacherask ta2=new teacherask（）；

198 teacherask ta3=new teacherask（）；

199 teacherask ta4=new teacherask（）；

200 teacherask ta5=new teacherask（）；

201 teacherask ta6=new teacherask（）；

202 teacherask ta7=new teacherask（）；

203 teacherask ta8=new teacherask（）；

204 studentanswer[]stt=new studentanswer[]{sa1，sa2，sa3，sa4，sa5，sa6，sa7，sa8}；

205 teacherask[]stt1=new teacherask[]{ta1，ta2，ta3，ta4，ta5，ta6，ta7，ta8}；

206 try

207 {

208 for（int x=0；x＜8；x++）

209 {

210 stt[x].set（"TOM"，"22"，"重庆大学"，"自动化系二年级"，"3"，"7"，"54"）；

211 stt1[x].set（"John老师"）；

212 stt1[x].setint（x）；

213 stt[x].setint（x）；

214 stt1[x].start（）；

215 stt[x].start（）；

216 }

217 }

218 catch（Exception e）{}

219 }

220 }

【代码说明】以上的程序段，使用了前面学过的知识：线程、分支语句、数组等。通过程序可以看到，封装后的所有方法和变量只能在程序的类的内部被使用，一旦走出了这个类，就是无效的。这也是前面章节所说的变量和方法的作用域。

【运行效果】

John老师说：你叫什么名字？

TOM说：我名字叫TOM。

John老师说：你在哪所学校读书？

TOM说：我在重庆大学读书。

John老师说：你读什么系哪一个年级？

TOM说：我现在在读自动化系二年级。

John老师说：你今年多大了？

TOM说：我今年22岁。

John老师说：你学习计算机软件开发几年了？

TOM说：我学习计算机软件开发3年。

John老师说：你几月放假？

TOM说：今年7月放假。

John老师说：放假大概有多少天？

TOM说：一般放假的天数是54天。

John老师说：谢谢你回答我的问题。

TOM说：不客气。

14.4　在程序设计中设计封装的注意点

在实际程序开发过程中，如何设计封装？什么时候将变量和方法设计成封装形式？这个对程序员来说至关重要。

在实际的开发工作中，一个应用程序基本上是由很多个不同的程序员设计出来的，彼此之间通过接口程序，将它们衔接起来成为一个完整的应用程序。

这种情况下，程序员本身不希望其他的程序员知道自己是如何实现一个方法函数或一个属性变量的。通常程序员会将方法设置为一个类中的私有方法，如上一节中的“said（）”方法函数。其他的程序员就不知道此方法是如何实现的，即无法知道方法中的代码段是什么内容。这样体现了程序的安全性。

14.5　常见疑难解答

14.5.1　封装在现实开发中给程序员带来什么启发

答：一般来说，程序员都力求软件工程系统的高集成性。一个具有高集成性的软件系统包含着各种执行独立任务的成分，而每一个独立任务都是整个系统的重要组成部分。相反，如果一个软件系统的集成性差，那么系统所包含的各种成分，由于没有很好地被定义，而往往容易发生冲突。

在凝聚封装过程中，重载非常重要。本节将讨论一些启发式知识。这些启发式知识会提供，生成具有高凝聚性、可重载的、基于java.io的封装。

❑封装启发式知识：生成高集成性的封装。

重载是类的重要特性，在实际开发过程中很少单独对一个类进行重载，一般重载都是对多个类进行。java.io封装就是一个高集成性的成功范例，封装中很少有类单独地使用，通常都是同时使用多个类来达到预期的目的。

对于高集成性的封装，软件维护和升级都很容易，因为它们提供了各种集成模块，这些模块中的类能提供各种功能齐全的函数。集成性差的封装包含的类中的函数都是自成一体，相互独立，这样使用起来就会非常困难，因为必须从其他类中提供接口。当然软件的维护也会变得很困难，因为在使用这样的封装时，往往不得不更改其中的类，这样的封装结果给软件的部署带来很大的麻烦。

❑封装启发式知识：注意封装内容对重载的影响。

当设计封装时，通常要考虑封装中类的使用情况，除此之处，也应该考虑到那些虽不是封装的内容，但经常与封装有联系的类。当完成一个高集成性的封装后，就可以把这一封装看成是完整的、可重载的各种成分的集合。

❑封装启发式知识。

封装过程中强调重载功能，封装是各个类的集合，其中的每一个类在程序中都执行一定的功能单元。这样的封装剔除了很多特定的细节，使各种功能单元变得更加容易使用。但是，为了更有效地使用这些封装，开发人员必须深刻地理解封装的函数功能，并懂得如何与封装进行通信。

❑封装启发式知识：以简化方式设计封装。

封装中的函数比一个独立类中的函数更简化有效，提供这样的服务也会产生软件维护的挑战，因为这需要用户对封装的内部函数功能，必须有着清楚的了解。

❑封装启发式知识：提供完整的封装接口。

一个封装的接口包括很多公用类及公用函数，工程中任一其他类都可以调用类中的公用函数。这样一旦这一公用函数改变，调用这一函数的类也要做出相应的更改。如果程序比较简单，可以很容易地找到在哪里调用了这一函数，然而，如果在不同程序之间使用封装，那么问题就会变得很复杂，所以封装的接口需要很好的设计。

14.5.2　封装在实际开发中的应用有哪些

答：封装在实际开发工作中有极其多的应用，如数据库系统开发，就会利用到封装的概念。还有就是很多程序员不愿意让其他程序员知道的代码，都可以进行封装。封装其实就是不让用户或非本程序编写者看到代码的内幕。

14.6　小结

本章讲解了封装的概念，读者明白概念后，首先要清楚程序设计中为什么需要封装，然后知道如何设计一个简单的封装。在设计封装的过程中，要知道需要注意哪些事项。通过本章的学习，读者不仅仅能开发自己的程序，还能读懂其他人的一些大型项目。

14.7　习题

1.封装有什么作用？

2.封装有什么特点？

3.分别设计一个教师和学生的封装类。

第三篇　Java程序设计数据处理篇

第15章　Java输入与输出（I/O）

本章将介绍输入和输出，输入和输出是一个包括了很多类的统称。其中有字符输入和输出类、字节的输入和输出类、文件的输入和输出类、随机访问类、对象序列化输入和输出类等。

输出和输入在整个应用程序中尤为重要，如果一个应用程序没有输入和输出，那么就不能给使用者提供所需要的信息。输入和输出也是整个应用程序为用户提供的接口，让用户能够清楚此应用程序的目的是什么、要做到什么等。

本章重点：

❑文件和目录的处理方法。

❑流的处理。

❑多字节数据的读取。

❑对象序列化的输入和输出。

15.1　输入与输出的重要性

输入就是平时看到的“input”，输出就是平时看到的“output”，输入和输出就是某个方向流动的数据流。有关输入和输出，在Java类库中有一个与之相对应的类库java.io包。在java.io包中，提供了众多的有关输入和输出的类。

那么应用程序为什么需要输入和输出呢？读者一定看过Office软件，在Word软件中，需要输入一些文本，用户需要打开文件，读取这些文本，这都需要利用输入和输出的功能。在现实生活中，输入和输出的例子比比皆是。

在前面的章节中，曾使用过“System.out.println（）”方法函数，其实它是一个输出函数。当然，后面会讲述更多的方法函数，并通过大量的实例来展示它们的用法。

15.2　Java有哪些重要的输入/输出类

Java程序类库包含大量的输入/输出类，提供不同情况下的不同功能。本章将详细讲述这些输入/输出类。

注意　在编写程序过程中，如果要使用输入/输出类的方法和属性值，就需要引入java.io类。

下面是一些经常使用的输入/输出类。

File类

InputStream类

OutputStream类

FilterStream类

Writer类

Reader类

这些类都拥有大量的方法，读者可通过这些方法，来操作各种不同情况下的输入流和输出流。

后面的章节会将每一个输入和输出类，通过实例进行详细的讲解。最重要的是，要将本章的内容和前面所学的内容紧紧地结合在一起，为以后的综合实例打好坚实的基础。

15.3　文件或目录信息的处理—File类

File类提供了与文件或目录相关的信息。下面是这个类的构造器。

public File（String pathname）：它的用处是使用指定的路径，创建一个File对象。

public File（String parent, String child）：使用指定的路径和字符串创建一个File类。

public File（File parent, String child）：使用一个File类的对象parent与字符串创建一个新的File对象。

通过以上的构造器来构造File对象。然后，使用这个类提供的方法函数和属性值，来进行文件和目录的操作。

15.3.1　File常用的操作方法

File类常用的方法函数很多，并提供不同的文件操作功能，这些方法如表15.1所示。

15.3.2　文件处理方法的应用

【实例15.1】下面将针对这些方法，举出有关File类的实例，让读者能够熟悉它们的用法。

01 import java.io.*；

02 ///创建一个文件类

03 ///通过getName、getParent、getPath来获取文件路径和文件名等信息

04 public class file

05 {

06 public static void main（String[]args）

07 {

08 File f=new File（"d：\\"，"file.txt"）；

09 System.out.println（f.getName（））；

10 System.out.println（f.getParent（））；

11 System.out.println（f.getPath（））；

12 }

13 }

【代码说明】在这个程序段中，开头第1行出现了一个“import java.io.*”，这就是要引入的输入输出包，因为程序员用到的类的方法被包含在这个包中。

【运行效果】

file.txt

d：\

d：\file.txt

15.3.3　文件和目录的完全处理

【实例15.2】以上的实例很简单，主要学习函数的用法。下面再看一个稍微复杂的程序。

01 import java.io.*；

02 ///通过print方法来判断这个文件类对象的性质

03 ///通过print1方法来获取文件对象的信息

04 ///通过print2方法来获取文件对象的信息

05 public class file1

06 {

07 public void print（File f）

08 {

09 if（f.isDirectory（））

10 {

11 System.out.println（"这是一个目录！"）；

12 }

13 else

14 {

15 System.out.println（"这不是一个目录！"）；

16 }

17 if（f.exists（））

18 {

19 System.out.println（"这个文件是存在的！"）；

20 }

21 else

22 {

23 System.out.println（"抱歉，这个文件是不存在的！"）；

24 try

25 {

26 f.createNewFile（）；

27 }

28 catch（Exception e）{}

29 }

30 }

31 public void print1（File f）

32 {

33 System.out.println（f.getName（））；

34 System.out.println（f.getParent（））；

35 System.out.println（f.getPath（））；

36 }

37 public void print2（File f）

38 {

39 if（f.isFile（））

40 {

41 System.out.println（f.lastModified（））；

42 System.out.println（f.length（））；

43 }

44 }

45 public static void main（String[]args）

46 {

47 file1 f1=new file1（）；

48 File f=new File（"d：\\filetest"，"file.txt"）；

49 f1.print（f）；

50 f1.print1（f）；

51 f1.print2（f）；

52 }

53 }

【代码说明】上面的程序段是一个比较综合的实例，它使用了File类的很多方法。在程序中，有一点读者可能不是很明白，当第41行要输出最后修改日期时，为什么会输出“1251538022515”呢？这是因为日期是以long类型数据形式输出的。

说明

先在d盘下创建一个目录filetest，然后在其中创建一个file.txt，在这个文本文件中随意添加一些内容。

【运行效果】

这不是一个目录！

这个文件是存在的！

file.txt

d：\filetest

d：\filetest\file.txt

1251538022515 211

【实例15.3】下面再看一个更加复杂的实例。

01 import java.io.*；

02 ///创建8个文件类对象，并且每一个对象产生一个新文本文件

03 ///通过循环语句将这8个文本文件名称和路径输出

04 public class file2

05 {

06 public static void main（String[]args）

07 {

08 //file2 f=new file2（）；

09 File f1=new File（"d：\\filetest"，"1.txt"）；

10 File f2=new File（"d：\\filetest"，"2.txt"）；

11 File f3=new File（"d：\\filetest"，"3.txt"）；

12 File f4=new File（"d：\\filetest"，"4.txt"）；

13 File f5=new File（"d：\\filetest"，"5.txt"）；

14 File f6=new File（"d：\\filetest"，"6.txt"）；

15 File f7=new File（"d：\\filetest"，"7.txt"）；

16 File f8=new File（"d：\\filetest"）；

17 try

18 {

19 f1.createNewFile（）；

20 f2.createNewFile（）；

21 f3.createNewFile（）；

22 f4.createNewFile（）；

23 f5.createNewFile（）；

24 f6.createNewFile（）；

25 f7.createNewFile（）；

26 }

27 catch（Exception e）{}

28 File[]ff=f8.listFiles（）；

29 for（int i=0；i＜ff.length；i++）

30 {

31 System.out.println（"文件名称为："+ff[i]）；

32 }

33 }

34 }

【代码说明】以上的程序段，是先创建一个目录“filetest”。然后在目录中，通过使用方法“createNewFile（）”创建7个文件。另外，通过使用方法“listFiles（）”，将7个文件的文件名存储到“file[]”数组中。然后再读取数组中的每一个元素，即文件名。最后将这7个文件的文件名称分别输出来。

【运行效果】

文件名称为：d：\filetest\1.txt

文件名称为：d：\filetest\2.txt

文件名称为：d：\filetest\3.txt

文件名称为：d：\filetest\4.txt

文件名称为：d：\filetest\5.txt

文件名称为：d：\filetest\6.txt

文件名称为：d：\filetest\7.txt

文件名称为：d：\filetest\file.txt

注意　filetest目录下如果还有其他内容，输出结果可能不同于上述结果。

15.4　读取数据的媒介之一—流（stream）

流是什么？读取数据与流有什么关系？本节将通过理论和大量的实例，展示数据的读取与流之间不可分隔的关系。

15.4.1　什么是流

流就是数据流向某个对象，并且到达这个对象的过程。要真正理解流的概念，并不是很容易，为了能让读者熟练地应用流来编写程序，先这样理解：数据流先流向对象，然后从对象中将这个流读出来。

15.4.2　什么是输入流和输出流

输入流就是从目标程序中，将数据以流的形式复制到前面说的流对象中，然后，再从流对象中将数据读取出来。

输出流就是将数据以流的形式复制到流对象中去，再从这些流对象中取出流，写入到目标中。

通过上面两个概念的解释，相信读者已经对流有了一定的了解。所谓的流，不过是一些数据，而数据流对象就是操作数据（读数据或者写数据）时，使用的一个媒介物而已。

下面针对不同的数据流对象，通过大量的实例来学习如何输入和输出数据流。

15.4.3　字节输入流及输出流

数据流对象分为两大类：一大类是负责输入的输入流对象，另一类是负责输出的输出流对象。这两大类的父类是“InputStream”类和“OutputStream”类。其实这两个类都是抽象类，通过前面对抽象和多态的学习，应该知道抽象类不能创建对象，但是可以通过其子类来创建对象，这一点在多态中已经详细讲述过。

这两个父类包含了丰富的方法，下面对这些方法进行详细的说明。

InputStream类的主要方法如表15.2所示。

OutputStream类的主要方法如表15.3所示。

上面的方法函数，可通过两个类的子类对象进行引用。有关这些子类，后面的章节将会一一介绍。

15.4.4　使用文件字节输入流读取文件

“FileInputStream”类与前面说过的“InputStream”类有点相似，其实“FileInputStream”类就是“InputStream”的子类，并且其不是一个抽象类。这样，程序员就可以利用“FileInputStream”类的对象来使用前面的方法。

下面先看如何构造一个“FileInputStream”类的对象，这需要用到类的构造器。下面是“FileInputStream”类的构造器种类。

public FileInputStream（String name）：创建一个FileInputStream对象，从以name为名称的文件名称中读取数据。

public FileInputStream（File file）：创建一个FileInputStream对象。从指定的对象file中读取数据。

【实例15.4】先看一个有关“FileInputStream”类的实例。

01 import java.io.*；

02 ///创建一个文件类f

03 ///创建一个输入流对象fis，并且以f作为参数

04 ///使用循环语句将文本文件中的字符读出

05 public class file3

06 {

07 public static void main（String[]args）throws Exception

08 {

09 File f=new File（"d：\\filetest\\file.txt"）；

10 FileInputStream fis=new FileInputStream（f）；

11 char ch；

12 for（int i=0；i＜f.length（）；i++）

13 {

14 ch=（char）fis.read（）；

15 System.out.print（ch）；

16 }

17 fis.close（）；

18 }

19 }

【代码说明】仔细分析上面的程序段，第9行建立一个目标对象，这里的目标对象是一个记事本文件。接着第10行建立一个输入流对象，利用这个输入流对象的“read（）”方法，读取目标对象中的数据，最后第17行将这个流关闭。由于是读数据，所以这个目标对象必须已经存在。这个过程可以使用一个图来表示，如图15.1所示。

图　15.1　数据输入流模拟图

【运行效果】

addbcnhhvbvbbvnbvncxzxvdfhhtrrgregfdsgdsfdhjxccbnh

说明　读者机器上如果没有file.txt文件，则会报错。先手动创建此文件，文件内容与上述结果相同。

【实例15.5】下面再看一个有关输入流对象的实例。

01 import java.io.*；

02 ///创建一个文件类f

03 ///创建一个输入流对象fis，并且以f作为参数

04 ///将所有的字节都保存到一个字节数组b中

05 ///使用循环语句将b中的字符读出

06 public class file4

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\filetest\\file.txt"）；

11 FileInputStream fis=new FileInputStream（f）；

12 byte[]b=new byte[（int）f.length（）]；

13 fis.read（b）；

14 for（int i=0；i＜f.length（）；i++）

15 {

16 System.out.print（（char）b[i]）；

17 }

18 fis.close（）；

19 }

20 }

【代码说明】第10行创建一个目标对象，其是file.txt文件，第11行创建一个读取数据流对象fis，第13行开始读取目标对象的数据。不要忘记第18行关闭读取流。

【运行效果】

addbcnhhvbvbbvnbvncxzxvdfhhtrrgregfdsgdsfdhjxccbnh

15.4.5　使用文件字节输出流输出文件

“FileOutputStream”类与超类“OutputStream”类有点相似，其实“FileOutputStream”类就是“OutputStream”的子类，并且不是一个抽象类，这样就可以利用“FileOutputStream”类的对象来使用前面的方法。

下面看看如何构造一个“FileOutputStream”类的对象，必须要用到类的构造器。表15.4是“FileOutputStream”类的构造器种类。

【实例15.6】下面将通过一个有关输出流对象的实例，熟悉它的用法。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个文件输出流对象fos

04 ///使用循环语句往f中写入数据

05 public class file5

06 {

07 public static void main（String[]args）throws Exception

08 {

09 File f=new File（"d：\\filetest\\file.txt"）；

10 FileOutputStream fos=new FileOutputStream（f）；

11 for（int i=’a’；i＜=’z’；i++）

12 {

13 fos.write（i）；

14 }

15 fos.close（）；

16 }

17 }

【代码说明】第9行创建目标对象，不管file.txt中有没有内容。第10行创建输出流对象fos，第11～14行输出26个小写的英文字母。

【运行效果】没有运行结果，但在file.txt中发现了刚才程序自动输入的数据，是26个小写的英文字母。

图　15.2　数据输出流模拟图

图15.2演示了输出流操作数据的过程。

【实例15.7】下面演示如何将输入流和输出流结合在一起使用。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个文件类对象f1

04 ///创建一个文件输入流对象fis

05 ///创建一个文件输出流对象fos

06 ///使用字节数组将f中的数据读出

07 ///使用循环语句将字节数组中的数据往f中写

08 public class file6

09 {

10 public static void main（String[]args）throws Exception

11 {File f=new File（"d：\\filetest\\file.txt"）；

12 File f1=new File（"d：\\filetest\\file2.txt"）；

13 FileInputStream fis=new FileInputStream（f）；

14 FileOutputStream fos=new FileOutputStream（f1）；

15 byte[]b=new byte[（int）f.length（）]；

16 fis.read（b）；

17 for（int i=0；i＜f.length（）；i++）

18 {

19 fos.write（b[i]）；

20 }

21 fis.close（）；

22 fos.close（）；

23 }

24 }

【代码说明】第13行创建的是输入流对象，从file.txt中读取数据。第14行创建的是输出流对象，往file2.txt中输出对象。第21～22行关闭输入流和输出流。

【运行效果】上面程序最后的结果就是，在新建的文本文件“file2.txt”中，输入了从文本文件“file.txt”中读取的数据。运行步骤是，先利用输入流从目标对象中读取数据，再利用输出流对象将这些数据写到新的目标对象中。下面通过模拟图15.3，让读者对上一个程序的运行过程看得更清晰。

图　15.3　输入流和输出流模拟图

15.5　多字节数据读取类—Filter类

Filter类是一个非常有用的类，它能够进行多字节数据的读取。本节将会详细讲述这个类的应用。

15.5.1　Filter流的概念

前面讲述的那些类，都是处理以字节为单位的数据，如果读者不是很清楚，可以查看前面“InputStream”和“OutputStream”两个类的方法函数中的参数。前面的讲述中，有的是一个字节一个字节地处理数据，有的是一个字节数组一个字节数组地处理数据，这样显然是不够的，因为以后还会遇到处理字符型数据、整型数据、浮点型数据的情况。

如果使用了Filter类，则可以方便地处理那些除字节数据类型以外的数据，Filter流对象被分为“FilterInputStream”和“FilterOutputStream”两个流类。

15.5.2　处理字节类型外的文件流

本节主要介绍“FilterInputStream”类，图15.4描述了这个类的用处。

仔细分析图15.4，数据的数据类型可以是字节、字符、整型、浮点型等。先通过“FileInputstream”类读取数据，然后通过“FilterInputStream”类对数据进行组合，最后再输出数据。

【实例15.8】下面看一个有关“FilterInputStream”类的实例。

图　15.4　FilterInputStream模拟图

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个文件输入流对象fis，并且以f作为参数

04 ///创建一个过滤输入流对象filter，并且以fis作为参数

05 ///通过循环语句将f中的数据读出并且输出

06 public class file7

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\filetest\\1.txt"）；

11 FileInputStream fis=new FileInputStream（f）；

12 FilterInputStream filter=new FilterInputStream（fis）；

13 for（int i=0；i＜f.length（）；i++）

14 {

15 System.out.print（filter.read（i））；

16 }

17 fis.close（）；

18 }

19 }

【代码说明】第10行创建目标对象f，第11行创建文件输入流对象fis，第12行创建过滤输入流对象filter。【运行效果】编译结果出现了错误，原因是什么呢？其实“FilterInputStream”类是一个protected类型。有关protected类型在本书中将不详细叙述，有兴趣的读者可以自行去翻阅相关的资料，查找相关的原因。

15.5.3　字节类型外的文件输出

本节主要介绍“FilterOutputStream”类，图15.5描述了这个类的用处。

仔细分析图15.5，数据的数据类型可以是字节、字符、整型、浮点型等。先通过“FilterOutputStream”类，将所有这些类型的数据分解成字节类型的数据，再将字节类型的数据通过“FileOutputStream”类向目标对象输出数据。

图　15.5　FilterOutputStream模拟图

【实例15.9】下面看一个有关“FilterOutputStream”类的实例。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个文件输出流对象fos，并且以f作为参数

04 ///创建一个过滤输出流对象filter，并且以fos作为参数

05 ///通过循环语句往f中写入数据

06 public class file8

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\filetest\\1.txt"）；

11 FileOutputStream fos=new FileOutputStream（f）；

12 FilterOutputStream filter=new FilterOutputStream（fos）；

13 for（int i=’a’；i＜’z’；i++）

14 {

15 filter.write（i）；

16 }

17 fos.close（）；

18 }

19 }

【代码说明】从上面的程序段可以看出，“FilterOutputStream”类不像使用“FileOutputStream”类那样，必须要使用字节数组来存储数据。“FilterOutputStream”类可以直接处理除字节外的数据。

【运行效果】在1.txt中输出英文小写字母，读者可以打开此文件观察结果。

其实“FilterOutputStream”类和“FilterInputStream”类同样很难处理整型、字符串型等的数据。那么遇到这种类型的数据，应该如何处理呢？下面将引进两个类专门处理这些数据。

15.5.4　增强的多字节输出流DataOutput

通过查看Filter类的两个部分，可以发现它们都继承“InputStream”和“OutputStream”，并且在这两个子类中，没有新的方法函数，只有一些简单的“write”和“read”函数。这些函数不能处理整型、字符串型的数据。为了解决这个问题，引进了“DataInput”接口和“DataOutput”接口，同时“DataInputStream”类和“DataOutputStream”类分别实现了以上两个接口，并继承了“FilterInputStream”类和“FilterOutputStream”类。下面详细讲述“DataOutputStream”类。

DataOutputStream类的常用方法如下所示。

boolean writeBoolean（boolean v）；

byte writeByte（int v）；

charwriteChar（int v）；

shortwriteShort（int v）；

int writeInt（int v）；

long writeLong（long v）；

floatwriteFloat（float v）；

double writeDouble（double v）；

以上这些方法都用于读取基本数据类型的数据，下面还有一个方法比较特殊，可以读取“utf-8”形式的字符串。

String writeUTF（String str）；

【实例15.10】下面看一个关于“DataOutputStream”类的实例。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个文件输出流对象fos，并且以f作为参数

04 ///创建一个多字节输出流对象dos，并且以fos作为参数

05 ///使用dos对象将数据写入到f中

06 public class file9

07 {

08 public static void main（String[]args）throws Exception

09 {

10 String st；

11 File f=new File（"d：\\1.dat"）；

12 FileOutputStream fos=new FileOutputStream（f）；

13 DataOutputStream dos=new DataOutputStream（fos）；

14 try

15 {

16 dos.writeUTF（"明天要下雨了。"）；

17 dos.writeUTF（"明天要下雨了。"）；

18 dos.writeUTF（"明天要下雨了。"）；

19 dos.writeUTF（"明天要下雨了。"）；

20 }

21 catch（Exception e）

22 {}

23 dos.close（）；

24 }

25 }

【代码说明】第11行在D盘下创建一个文件1.dat，第16～19行在此文件中输入数据。

【运行效果】在D盘下生成了一个1.dat文件，可以使用写字板打开此文件查看结果。

从这个程序可以看出，通过DataOutputStream类可以处理整型、字符串类型的数据。

15.5.5　增强的多字节输入流DataInput

“DataInputStream”类实现了“DataInput”接口，所以它可以使用这个接口所提供的方法。这个接口的常用方法如下所示。

boolean readBoolean（）；

byte readByte（）；

char readChar（）；

short readShort（）；

int readInt（）；

long readLong（）；

float readFloat（）；

double readDouble（）；

以上这些方法都用于读取基本数据类型的数据，下面还有一个方法比较特殊，可以读取“utf-8”形式的字符串。

String readUTF（）；

【实例15.11】下面看一个关于“DataInputStream”类的实例。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个文件输入流对象fis，并且以f作为参数

04 ///创建一个多字节输入流对象dis，并且以fis作为参数

05 ///使用dis对象从f中读取数据

06 public class file10

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\1.dat"）；

11 FileInputStream fis=new FileInputStream（f）；

12 DataInputStream dis=new DataInputStream（fis）；

13 try

14 {

15 System.out.println（dis.readUTF（））；

16 }

17 catch（Exception e）

18 {}

19 dis.close（）；

20 }

21 }

【代码说明】第10行创建目标对象f，这里要读取的文件是D盘下的1.dat。第15行调用readUTF（）方法读取数据。

【运行效果】

明天要下雨了。

从上面的程序段可以看出，通过DataInputStream类可以处理一些整型、字符串类型的数据。【实例15.12】针对上面两个类的学习，练习这两个类的一个综合实例，代码如下。

01 import java.io.*；

02 ///创建一个类file11

03 ///在类中设置age、maths、name、chinese、physical等参数

04 ///在read方法中，以多字节输入流对象作为参数，并且利用此对象读取数据

05 public class file11

06 {

07 public void read（DataInputStream dis）

08 {

09 String name=""；

10 int age=0；

11 float maths=0；

12 float english=0；

13 float chinese=0；

14 float physical=0；

15 try

16 {

17 name=dis.readUTF（）；

18 age=dis.readInt（）；

19 maths=dis.readFloat（）；

20 english=dis.readFloat（）；

21 chinese=dis.readFloat（）；

22 physical=dis.readFloat（）；

23 }

24 catch（Exception e）

25 {}

26 System.out.println（"姓名："+name）；

27 System.out.println（"　年龄："+age）；

28 System.out.println（"数学成绩："+maths）；

29 System.out.println（"英语成绩："+english）；

30 System.out.println（"语文成绩："+chinese）；

31 System.out.println（"物理成绩："+physical）；

32 }

33 //在write方法中，以多字节输出流对象作为参数，并且利用此对象写入数据

34 public void write（String name, int age, float maths, float english, float chinese, float

35 physical, DataOutputStream dos）

36 {

37 try

38 {

39 dos.writeUTF（name）；

40 dos.writeInt（age）；

41 dos.writeFloat（maths）；

42 dos.writeFloat（english）；

43 dos.writeFloat（chinese）；

44 dos.writeFloat（physical）；

45 }

46 catch（Exception e）

47 {}

48 }

49 ///创建文件类对象

50 ///创建文件输入流对象

51 ///创建数据输入流对象

52 ///创建文件输出流对象

53 ///创建数据输出流对象

54 ///在文件类对象中写入内容

55 ///再将其内容读出来

56 public static void main（String[]args）throws Exception

57 {

58 file11 f2=new file11（）；

59 File f=new File（"d：\\1.dat"）；

60 FileInputStream fis=new FileInputStream（f）；

61 DataInputStream dis=new DataInputStream（fis）；

62 FileOutputStream fos=new FileOutputStream（f）；

63 DataOutputStream dos=new DataOutputStream（fos）；

64 f2.write（"王鹏"，30，87，88，93，100，dos）；

65 f2.read（dis）；

66 f2.write（"张浩"，29，90，89，93，100，dos）；

67 f2.read（dis）；

68 f2.write（"宋江"，33，77，80，90，80，dos）；

69 f2.read（dis）；

70 f2.write（"李宇"，32，92，81，83，90，dos）；

71 f2.read（dis）；

72 f2.write（"宋丹"，31，81，98，100，99，dos）；

73 f2.read（dis）；

74 dos.close（）；

75 dis.close（）；

76 }

77 }

【代码说明】上面的程序段是先将数据写到文本中，然后再从文本中将数据读出来。这个程序看上去很复杂，其实只要将输入和输出分成两个子系统，自然就简单了。

【运行效果】

姓名：王鹏

年龄：30

数学成绩：87.0

英语成绩：88.0

语文成绩：93.0

物理成绩：100.0

姓名：张浩

年龄：29

数学成绩：90.0

英语成绩：89.0

语文成绩：93.0

物理成绩：100.0

姓名：宋江

年龄：33

数学成绩：77.0

英语成绩：80.0

语文成绩：90.0

物理成绩：80.0

姓名：李宇

年龄：32

数学成绩：92.0

英语成绩：81.0

语文成绩：83.0

物理成绩：90.0

姓名：宋丹

年龄：31

数学成绩：81.0

英语成绩：98.0

语文成绩：100.0

物理成绩：99.0

前面所学习的处理数据的方式，都以字节为单位，只不过通过不同的流组合成其他类型数据而已。真正要处理其他的字符流，还需要继续学习下面的章节。

15.6　读取数据的媒介之二—字符流（character stream）

什么是字符流？如何使用字符流的类来处理数据？带着这些疑问，通读本节。本节的重点是学习使用字符流处理数据，并且结合了大量的实例进行演示。

15.6.1　字符流的概念

前面学过的流都是以字节为单位处理数据，称为字节流。本节将介绍一种可以一次性处理两个字节的流，称为字符流。字符流分为两个类：“Reader”类和“Writer”类，“Reader”类负责字符输入工作，而“Writer”类负责字符输出工作。

15.6.2　抽象字符输入流Reader类的使用

这个类是一个抽象类，其中的方法没有被实现，所以无法使用。另外，由于它是一个抽象类，所以无法创建对象，根据前面的介绍，如果是抽象类，可以使用其子类来覆盖其抽象方法，从而创建对象。

“Reader”类的常用方法如下：

public int read（）：自输入流读取一个字符，并以int类型返回。若无数据可读，返回-1。

public int read（char cbuf[]）：自输入流读取cbuf这个数组长度的字符，并且存储在这个数组中。如果无数据可读，则返回-1。

15.6.3　抽象字符输出流Writer类的使用

Writer类也是抽象类，其中的方法没有被实现。“Writer”类的常用方法如下：

public int write（int c）：将字符c写入输出流。

public int write（char cbuf[]）：将字符数组中的字符写入输出流。

public void write（String str）：将字符串str写入输出流。

15.6.4　读取带缓存的BufferedReader字符流

通过上面的叙述知道，“Reader”类本身不可能创建对象，因为它是抽象类，那么可以通过子类覆盖这个抽象类中的抽象方法，这称为非抽象类。非抽象类就可以创建对象。这个抽象类可以形成两个类：“InputStreamReader”和“BufferedReader”两个类。

【实例15.13】“InputStreamReader”类比较简单，通过一个实例来掌握该如何使用它。实例代码如下。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个输入流对象fis，并且以f作为参数

04 ///创建一个字符输入流对象isr，并且以fis作为参数

05 ///将读出的数据放入字符st中

06 ///再将字符输出

07 public class file12

08 {

09 public static void main（String[]args）throws Exception

10 {

11 File f=new File（"d：\\filetest\\2.txt"）；

12 FileInputStream fis=new FileInputStream（f）；

13 InputStreamReader isr=new InputStreamReader（fis）；

14 char st=（char）isr.read（）；

15 System.out.println（st）；

16 }

17 }

【代码说明】第14行将文本中的字符读取出来，但它一次只能读一个字符。

【运行效果】这是读取2.txt中的内容，读者可在此文本中输入几个字符进行测试。

“BufferedReader”类是继承“Reader”类的子类，其在内部有缓冲机制，所以可以以行为单位进行输入工作。它的工作原理如图15.6所示。

图　15.6　BufferedReader类模拟图

从图15.6可以看出，先从文件中以字节形式读取数据，然后将数据组合成字符型数据，最后将所有读取的数据缓存起来一起输出。这个类的常用方法如下：

public String readLine（）；

【实例15.14】下面举一个有关BufferedReader类的实例。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个输入流对象fis，并且以f作为参数

04 ///创建一个字符输入流对象isr，并且以fis作为参数

05 ///创建一个带缓冲的输入流对象，利用此对象读取一行数据

06 public class file13

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\filetest"，"2.txt"）；

11 FileInputStream fis=new FileInputStream（f）；

12 InputStreamReader isr=new InputStreamReader（fis）；

13 BufferedReader br=new BufferedReader（isr）；

14 System.out.println（br.readLine（））；

15 System.out.println（br.readLine（））；

16 }

17 }

【代码说明】第14～15行表示读取2.txt的前两行数据，readLine（）方法每次读取一行数据。2.txt中的数据读者可自己添加几行。

【运行效果】

one line

two line

上述代码可以读取大量的数据，比前面的类读取数据要容易得多。

15.6.5　带缓存的字符输出流BufferedWriter类

“Writer”类本身不能创建对象，因为它是抽象类，那么可通过子类来覆盖这个抽象类中的抽象方法，然后就可以创建对象。由这个抽象类可以形成两个类：“OutputStreamWriter”和“BufferedWriter”两个类。

【实例15.15】下面通过一个实例，学习“OutputStreamWriter”类的使用方法。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个输出流对象fos，并且以f作为参数

04 ///创建一个字符输出流对象osw，并且以fos作为参数

05 ///利用osw对象写数据

06 public class file14

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\filetest"，"2.txt"）；

11 FileOutputStream fos=new FileOutputStream（f）；

12 OutputStreamWriter osw=new OutputStreamWriter（fos）；

13 osw.write（'美'）；

14 osw.close（）；

15 }

16 }

【代码说明】第10行创建一个文件类对象f，指向2.txt文件，第13行在此文件内输入一个字。

【运行效果】在2.txt内输入了一个字“美”。

这个程序段将数据写到文本中。

“BufferedWriter”类是继承“Writer”类的子类，其内部有缓冲机制，所以它可以以行为单位进行输入工作。它的工作原理如图15.7所示。

图　15.7　BufferWriter类模拟图

从图15.7可以看出，先是以字符的形式将数据缓存起来，然后将其变成字节的形式写入文件。这个类的常用方法如下所示：

public void write（String str）：将字符串写入输出流中。

public void flush（）：将缓冲区内的数据强制写入输出流。

public void newLine（）：向输出流中写入一个行结束标记。

BufferedWriter的原理同“BufferedReader”类一样，只不过顺序反过来了。

【实例15.16】下面来看一个有关“BufferedWriter”类的实例。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个输出流对象fos，并且以f作为参数

04 ///创建一个字符输出流对象osw，并且以fos作为参数

05 ///创建一个带缓冲的输出流对象，利用此对象写入数据

06 public class file15

07 {

08 public static void main（String[]args）throws Exception

09 {

10 File f=new File（"d：\\filetest"，"2.txt"）；

11 FileOutputStream fos=new FileOutputStream（f）；

12 OutputStreamWriter osw=new OutputStreamWriter（fos）；

13 BufferedWriter bw=new BufferedWriter（osw）；

14 bw.write（"小王是一个好学生。"）；

15 bw.newLine（）；

16 bw.write（"他也是一个好学生。"）；

17 bw.newLine（）；

18 bw.write（"小明也是一个好学生。"）；

19 bw.close（）；

20 }

21 }

【代码说明】第10行创建文件类对象f，指向2.txt文件。第14～18行在文件中输入三行文字。第19行关闭输出流。

【运行效果】这样就可以一个字符串一个字符串地输入数据了。结果是在2.txt中输入了三行文字。

【实例15.17】为了能让读者复习已经学过的流的知识，下面将举一个有关流的综合实例。

01 import java.io.*；

02 ///在read1方法中，以带缓冲的输入流对象为参数，它主要是让这个输入流对象读取数据

03 ///在write1方法中，以带缓冲的输出流对象为参数，它主要是让这个输出流对象写入数据到f

04 ///对象中

05 public class file16

06 {

07 public void read1（BufferedReader br）

08 {

09 try

10 {

11 System.out.println（br.readLine（））；

12 }

13 catch（Exception e）

14 {}

15 }

16 public void write1（String str, BufferedWriter bw）

17 {

18 if（str.length（）＞5）

19 {

20 try

21 {

22 bw.write（str）；

23 bw.newLine（）；

24 bw.flush（）；

25 }

26 catch（Exception e）{}

27 }

28 else if（（str.length（））＜5）

29 {

30 try

31 {

32 bw.write（"输入有误！"）；

33 bw.newLine（）；

34 bw.flush（）；

35 }

36 catch（Exception e）{}

37 }

38 }

39 ///创建一个文件类对象f

40 ///创建一个文件输出流对象fos

41 ///创建一个文件输入流对象fis

42 ///创建一个多字节的输出流对象osw

43 ///创建一个多字节输入流对象isr

44 ///创建一个带缓冲的输入流对象br

45 ///创建一个代缓冲的输出流对象bw

46 ///让bw将数据写入到f中

47 ///让br从f中将数据读出来

48 public static void main（String[]args）throws Exception

49 {

50 file16 f2=new file16（）；

51 File f=new File（"d：\\filetest"，"2.txt"）；

52 FileOutputStream fos=new FileOutputStream（f）；

53 FileInputStream fis=new FileInputStream（f）；

54 OutputStreamWriter osw=new OutputStreamWriter（fos）；

55 InputStreamReader isr=new InputStreamReader（fis）；

56 BufferedWriter bw=new BufferedWriter（osw）；

57 BufferedReader br=new BufferedReader（isr）；

58 f2.write1（"祖国是个大花园"，bw）；

59 f2.write1（"小明说是吗"，bw）；

60 f2.write1（"小张觉得小明说的没有错"，bw）；

61 f2.write1（"谢谢了"，bw）；

62 f2.read1（br）；

63 f2.read1（br）；

64 f2.read1（br）；

65 f2.read1（br）；

66 br.close（）；

67 bw.close（）；

68 }

69 }

【代码说明】上面的程序段先用输出流将内容输入到文本中，然后再使用输入流读出文本。在读文本时，根据字数判断，如果字数超过5个字的就输出，否则就输出“输入有误！”，这里的判断条件只有大于5和小于5，而第59行输入的正好是5，代码中没有处理。所以其实只是在2.txt中输入了3行数据，所以输出的时候第4行读取的是null。

【运行效果】

祖国是个大花园

小张觉得小明说的没有错

输入有误！

null

15.6.6　字符输入流FileReader类和输出流FileWriter类的使用

“FileReader”类和“FileWriter”类分别是“InputStreamReader”类和“OutputStreamWriter”类的子类，它们提供将字符数据直接写入文件，或从文件中直接读出字符数据的简便方法。

【实例15.18】下面看一个有关这两个类的实例，此实例由上面的程序段修改而成，读者可通过对比，来衡量哪种方法更好。

01 import java.io.*；

02 ///在read1方法中，以带缓冲的输入流对象为参数，它主要是让这个输入流对象读取数据

03 ///在write1方法中，以带缓冲的输出流对象为参数，它主要是让这个输出流对象写入数据到f

04 ///对象中

05 public class file17

06 {

07 public void read1（BufferedReader br）

08 {

09 try

10 {

11 System.out.println（br.readLine（））；

12 }

13 catch（Exception e）

14 {}

15 }

16 public void write1（String str, BufferedWriter bw）

17 {

18 if（str.length（）＞5）

19 {

20 try

21 {

22 bw.write（str）；

23 bw.newLine（）；

24 bw.flush（）；

25 }

26 catch（Exception e）{}

27 }

28 else if（（str.length（））＜5）

29 {

30 try

31 {

32 bw.write（"输入有误！"）；

33 bw.newLine（）；

34 bw.flush（）；

35 }

36 catch（Exception e）

37 {}

38 }

39 }

40 ///创建一个文件类对象f

41 ///创建一个文件输出流对象fw

42 ///创建一个文件输入流对象fr

43 ///fr对象先读取2.txt文件

44 ///fr对象输出读入的2.txt文件

45 ///创建一个带缓冲的输入流对象br

46 ///创建一个代缓冲的输出流对象bw

47 ///让bw将数据写入到f中

48 ///让br从f中将数据读出来

49 public static void main（String[]args）throws Exception

50 {

51 file17 f2=new file17（）；

52 File f=new File（"d：\\filetest"，"2.txt"）；

53 FileReader fr=new FileReader（f）；

54 FileWriter fw=new FileWriter（f）；

55 BufferedWriter bw=new BufferedWriter（fw）；

56 BufferedReader br=new BufferedReader（fr）；

57 f2.write1（"祖国是个大花园"，bw）；

58 f2.write1（"小明说是吗"，bw）；

59 f2.write1（"小王觉得小明说的没有错"，bw）；

60 f2.write1（"谢谢了"，bw）；

61 f2.read1（br）；

62 f2.read1（br）；

63 f2.read1（br）；

64 f2.read1（br）；

65 br.close（）；

66 bw.close（）；

67 }

68 }

【代码说明】第53行创建了FileReader对象fr，第54行创建了FileWriter对象fw。第57～60行在文件2.txt中输入数据。第61～64行在控制台输出数据。

【运行效果】

祖国是个大花园

小王觉得小明说的没有错

输入有误！

null

从上面修改过的程序段可以看出，使用“FileReader”类和“FileWriter”类的确是比以前要简单的多，其流程分别如图15.8a和图15.8b所示。

图　15.8　FileReader与FileWriter比较的模拟图

15.6.7　如何用标准输入流System.in来获取数据

【实例15.19】“System.in”用于从标准键盘输入设备读入数据。下面举一个实例，让读者有个更清晰的认识。

01 import java.io.*；

02 public class file18

03 ///创建一个多字节输入流对象isr

04 ///创建一个带缓冲的输入流对象br

05 ///使用br的方法读输出

06 {

07 public static void main（String[]args）throws Exception

08 {

09 InputStreamReader isr=new InputStreamReader（System.in）；

10 BufferedReader br=new BufferedReader（isr）；

11 System.out.println（br.readLine（））；

12 br.close（）；

13 }

14 }

【代码说明】这个程序段非常简单，只是从键盘输入数据，在终端显示输入的数据。第9行的System.in用于接收输入数据。第11行输出结果。

【运行效果】

2（这是用户输入的数据，输入后按Enter键）

2

15.6.8　打印输入流PrintWriter类与PrintStream类的区别

“PrintStream”与“PrintWriter”类都是打印输出流，它们在许多方面提供了相似的功能。它们将各种基本类型的数据输出至字符串流中，并提供了自动刷新功能。这两个类的不同点，也是在自动刷新功能的设定上。

“PrintStream”会调用“println（）”方法，其输出会包含换行符。但是“PrintWriter”只有在调用“println（）”方法时自动刷新，其实两者在功能和方法上没有太大的差别。本节以“PrintWriter”类为例，介绍打印输入/输出的方法。

“PrintWriter”类的常用构造函数如下：

public PrintWriter（Writer out, boolean autoflush）：使用指定的Writer对象out，创建一个打印输出流对象。若autoflush为真，则自动刷新。

public PrintWriter（OutputStream out, boolean autoflush）：使用指定的OutputStream对象out，创建一个打印输出流对象。若autoflush为真，则自动刷新。

“PrintWriter”类的常用方法如表15.5所示。

【实例15.20】使用上述方法编写一段程序，代码如下所示。

01 import java.io.*；

02 ///创建一个多字节输入流对象isr

03 ///创建一个带缓冲的输入流对象br

04 ///使用br的方法来读取数据

05 public class file19

06 {

07 public static void main（String[]args）throws Exception

08 {

09 InputStreamReader isr=new InputStreamReader（System.in）；

10 BufferedReader br=new BufferedReader（isr）；

11 PrintWriter pw=new PrintWriter（System.out, true）；

12 pw.println（"请输出字符："）；

13 String s；

14 while（！（s=br.readLine（））.equals（""））

15 pw.println（s）；

16 br.close（）；

17 pw.close（）；

18 }

19 }

【代码说明】第12行给出提示，此时用户输入一个字符，则控制台通过pw.println（s）输出一个字符。当输入为空时，则结束程序。

【运行效果】运行这个程序，当输入一个字符，按“Enter”键后就会出现刚才输入的字符。

a

a

b

b

c

c

d

d

e

e

ff

ff

dd

dd

15.6.9　随机文件访问RandomAccessFile类的使用

“RandomAccessFile”类就是随机文件访问类。所谓的随机文件访问，就是指可以读写任意位置数据的文件，如何访问任意位置的数据呢？通过文件指针。通过移动文件指针，来达到随机访问的目的。

“RandomAccessFile”类实现了“Datainput”与“Dataoutput”接口，所以可以读取基本数据类型的数据。为了能够随机访问，必须先创建对象。下面看看“RandomAccessFile”类的构造器。

public RandomAccessFile（String name, String mode）

public RandomAccessFile（File file, String mode）

使用给定的字符串或文件对象和存取模式创建一个RandomAccessFile类对象。

对于“RandomAccessFile”类的存取模式，总共有四种，分别是“r”、“rw”、“rws”、“rwd”。“r”代表以只读方式打开文件，若此时进行写操作会出错；“rw”、“rws”、“rwd”是以读写模式打开文件，若文件不存在，则创建它。

创建完了对象后，就要使用“RandomAccessFile”类的方法来操作数据。下面介绍有关这个类的方法。

public native long getFilePointer（）：取到文件位置指针。

public native void seek（long pos）：将文件位置指针移至pos处，pos以字节为单位。

public native long length（）：获取文件大小，以字节为单位。

readBoolean（），readByte（），readChar（），readShort（），readInt（），readLong（），readFloat（），readDouble（），readU TF（）：读取不同类型的数据。

write（int b），writeBoolean（boolean v），writeByte（byte v），writeChar（char v），writeShort（short v），writeInt（int v），writeLong（long v），writeFloat（float v），writeDouble（double v），writeUTF（String str）：将不同类型的数据输出到文件。

【实例15.21】了解上述理论知识后，下面来看一个有关“RandomAccessFile”类的实例。

01 import java.io.*；

02 ///创建一个文件类对象

03 ///创建一个随机访问类对象raf

04 ///利用raf写入不同的数据类型的数据到f中

05 public class file20

06 {

07 public static void main（String[]args）throws Exception

08 {

09 File f=new File（"d：//raf.txt"）；

10 RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

11 int x=4；

12 char c=’a’；

13 long l=123；

14 float fl=3.4F；

15 double d=4.222D；

16 String str="这些都是基本数据类型的数据"；

17 raf.writeInt（x）；

18 raf.writeChar（c）；

19 raf.writeLong（l）；

20 raf.writeFloat（fl）；

21 raf.writeDouble（d）；

22 raf.writeUTF（str）；

23 raf.close（）；

24 System.out.println（"文件已经创建完毕！"）；

25 }

26 }

【代码说明】第11～15行创建6种类型的数据，然后第17～22行将这些数据输入到raf.txt中。第24行在控制台输出提示信息。

【运行效果】

文件已经创建完毕！

上面的程序段运行后，会把数据写到文本文件raf.txt中，读者可以到此文件中查看结果。

【实例15.22】下面再看一个实例，使用这个类从文本文件中读取文件数据。

01 import java.io.*；

02 ///创建一个文件类对象

03 ///创建一个随机访问类对象raf

04 ///利用raf读取不同的数据类型的数据

05 public class file21

06 {

07 public static void main（String[]args）throws Exception

08 {

09 File f=new File（"d：//raf.txt"）；

10 RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

11 //int x=4；

12 //char c=’a’；

13 //long l=123；

14 //float fl=3.4F；

15 //double d=4.222D；

16 //String str="这些都是基本数据类型的数据"；

17 System.out.println（raf.readInt（））；

18 System.out.println（raf.readChar（））；

19 System.out.println（raf.readLong（））；

20 System.out.println（raf.readFloat（））；

21 System.out.println（raf.readDouble（））；

22 System.out.println（raf.readUTF（））；

23 raf.close（）；

24 System.out.println（"这就是文件的内容！"）；

25 }

26 }

【代码说明】第9行创建文件类对象f，因为已经创建了raf.txt文件，所以这里注释掉第11～16行，然后通过第17～22行从文件中读取数据。

【运行效果】

4 a

123 3.4

4.222

这些都是基本数据类型的数据

这就是文件的内容！

【实例15.23】上面的实例还不能真正地体现出RandomAccessFile类的作用。下面再举一个实例，来学习如何利用文件指针来操作数据。

01 import java.io.*；

02 ///创建一个文件类对象f

03 ///创建一个随机访问类对象raf

04 ///让raf的指针指向内部数据的末端

05 public class file22

06 {

07 public static void main（String[]args）throws Exception

08 {

09 File f=new File（"d：//raf.txt"）；

10 RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

11 String s="这个就是学校最好的学生"；

12 System.out.println（"现在要添加数据了！"）；

13 try

14 {

15 long l=raf.length（）；

16 raf.seek（l）；

17 raf.writeUTF（s）；

18 System.out.println（"刚刚加到后面的字符串是："）；

19 raf.seek（l）；

20 System.out.println（raf.readUTF（））；

21 raf.seek（0）；

22 System.out.println（raf.readUTF（））；

23 raf.close（）；

24 }

25 catch（Exception e）

26 {}

27 }

28 }

【代码说明】上面的这个程序，主要是通过指针指向原先数据的末端，然后再添加数据。这个实例将文件指针的用法展现出来了，由于文件指针的灵活性，使得“RandomAccessFile”类比其他的流显得特殊，它可以在字符串中指向任何一个想得到的位置。

【运行效果】

现在要添加数据了！

刚刚加到后面的字符串是：

这个就是学校最好的学生

15.7　利用对象序列化控制输入/输出

前面讲述了如何控制基本数据的输入/输出，本节将讲述如何输入/输出对象数据。对象数据是很复杂的，那么如何将它们输入到文本中呢？又如何从文本中将其读出来？例如一个数组如果采用前面所说的方式去输入/输出，那将是一个复杂的过程。再如一个类，如何将类中的一些数据输入或者输出？这些都涉及一个概念，就是对象序列化。本节将详细地介绍什么是对象序列化，如何利用对象序列化来控制输入流和输出流。

15.7.1　什么是对象序列化

什么是对象序列化呢？简单地说，就是将对象写入流，而序列化解体则指从流中获取数据后，重构对象的过程。Java的对象可以分为可序列化对象和不可序列化对象，从说明文档中，可以看到只有实现了“Serializable”接口的对象才是可序列化对象。

15.7.2　基本数据和对象数据读写ObjectInput接口与ObjectOutput接口

“ObjectInput”接口与“ObjectOutput”接口分别继承了“DataInput”和“DataOutput”接口，提供用于读写基本数据和对象数据的方法。“ObjectInput”提供了“readObject（）”方法，此方法用于将对象从流中读出。“ObjectOutput”提供了“writeObject（）”方法，此方法用于将对象写入流中。因为“ObjectInput”与“ObjectOutput”皆为接口，所以不能创建对象，那么就只有使用实现了这两个接口的“ObjectInputStream”类和“ObjectOutputStream”类来创建对象。下面将详细地讲述如何使用“ObjectInputStream”类和“ObjectOutputStream”类来操作数据。

15.7.3　对象序列化处理ObjectOutputStream类的使用

“ObjectOutputStream”类继承了“OutputStream”类，同时实现了“ObjectOutput”接口，提供将对象序列化并写入流中的功能。此类的构造器如下：

public ObjectOutputStream（OutputStream out）

【实例15.24】下面通过一个实例，来看看如何使用“ObjectOutputStream”类。

01 import java.io.*；

02 public class file23

03 {

04 ///创建一个文件对象f

05 ///创建一个输出流对象fos，并且以f作为参数

06 ///创建一个对象序列化处理类的对象oos，并且以fos作为参数

07 ///创建一个学生类st

08 ///利用oos对象方法将st的属性值写入到f中

09 public static void main（String[]args）throws Exception

10 {

11 File f=new File（"d：//raf.txt"）；

12 FileOutputStream fos=new FileOutputStream（f）；

13 ObjectOutputStream oos=new ObjectOutputStream（fos）；

14 student st=new student（）；

15 st.name="王鹏"；

16 st.code="96765"；

17 st.age="28"；

18 st.sexy="男"；

19 st.school="重庆大学"；

20 st.grade="计算机三年级二班"；

21 st.address="重庆市沙坪坝"；

22 oos.writeObject（st）；

23 oos.close（）；

24 System.out.println（"文件创建完了！"）；

25 }

26 }

27 ///创建一个学生类

28 ///类中有name、age、sexy、school、code、address等属性

29 class student implements Serializable

30 {

31 String name；

32 String age；

33 String sexy；

34 String school；

35 String grade；

36 String address；

37 String code；

38 }

【代码说明】第29～38行创建了一个类student，其实现了Serializable接口。第14～21行创建st对象，并设置对象的各个属性。第22行输出类对象。

【运行效果】这个程序将一个对象直接写入到文本文件raf.txt中。读者可查看该文件，以了解程序的输出。

15.7.4　对象序列化处理ObjectInputStream类的使用

“ObjectInputStream”类继承了“InputStream”类，同时实现了“ObjectInput”接口，提供将对象序列化并从流中读取出来的功能。此类的构造器如下：

public ObjectInputStream（InputStream out）

【实例15.25】下面通过一个实例，来看看如何使用“ObjectInputStream”类。

01 import java.io.*；

02 ///创建一个文件对象f

03 ///创建一个输入流对象fis，并且以f作为参数

04 ///创建一个对象序列化处理类的对象ois，并且以fis作为参数

05 ///创建一个学生类st

06 ///利用ois对象方法将st的属性值读出

07 public class file24

08 {

09 public static void main（String[]args）throws Exception

10 {

11 File f=new File（"d：//raf.txt"）；

12 FileInputStream fis=new FileInputStream（f）；

13 ObjectInputStream ois=new ObjectInputStream（fis）；

14 student st=new student（）；

15 st=（student）ois.readObject（）；

16 ois.close（）；

17 System.out.println（st.name）；

18 System.out.println（st.age）；

19 System.out.println（st.code）；

20 System.out.println（st.school）；

21 System.out.println（st.grade）；

22 System.out.println（st.address）；

23 }

24 }

【代码说明】上面的程序段从文本中依次读出了对象中的数据，这样，就方便以后将对象类型的数据放入流中，来进行输入/输出操作。

【运行效果】

王鹏

28 96765

重庆大学

计算机三年级二班

重庆市沙坪坝

学到这里，所有的流输入和输出的知识就全部学习完了，希望读者能够真正地理解输入和输出的真谛。

15.8　常见疑难解答

15.8.1　字节流与字符流主要的区别

答：字节流是最基本的，所有的“InputStream”和“OutputStream”的子类都是字节流，其主要用于处理二进制数据，并按字节来处理。实际开发中很多的数据是文本，这就提出了字符流的概念，它按虚拟机的encode来处理，也就是要进行字符集的转化。这两者之间通过“InputStreamReader”和“OutputStreamWriter”来关联。实际上，通过“byte[]”和“String”来关联在实际开发中出现的汉字问题，这都是在字符流和字节流之间转化不统一而造成的。在从字节流转化为字符流时，实际上就是“byte[]”转化为“String”。

public String（byte bytes[]，String charsetName）

注意　有一个关键的参数字符集编码，通常可以省略，就是操作系统的lang。

字符流转化为字节流，实际上是String转化为byte[]。

byte[]String.getBytes（String charsetName）

至于java.io中还出现了许多其他的流，主要是为了提高性能和使用方便，如“BufferedInputStream”、“PipedInputStream”等。

15.8.2　输入流与输出流如何区分，各有什么作用

答：初学Java，看到输入流与输出流的部分，有一点不明白，到底是输入流写入还是输出流写入文件呢？要将文件读出是用输入流还是输出流好呢？

程序在内存中运行，文件在磁盘上，把文件从磁盘上读入内存中来，这就需要输入流。反之，把内存中的数据写到磁盘上的文件里就是输出。

那与Windows里所说的写（将内容写入到文件里，如存盘）—输入和读（把内容从文件里读出来，如显示）输出，为什么不一样呢？

是不是可以这样理解：Java里的输入流与输出流针对内存而言，它是从内存中读写，而不是所说的显示与存盘，因为输入流与输出流都可以将内容从屏幕上显示出来。

屏幕和键盘也是区别于内存的设备，可以将内存中的数据“输出”到屏幕上，所以要用“System.out.println（）”，而从终端读取键盘输入用“System.in”。

那写文件该用输入流还是输出流呢？读文件又用什么好呢？为什么？

程序操作的数据都应该是在内存里面，内存是操作的主对象，把数据从其他资源中传送到内存，就是输入。反之，把数据从内存传送到其他资源，就是输出。例如读文件：

BufferedReader in=new BufferedReader（new InputStreamReader（new FileInputStream（"infilename"）））；

不管从磁盘读、从网络读，还是从键盘读，读到内存，就是“InputStream”。例如：写文件：

BufferedWriter out=new BufferedWriter（new OutputStreamWriter（new FileOutputStream（"outfilename"）））；

不管写到磁盘、网络，或者屏幕，都是使用“OuputStream”。

15.8.3　什么是管道流

答：管道流是输入/输出并用。例如，将数据从输出管道进，从输入管道出。

PipedInputStream pis=new PipedInputStream（）；

PipedOutputStream pos=new PipedOutputStream（pis）；

在本书中没有介绍管道流，是因为这种流对于初学者来说比较难理解，有兴趣的读者可以查阅相关的资料。

15.9　小结

本章介绍了输入/输出的一些关键类和方法：处理文件和目录有专门的File类，读取数据的媒介Stream类等。通过本章的学习，读者应该能处理简单的文件和数据流。本章介绍的类非常多，所以讲解了特别多的实例，希望读者都能亲自动手验证这些实例。

15.10　习题

1.字节流都派生自_________和_________类。

2.所有的流操作都需要在最后_________。

3._________可以带来更好的流操作效率。

4._________关键字可以允许属性不被序列化。

5.Java中简单的配置可借助什么类进行？

第16章　异常与处理及内存管理

本章将分为两部分进行讲述：一部分要详细地讲述异常及其处理方法；另一部分介绍Java程序中的内存管理。第一部分，几乎在所有的开发程序中都存在。而第二部分的知识，是为了让读者能清楚地知道，Java程序运行和结束时，内存是如何分配的。

本章重点：

❑异常的处理。

❑异常的抛出和捕获。

❑内存的管理。

16.1　异常的概念

在以前的程序段中，经常会出现“try{}catch（Exception e）{}”这样的语句。这就是本章重点要讲述的异常处理。为了让读者能循序渐进地掌握这一章，本节先讲述什么是异常，异常有哪几种。

16.1.1　异常的分类

所谓的异常，就是可以预见的错误。Java中所有的异常都从“Throwable”类中继承，不过一般情况下，都会由Exception类派生出来。异常分为两个部分：一部分是错误，另一部分是异常。对于错误来说，只能终止程序。

对于异常，又可以分为运行期异常和非运行期异常。一个运行期异常是由程序员自身的错误造成的。例如，一个越界的数组访问、一个空指针的访问等。而这些异常被称为未检查异常，它们都不应该产生，除此之外，都属于已检查异常。

本章主要讲述一些已检查异常，因为这些异常编译器不能处理，通过抛出一个已检查异常给异常处理器来处理，这样就可以顺利地通过程序的编译。

已检查异常可以分为很多种，主要分类如下：

ArithmeticException（算术异常）

NullPointerException（无空间对象异常）

ArrayIndexOutOfBoundsException（数组越界异常）

NegativeArraySizeException（数组长度为负值异常）

ArrayStoreException（数组存储异常）

IllegalArgumentException（非法参数异常）

SecurityException（安全性异常）

因为它们的父类都是Exception类，所以可以使用“Exception”来代替所有的异常。在实际开发中，这些类型的异常会经常遇到。例如，数组越界异常、数组存储异常、非法参数异常等。在编写程序代码时，可以不用写出详细的异常，使用它们的总类作为代替，即使用“Exception”来代替上面所有类型的异常。这样对编程人员来说，就省了很多事情。

16.1.2　异常的声明

如何进行异常处理呢？首先要了解异常是如何声明的。异常的声明是告诉Java编译器有一个异常出现了。

【实例16.1】在方法中，异常声明在方法的头部，利用关键字“throws”来表示此方法在运行的时候，很可能会出现异常现象。下面先看一个例子。

01 import java.io.*；

02 public class file1

03 {

04 public static void main（String[]args）

05 {

06 File f=new File（"d：//raf.txt"）；

07 RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

08 String s="这个就是我们学校最好的学生"；

09 System.out.println（"现在要添加数据了！"）；

10 long l=raf.length（）；

11 raf.seek（l）；

12 raf.writeUTF（s）；

13 System.out.println（"刚刚加到后面的字符串是："）；

14 raf.seek（l）；

15 System.out.println（raf.readUTF（））；

16 raf.seek（0）；

17 System.out.println（raf.readUTF（））；

18 raf.close（）；

19 }

20 }

【代码说明】这个程序在编译的时候，会出现以下的编译错误。但如果在方法的头部声明异常，那编译时就不会再出现错误。

【运行效果】

file2.java：7：未报告的异常java.io.FileNotFoundException；必须对其进行捕捉或声明

以便抛出

RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

^

file2.java：11：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

long l=raf.length（）；

file2.java：12：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

raf.seek（l）；

file2.java：13：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

raf.writeUTF（s）；

file2.java：15：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

raf.seek（l）；

^

file2.java：16：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

System.out.println（raf.readUTF（））；

file2.java：17：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

raf.seek（0）；

file2.java：18：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

System.out.println（raf.readUTF（））；

file2.java：19：未报告的异常java.io.IOException；必须对其进行捕捉或声明以便抛出

raf.close（）；

9错误

【实例16.2】看下面的代码。

01 import java.io.*；

02 public class file2

03 {

04 public static void main（String[]args）throws Exception

05 {

06 File f=new File（"d：//raf.txt"）；

07 RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

08 String s="这个就是这所学校最好的学生"；

09 System.out.println（"现在要添加数据了！"）；

10 long l=raf.length（）；

11 raf.seek（l）；

12 raf.writeUTF（s）；

13 System.out.println（"刚刚加到后面的字符串是："）；

14 raf.seek（l）；

15 System.out.println（raf.readUTF（））；

16 raf.seek（0）；

17 System.out.println（raf.readUTF（））；

18 raf.close（）；

19 }

20 }

【代码说明】第4行由于声明了异常，所以在编译时，就不会报错。但是当运行时，又可能会出现运行时的异常报错，如何去处理呢？这就需要抛出异常。关于抛出异常后面会详细讲述，下面总结如何进行异常声明。

方法名throws Exception

｛方法体｝

异常的声明其实就是声明一个异常类，在声明之后将其抛出。

【运行效果】

现在要添加数据了！

刚刚加到后面的字符串是：

这个就是这所学校最好的学生

这个就是这所学校最好的学生

16.1.3　异常的抛出

所谓异常的抛出，就是将异常抛给异常处理器，暂时不去处理它。如何处理它呢？这就依靠异常的捕获。关于异常的捕获在下一节中会具体地讲述。言归正传，在上一小节中声明的内容，其实就是一个异常的抛出。

还有一种异常的抛出，就是一直将异常不断地抛出而不去处理。这通过关键字“throw”来处理。下面看一个有关异常再抛出的模型。

方法名throws Exception

{方法体throw Exception

方法体

}

异常的抛出这个知识点相信读者已经掌握了。

【实例16.3】下面继续讲述如何来处理被抛出的异常，来看一个有关异常抛出的实例。

01 import java.io.*；

02 public class file3

03 {

04 public static double quotient（int numerator, int denominator）throws

05 DivideByZeroException

06 {

07 if（denominator==0）

08 throw new DivideByZeroException（）；

09 else

10 return（double）numerator；

11 }

12 public static void main（String[]args）

13 {

14 try

15 {

16 double result=quotient（3，0）；

17 }

18 catch（DivideByZeroException exception）

19 {

20 System.out.println（exception.toString（））；

21 }

22 }

23 public static class DivideByZeroException extends ArithmeticException

24 {

25 public DivideByZeroException（）

26 {

27 super（"不能被0除"）；

28 }

29 }

}

【代码说明】第4～11行是创建了一个方法quotient，其抛出错误信息DivideByZeroException。第23～29行是自定义的错误信息。第14～21行进行除法运算并俘获异常。

【运行效果】

file3$DivideByZeroException：不能被0除

上述程序演示了如何抛出异常。读者通过这个程序，学习在实际开发程序中，异常抛出的用法。在后面的综合实例时，会经常看到异常的用法。

16.2　异常的捕获

异常的抛出就是将异常抛给异常处理器，但没有告诉异常处理器需要如何处理。这是一个极其不负责任的表现，只有需要好好地处理异常现象，在程序运行时才不会再出现异常报错。

16.2.1　捕获多个异常

Java语言对于异常的处理，通过try和catch语句来实现。当有一个异常被抛到异常处理器后，通过try和catch语句对被抛出的异常进行捕获和处理。其语法结构如下：

try

{可能会出现异常的程序段}

catch（Exception e）

{捕获异常后，如何处理它}

【实例16.4】下面修改前面的一个例子，代码如下：

01 import java.io.*；

02 public class file4

03 {

04 public static void main（String[]args）throws Exception

05 {

06 File f=new File（"d：//raf.txt"）；

07 RandomAccessFile raf=new RandomAccessFile（f，"rw"）；

08 String s="这个就是这所学校最好的学生"；

09 System.out.println（"现在要添加数据了！"）；

10 try

11 {

12 long l=raf.length（）；

13 raf.seek（l）；

14 raf.writeUTF（s）；

15 System.out.println（"刚刚加到后面的字符串是："）；

16 raf.seek（l）；

17 System.out.println（raf.readUTF（））；

18 raf.seek（0）；

19 System.out.println（raf.readUTF（））；

20 raf.close（）；

21 }

22 catch（Exception e）{}

23 }

24 }

【代码说明】代码第10～21行是要捕获异常的程序段，第22行是捕获异常后的程序处理，这里没有任何处理代码。

【运行效果】

现在要添加数据了！

刚刚加到后面的字符串是：

这个就是这所学校最好的学生

这个就是这所学校最好的学生

上述程序将异常捕获后进行了处理，运行时，就不会再对异常报错。如果一个程序段有多个异常出现，应该如何处理呢？下面来看看多异常的处理方法。

try

{

}

catch（1）

{

}；

catch（2）

{

}；

catch（3）

{

}；

finally

{

}；

以上结构的意义是：若在try语句出现异常后，那么看它出现的异常是“1”还是“2”或者“3”，如果是以上的异常，那么就分别执行catch后“｛｝”内的语句。

说明

无论是否发生异常，或者发生哪一个异常，都要执行finally后的语句。这一点非常重要，请读者务必牢记。

【实例16.5】下面看一个有关多异常处理的实例。

01 import java.io.*；

02 public class file5

03 {

04 public static void main（String[]args）throws Exception

05 {

06 int[]a=new int[5]；

07 try

08 {

09 a[10]=1；

10 }

11 catch（ArrayIndexOutOfBoundsException ae）

12 {

13 System.out.println（ae）；

14 }

15 catch（ArrayStoreException ae）

16 {

17 System.out.println（ae）；

18 }

19 catch（ArithmeticException ae）

20 {

21 System.out.println（ae）；

22 }

23 finally

24 {

25 System.out.println（"发现了异常，并且处理它了！"）；

26 }

27 }

28 }

【代码说明】代码第9行发生了错误，但它在try程序段内。所以可以捕获异常，判断异常是不是第11～22行所指向的异常，判断后还要执行第23～26行的finally程序段。

【运行效果】

java.lang.ArrayIndexOutOfBoundsException：10

发现了异常，并且处理它了！

以上的程序段证明了前面所说的，无论是否发生异常，或者发生哪一个异常，finally后面的语句都必须执行。

16.2.2　自定义异常

用户自定义异常通过扩展Exception类来创建。这种异常类可以包含一个“普通”类所包含的任何东西。

【实例16.6】下面就是一个用户定义异常类的例子，它包含一个构造函数、几个变量和方法。

01 public class One

02 {

03 public static void main（String str[]args）throws ServerTimedOutException

04 {

05 try

06 {

07 throw new ServerTimedOutException（"Could not connect"，80）；

08 }

09 catch（ServerTimedOutException e）

10 {

11 System.out.println（"异常信息是："+e.toString（））；

12 }

13 }

14 }

15 class ServerTimedOutException extends Exception

16 {

17 private String reason；

18 private int port；

19 public ServerTimedOutException（String reason, int port）

20 {

21 this.reason=reason；

22 this.port=port；

23 }

24 public String getReason（）

25 {

26 return reason；

27 }

28 public int getPort（）

29 {

30 return port；

31 }

32 }

【代码说明】第15～32行自定义了一个异常类ServerTimedOutException，其继承自Exception类。第5～8行捕获异常信息，第9～12行处理异常信息。

【运行效果】

异常信息是：ServerTimedOutException

【实例16.7】考虑一个客户服务器程序。在客户代码中，要与服务器连接，并希望服务器在5秒内响应。如果服务器没有响应，代码就抛出一个异常（如一个用户定义的ServerTimedOutException），实现方法如下所示。

01 public void connectMe（String serverName）throws

02 ServerTimedOutException{

03 int success；

04 int portToConnect=80；

05 success=open（serverName, portToConnect）；

06 if（success==-1）{

07 throw new ServerTimedOutException（

08 "Could not connect"，80）；

09 }

10 }

要捕获异常，使用try语句：

11 public void findServer（）

12 {

13 try{

14 connectMe（defaultServer）；

15 }catch（ServerTimedOutException e）{

16 System.out.println（"Server timed out, trying alternate"）；

17 try{

18 connectMe（alternateServer）；

19 }catch（ServerTimedOutException e1）

20 {

21 System.out.println（"No server currently available"）；

22 }

23 }

【代码说明】上述代码并不完整，只是给出了部分代码，包括第2行的异常声明，第13～22行的捕获异常和处理异常。读者可根据上一个实例的实现方法，实现这个实例。

也可部分地处理一个异常，然后将其抛出。如：

try{

……

}catch（ServerTimedOutException e）{

System.out.println（"Error caught"）；

throw e；

}

16.3　内存的管理和回收

使用Java无须担心如何销毁对象。换句话说，就是Java运行时，无须负责Java对象的内存管理。当Java不再使用某个对象时，它会自动进行垃圾回收。

垃圾回收是一个比较复杂的过程，当程序运行时会自动检查整个内存，检查内存中哪些对象引用不再被使用。一旦检查出来后，便会安全删除这些对象。然而，由于垃圾回收需要占用系统的资源，所以它可能会影响应用程序代码的运行，即如果在执行应用程序代码的过程中，执行垃圾回收，则应用程序代码的执行时间可能延长，这会导致程序运行的延迟。由于不知道何时会进行垃圾回收，因此延迟的时间也是不可预知的。

实时应用程序对时间的要求非常严格，即它们必须在确定的、已知的延迟条件下，执行应用程序代码，因此垃圾回收所引起的不可预知的延迟，就成为一个实时程序致命的问题。

垃圾回收的主要问题，是程序无法估计时间延迟导致程序执行的延迟。能否避免这种问题的发生呢？通过更频繁地进行垃圾回收，就可以限制最大延迟时间。因此，垃圾回收成为可预知的。

注意

虽然有垃圾回收和其他的内存管理技术，Java程序仍然可能存在内存泄漏。例如：在不需要组件对象时，却没将其从容器中移除。

下面是一些Java程序设计中有关内存管理的经验：

❑最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候，都是让引用变量在退出活动域后，自动设置为null。在使用这种方式时，必须特别注意一些复杂的对象图。例如，数组、队列、树、图等，这些对象之间的相互引用关系较为复杂。对于这类对象，GC（垃圾回收）回收它的效率一般较低，如果程序允许，尽早将不用的引用对象赋为null，这样可以加速GC的工作。例如：

……

A a=new A（）；

//应用a对象

a=null；//当使用对象a之后主动将其设置为空

……

但要注意，如果“a”是方法的返回值，千万不要做这样的处理，否则从该方法中得到的返回值永远为空，而且这种错误不易被发现，因此这时很难及时抓住、排除“NullPointerException”异常。

❑尽量少用“finalize”函数。“finalize”函数是Java给程序员提供的一个释放对象或资源的机会，但是，它会加大GC的工作量，因此尽量少采用“finalize”方式回收资源。

❑如果需要使用经常用到的图片，可以使用soft应用类型，它尽可能将图片保存在内存中，供程序调用，而不引起“OutOfMemory”。

❑注意集合数据类型，包括数组、树、图、链表等数据结构，这些数据结构对GC来说，回收更为复杂。另外，注意全局变量以及静态变量，这些变量往往容易引起悬挂对象，造成内存浪费。

❑尽量避免在类的默认构造器中创建、初始化大量的对象，防止在调用其自己类的构造器时，造成不必要的内存资源浪费。

❑尽量避免强制系统做垃圾内存的回收（通过显式调用方法“System.gc（）”），增长系统做垃圾回收的最终时间，降低系统性能。

❑尽量避免显式申请数组空间，当不得不显式地申请数组空间时，尽量准确地估计出其合理值，以免造成不必要的系统内存开销。

❑尽量在做远程方法调用（RMI）类应用开发时，使用瞬间值（transient）变量，除非远程调用端需要获取该瞬间值变量的值。

❑尽量在合适的场景下，使用对象池技术以提高系统性能，缩减系统内存开销。但是要注意对象池的尺寸不易过大，及时清除无效对象释放内存资源。综合考虑应用运行环境的内存资源限制，避免过高估计运行环境所提供内存资源的数量。

16.4　常见疑难解答

16.4.1　为什么要声明方法抛出异常

答：方法是否抛出异常与方法返回值的类型一样重要，假设方法抛出异常，却没有声明该方法将抛出异常，那么客户程序员可以调用这个方法。不编写处理异常的代码，那么，一旦出现异常，就没有合适的异常控制器来解决。

16.4.2　为什么抛出的异常一定是已检查异常

答：RuntimeException与Error可以在任何代码中产生。它们不需要由程序员显式地抛出，一旦出现错误，那么相应的异常会被自动抛出。

而已检查异常是由程序员抛出的，这分为两种情况：程序员调用会抛出异常的库函数（库函数的异常由库程序员抛出）、程序员自己使用throw语句抛出异常。遇到Error，程序员一般是无能为力的，遇到RuntimeException，那么一定是程序存在逻辑错误，要对程序进行修改（相当于调试的一种方法）。

只有已检查异常才是程序员所关心的，程序应该抛出或处理已检查异常。覆盖父类某方法的子类方法，不能抛出比父类方法更多的异常。有时设计父类的方法时，会声明抛出异常，但实现方法的代码却并不抛出异常。这样做的目的就是，方便子类方法覆盖父类方法时可以抛出异常。

16.5　小结

代码越多的项目错误就会越多，本章所学习的知识就是要求我们尽量减少代码错误，并且能及时捕获错误。读者可能都知道微软的XP一直在打补丁，这些所谓的补丁就是一些程序的漏洞或错误。为了尽量避免这些错误的发生，掌握本章的知识非常关键。

16.6　习题

1.异常是什么？

2.异常分为哪些类？

3.列出几个常见的异常。

4.throw和throws有什么区别？

5.Unchecked异常需要捕获吗？

6.清场的代码应该写在哪个代码块里？

第17章　Java中对数据的处理

本章将继续讲述如何处理数据，包括一些经常使用的数据处理方法，例如包装类、随机性数据处理类、数据整理和排序处理类等。其实，在应用程序中，最能让用户感兴趣的不是华丽的界面，而是数据。例如一个学校管理系统，大家要看这个软件是否好，当然关心软件是如何处理数据的。可见处理数据在程序中的作用是何等之大。

本章重点：

❑基本数据类型和对象数据类型的转换。

❑随机性数据的处理。

❑数据的排序和整理。

17.1　如何将基本数据类型转换为对象

前面介绍过，Java语言分为基本数据类型和对象数据类型，而前面大部分的章节是讲述如何处理基本类型的数据。本章讲述对象类型数据的处理方法，通过大量实例和经验总结，介绍如何熟练地处理程序中的数据。

17.1.1　为什么要将基本数据转换为对象

Java是一种面向对象语言，Java中的类把方法与数据连接在一起，并构成了自包含式的处理单元。在Java中不能定义基本类型（primitive type）。为了能将基本类型视为对象来处理，并能连接相关的方法，Java为每个基本类型都提供了包装类，这样，便可以把这些基本类型转化为对象来处理。这些包装类有“Boolean”、“Byte”、“Short”、“Character”、“Integer”、“Long”、“Float”、“Void”等。

Java可以直接处理基本类型，但是，在有些情况下，需要将其作为对象来处理，这时就需要将其转化为包装类。所有的包装类（Wrapper Class）都有共同的方法，它们是：

❑带有基本值参数并创建包装类对象的构造函数，如可以利用Integer包装类创建对象，Integer obj=new Integer（145）。

❑带有字符串参数并创建包装类对象的构造函数，如new Integer（“-45.36”）。

❑生成字符串表示法的toString（）方法，如obj.toString（）。

❑对同一个类的两个对象进行比较的equals（）方法，如obj1.eauqls（obj2）。

❑生成哈希表代码的hashCode（）方法，如obj.hashCode（）。

❑将字符串转换为基本值的parseType（）方法，如Integer.parseInt（args[0]）。

❑可生成对象基本值的typeValue（）方法，如obj.intValue（）。

在一定的场合，运用Java包装类来解决问题，能大大提高编程效率。

Wrapper类由以下几个部分组成。

❑Boolean：将boolean型数据包装成Boolean类对象。

❑Byte：将byte型数据包装成Byte类对象。

❑Character：将character型数据包装成Character类对象。

❑Short：将short型数据包装成Short类对象。

❑Integer：将integer型数据包装成Integer类对象。

❑Long：将long型数据包装成Long类对象。

❑Float：将float型数据包装成Float类对象。

❑Double：将double型数据包装成Double类对象。

说明　由于所有的Wrapper类都具有相似的成员，因此在这里就以Integer类为例，讲述包装类的使用。

17.1.2　Wrapper类的构造函数

本节将以Integer类为例，讲述Wrapper类的使用。作为一个类，读者最关心的是这个类如何构造对象，这又涉及构造器的问题。下面将看一下Integer类的构造器。

public integer（int value）：将整型值value包装成持有此值的Integer类对象。

public integer（string s）：将由数字字符包装成持有此值的Integer类对象。若s并非由数字构成，则会异常抛出。

【实例17.1】为了能更清楚这个类的构造器的用法，下面举一个有关它的实例。

01 public class file1

02 {

03 public static void main（String[]args）

04 {

05 int x=12；

06 String str="13579"；

07 Integer t1=new Integer（x）；

08 Integer t2=new Integer（str）；

09 System.out.println（t1）；

10 System.out.println（t2）；

11 }

12 }

【代码说明】第5行定义了一个基本类型的变量x，第6行定义了一个字符串对象str。第7～8行通过构造函数构造两个Integer对象，然后第9～10行输出这两个对象。

【运行效果】

12 13579

注意

这个实例将不同类型的数据作为对象输出，输出结果虽然是数字，但它们是对象数据，而不是基本类型的数据。

17.1.3　Wrapper类的常用函数

本节将以Integer类为例讲述Wrapper类的使用。一个类要处理数据，就要关心这个类中的方法函数。表17.1展示的是这个类的常用方法。

【实例17.2】说了那么多理论知识，下面举一个有关的实例，来学习如何使用这些方法函数。

01 ///创建一个包装类的对象x1

02 ///通过x1中的不同方法输出不同的数据类型的数据

03 public class file2

04 {

05 public static void main（String[]args）

06 {

07 int a=20；

08 byte b='a'；

09 short c=11；

10 long d=112；

11 float e=11.2f；

12 double f=11.3；

13 Integer x1=new Integer（a）；

14 Byte x2=new Byte（b）；

15 Short x3=new Short（c）；

16 Long x4=new Long（d）；

17 Float x5=new Float（e）；

18 Double x6=new Double（f）；

19 System.out.println（x1）；

20 System.out.println（x2）；

21 System.out.println（x3）；

22 System.out.println（x4）；

23 System.out.println（x5）；

24 System.out.println（x6）；

25 System.out.println（x1.toBinaryString（a））；

26 System.out.println（x1.toHexString（a））；

27 }

28 }

【代码说明】第7～12行创建了6个基本类型的变量，第13～18行通过构造器创建了6个对象类型的构造对象。第25～26行使用了Wrapper类的两个方法实现类型转换。

【运行效果】

20 97

11 112

11.2

11.3

10100 14

【实例17.3】上面的程序是否看起来有点简单？那么再看看下面这个程序段。

01 ///创建一个包装类的对象i

02 ///自定义一个方法isNumberic

03 public class file3

04 {

05 public static boolean isNumberic（String str）

06 {

07 try

08 {

09 Integer i=new Integer（str）；

10 }

11 catch（NumberFormatException e）

12 {

13 return false；

14 }

15 return true；

16 }

17 public static void main（String[]args）

18 {

19 System.out.println（isNumberic（"123"））；

20 System.out.println（isNumberic（"-123.34"））；

21 System.out.println（isNumberic（"0x12"））；

22 System.out.println（isNumberic（"453"））；

23 System.out.println（isNumberic（"1abcd"））；

24 System.out.println（isNumberic（"-1a33"））；

25 26

}

27 }

【代码说明】从上面的程序段可以看出，如果str不是全部由数字组成，就会抛出异常，而异常处理就是返回false。第9行通过Integer类的构造器来构造对象。

【运行效果】

true

false

false

true

false

false

分析上面的例子发现，原来构造器可以作为一个判断的依据。这样，就可以将这个实战经验应用在一些大型的程序中。

17.2　如何处理随机性的数据

在现实生活中，会遇到很多随机性的事物，例如中彩票、摇奖等，这些都可以通过程序计算其中奖的概率。在Java语言类库中，有一个专门操作这种随机性数据的类，它就是Random类。下面先看看这个类如何操作随机性数据。

Random类的常用方法：

public boolean nextBoolean（）：返回true或者false。

public double nextDouble（）：返回0～1之间的double型小数。

public float nextFloat（）：返回0～1之间的float型小数。

public int nextInt（）：返回int型整数。

public int nextInt（int n）：返回0～1之间的整数。

public long nextLong（）：返回long型整数。

【实例17.4】为了能够熟练地掌握它们，先举一些简单的实例。

01 import java.util.*；

02 ///创建一个随机类的对象rnd

03 ///通过随机类对象的方法随机地给数据赋值

04 public class file4

05 {

06 public static void main（String[]args）

07 {

08 Random rnd=new Random（）；

09 int a=rnd.nextInt（10）；

10 int b=rnd.nextInt（10）+5；

11 int c=3*rnd.nextInt（10）；

12 System.out.println（a）；

13 System.out.println（b）；

14 System.out.println（c）；

15 }

16 }

【代码说明】第8行创建随机数对象rnd，第9～11行通过对象的方法为变量赋值。

【运行效果】上面程序段运行3次，会出现3次不同的结果，因为它本来就是一个随机数的使用。

0 13

27 再运行

9 6

6 再运行

2 10

15

注意　在编写上述程序段时，必须引进“java.util.*”包，因为程序中需要调用包中的很多方法和类。

17.2.1　Random类的使用场合

Random类一般使用在那种随机性比较强的场合，例如人机对抗游戏、彩票中奖等。因为这些场合都含有一种随机的特性，也就是使用同样的方法去操作数据，每次操作的结果都不一样。下面会通过一些实际开发中的例子，让读者熟悉这个类的用法。

17.2.2　通过实例熟悉Random类的常用方法

前面的小节中，讲述了Random类的基本概念和常用的方法，以及在什么场合使用Random类。下面将通过一些具有代表性的实例，介绍如何使Random类在实际开发工作中发挥作用。

【实例17.5】编写一个扑克游戏程序，游戏中需要均匀地插排，即任意选择两张扑克，进行彼此交换。程序如下：

01 import java.util.*；

02 ///创建一个随机类对象rnd

03 ///创建一个字符串类型的数组card

04 ///通过循环语句，不断地从数组中取出两个对象，对其进行比较和排序

05 public class file5

06 {

07 public static void main（String[]args）

08 {

09 try

10 {

11 Random rnd=new Random（）；

12 String[]card={"方块1"，"方块2"，"方块3"，"方块4"，"方块5"，"方块6"，"方块7"，"方块8"，"方块9"}；

13 14

String str；

15 int x, y；

16 for（int i=0；i＜100；i++）

17 {

18 x=rnd.nextInt（9）；

19 y=rnd.nextInt（9）；

20 str=card[x]；

21 card[x]=card[y]；

22 card[y]=str；

23 }

24 for（int i=0；i＜9；i++）

25 {

26 System.out.println（card[i]）；

27 }

28 }

29 catch（Exception e）{}

30 }

31 }

【代码说明】第9行和第29行实现了程序的异常处理功能。第12行定义一个字符串数组card，然后第16～23行随意抽取两张扑克进行位置调换。第24～27行循环输出字符串数组card的数据。

【运行效果】

方块3

方块6

方块4

方块5

方块8

方块2

方块9

方块1

方块7

再运行：

方块3

方块9

方块5

方块8

方块7

方块6

方块2

方块1

方块4

通过这个程序段，读者可以体会到Random类的好处所在。现在再来看一个比较复杂的实例。

【实例17.6】编制一个剪子、包袱、锤子的游戏程序，计算机的选择由随机发生器实现。

01 import java.util.*；

02 import java.io.*；

03 ///创建一个随机类rnd

04 ///通过分支语句来输出不同的结果

05 public class file6

06 {

07 public static void main（String[]args）

08 {

09 try

10 {

11 Random rnd=new Random（）；

12 int i=0；

13 String[]hand={"剪子"，"锤子"，"包袱"}；

14 System.out.println（"开始游戏了"）；

15 System.out.print（"剪子（0），锤子（1），包袱（2）中，选择哪一个呢？"）；

16 int c=System.in.read（）；

17 System.out.println（"＜结果＞"）；

18 switch（c）

19 {

20 case'0'：

21 System.out.println（"玩家："+hand[0]）；

22 break；

23 case'1'：

24 System.out.println（"玩家："+hand[1]）；

25 break；

26 case'2'：

27 System.out.println（"玩家："+hand[2]）；

28 break；

29 }

30 int b=rnd.nextInt（3）；

31 System.out.println（"计算机："+hand[b]）；

32 if（（c-49）＞b）

33 {

34 System.out.println（"玩家胜利！"）；

35 System.out.println（"比分：1：0"）；

36 }

37 else if（（c-49）＜b）

38 {

39 System.out.println（"计算机胜利！"）；

40 System.out.println（"比分：0：1"）；

41 }

42 else if（（c-49）==b）

43 {

44 System.out.println（"平手！"）；

45 System.out.println（"比分：0：0"）；

46 }

47 }

48 catch（Exception e）{}

49 }

50 }

【代码说明】上面的程序段中，先使用随机类在第13行定义的划拳数组hand中，随机取一个。然后，用户选择一个，最后将两者进行比较，通过比较得出想要的结果。

【运行效果】

开始游戏了

剪子（0），锤子（1），包袱（2）中，选择哪一个呢？0

＜结果＞

玩家：剪子

计算机：锤子

计算机胜利！

比分：0：1

再运行

开始游戏了

剪子（0），锤子（1），包袱（2）中，选择哪一个呢？2

＜结果＞

玩家：包袱

计算机：剪子

玩家胜利！

比分：1：0

17.3　如何对数据进行排列、整理

在很多应用程序中，一堆数据摆在眼前，需要知道哪些数据是最大的，哪些数据是最小的。在学生管理系统的成绩模块中，就必须要对所有成绩进行一个排序和整理，所以对数据的排列和整理，是程序设计中一个不可缺少的环节。

17.3.1　Arrays类的常用方法

在排序方面，Java语言提供了Arrays类来处理这个问题，Arrays类提供了数组整理、比较和检索功能。Arrays类中所有的方法都是静态方法，所以没有办法创建对象，它的常用方法如表17.2所示。

不知道读者有没有注意到，在搜索方法函数中，有一个二进制查找的概念。与二进制查找类似的查找方式，还有一种叫做顺序查找，这两者有什么区别吗？

❑顺序查找：在未经过整理的数组中，检索key值。

❑二进制查找：在整理过的数组中，检索key值。

说明　由于二进制查找仅用于经过整理后的数组中，所以速度要相对快点。

【实例17.7】下面将举一个实例来熟悉排序的用法。

01 import java.util.*；

02 public class file7

03 {

04 public static void main（String[]args）

05 {

06 int[]a=new int[]{2，34，21，11，23，56，65，33，89，90}；

07 Arrays.sort（a）；

08 for（int i=0；i＜a.length；i++）

09 {

10 System.out.print（a[i]+""）；

11 }

12 System.out.println（"\n56的位置为："+Arrays.binarySearch（a，56））；

13 System.out.println（"33的位置为："+Arrays.binarySearch（a，33））；

14 }

15 }

【代码说明】第6行定义了一个数组a，其包含10个没有顺序的数字，第7行使用排序方法sort（）对a进行排序，第8～11行输出排序结果，第12～13行使用binarySearch（）方法查找指定数字在数组中的位置。

注意　第7行直接使用Arrays.sort（）方法，不需要创建新对象。

【运行效果】

2 11 21 23 33 34 56 65 89 90

56的位置为：6

33的位置为：4

说明　数组位置的索引是从0开始。

从上面的程序段可以观察到，一个数组如何进行排序和整理，以及搜索元素位置。

基本数据之间可以比较大小，那么对象之间如何比较大小呢？对象也可以分为可比较对象和不可比较对象，这就取决于其是否实现了Comparable接口，可比较对象实现了Comparable接口，而不可比较对象没有实现此接口。

要想实现这个接口，就应该实现Comparable接口中的compareTo（）方法。如下所示：

public interface Comparable

{

public int compareTo（object o）{}；

}

这个compareTo（）方法的功能是：如果这个对象小于对象o，则返回负值；若这个对象大于对象o，则返回正值；若两者相等，则返回0。

17.3.2　实例分析

【实例17.8】本小节将介绍一个比较复杂的实例，目的是结合前面的知识作一个综合性的运用。先来看看下面程序的一个流程，如图17.1所示。

为了让读者可以很好地理解这个程序，下面将分步骤去讲解。

（1）设计学生类，代码如下所示。

图　17.1　排列和整理数据实例

01 ///创建一个学生类，并让其实现Comparable接口，这样就可以对类中的数据进行排列

02 ///name指学生的姓名

03 ///age指学生的　年龄

04 ///sexy指学生的　性别

05 ///code指学生的　学号

06 ///school指学生所在学校的名称

07 ///grade指学生的年级

08 ///major指学生的专业

09 ///address指学生家庭住址

10 ///set函数是一个设置器

11 ///get函数是一个访问器

12 class student implements Comparable 13{

14 String name；

15 int age；

16 String sexy；

17 String code；

18 String school；

19 String grade；

20 String major；

21 String address；

22 student x；

23 student y；

24 student（String name）

25 {

26 this. name=name；

27 }

28 public void set（int age, String sexy, String code, String school, String grade, String major, String

29 address）

30 {

31 this. age=age；

32 this. sexy=sexy；

33 this. school=school；

34 this. grade=grade；

35 this. major=major；

36 this. code=code；

37 this. address=address；

38 }

39 public String getname（）

40 {

41 return name；

42 }

43 public String getcode（）

44 {

45 return name；

46 }

47 public String getsexy（）

48 {

49 return sexy；

50 }

51 public int getage（）

52 {

53 return age；

54 }

55 public String getschool（）

56 {

57 return school；

58 }

59 public String getmajor（）

60 {

61 return major；

62 }

63 public String getgrade（）

64 {

65 return grade；

66 }

67 public String getaddress（）

68 {

69 return address；

70 }

71 public String toString（）

72 {

73 String information="学生　姓名："+name+""+"　学号："+code+""+"　性别"+sexy+""+"　年龄：

74 "+age+""+"所在学校："+school+""+"所学专业："+major+""+"　所在年级："+grade+""+"　家庭地址：75"+address；

76 return information；

77 }

78 ///通过实现compareTo这个方法，来实现排序

79 public int compareTo（Object o）

80 {

81 student st=（student）o；

82 return（age-st. age）；

83 }

84 }

注意　虽然先给出了学生类代码，但在实际文件中第一次编译时，一定让该类在主运行类的后面。

（2）让这个学生类对象在主运行类中输出。详细代码如下所示。

85 import java.util.*；

86 ///主运行类

87 ///st1至st10是创建出来的学生类的十个对象

88 ///set方法设置了对象的参数值

89 ///Arrays用来排列对象中的数据

90 public class file2

91 {

92 public static void main（String[]args）

93 {

94 student st1=new student（"王鹏"）；

95 student st2=new student（"王浩"）；

96 student st3=new student（"孙鹏"）；

97 student st4=new student（"孙文君"）；

98 student st5=new student（"谭妮"）；

99 student st6=new student（"赵志强"）；

100 student st7=new student（"王凯"）；

101 student st8=new student（"苏瑞"）；

102 student st9=new student（"张伟"）；

103 student st10=new student（"张杰"）；

104 //通过设置器赋予多个对象参数值

105 st1.set（20，"男"，"10000"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

106 st2.set（22，"男"，"10001"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

107 st3.set（21，"男"，"10002"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

108 st4.set（19，"女"，"10003"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

109 st5.set（18，"女"，"10004"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

110 st6.set（24，"男"，"10005"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

111 st7.set（22，"男"，"10006"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

112 st8.set（29，"女"，"10007"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

113 st9.set（25，"女"，"10008"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

114 st10.set（28，"男"，"10009"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

115 student[]a=new student[]{st1，st2，st3，st4，st5，st6，st7，st8，st9，st10}；

116 try

117 {

118 Arrays.sort（a）；

119 for（int i=0；i＜a.length；i++）

120 {

121 System.out.println（a[i]）；

122 }

123 }

124 catch（Exception e）

125 {

126 System.out.println（"出错了"）；

127 }

128 }

129 }

【代码说明】第12～84行构建一个学生类，这个在前面多次讲过，具体属性不再介绍。要注意的是第12行中，此类实现了Comparable接口，第79～83行又实现了该接口的compareTo方法，所以通过该学生类创建的对象可以进行比较。从代码第82行可以看出，对象通过age属性进行比较。

【运行效果】

学生　姓名：谭妮　学号：10004　性别女　年龄：18　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：孙文君　学号：10003　性别女　年龄：19　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：王鹏　学号：10000　性别男　年龄：20　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：孙鹏　学号：10002　性别男　年龄：21　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：王浩　学号：10001　性别男　年龄：22　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：王凯　学号：10006　性别男　年龄：22　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：赵志强　学号：10005　性别男　年龄：24　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：张伟　学号：10008　性别女　年龄：25　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：张杰　学号：10009　性别男　年龄：28　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：苏瑞　学号：10007　性别女　年龄：29　所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

上面的实例很简单，主要目的是学习如何比较对象大小。

17.4　常见疑难解答

17.4.1　Random类函数在现实生活中有什么用处

答：Random类函数在现实生活中的用处很多，例如在彩票业务中的抽奖程序，还有在游戏中的扑克游戏和麻将游戏，这些都含有Random类函数，因为它们都具有随机性。

17.4.2　数组的排列和整理数据在实际应用中有什么用处

答：排列和整理数据在实际开发中有很大的用处。在学校管理系统中，老师可以通过数据的排列和整理数据来查看数据，例如成绩的高低等。

17.5　小结

本章是一些特殊的数据处理情况，主要是复习了一些前面的基础知识，如类型一般分为两大类：基本类型和对象类型。那么如何实现这两大类型之间的转换呢？这是第17.1节介绍的内容。买过彩票的人都知道，抽奖结果是随机的，那如何通过Java实现随机抽取值的程序呢？读者不妨看一下第17.2节。在很多数据的情况下，我们需要做一些排序或其他操作，这些就是第17.3节介绍的知识。

17.6　习题

1.parseInt（）方法的作用是______。

2.实现数组排序的方法是______。

3.二进制查找和顺序查找的区别是什么？

第四篇　Java数据结构

第18章　数据结构的接口

本章将重点讲述数据结构中的一些基本概念，同时还讲述什么是Collection接口，什么又是Iterator接口，使用什么类来实现这些接口。本章的目的主要是对数据结构的接口做一个初步的、概念性的介绍，让读者能够清楚这些接口的用处，以便在实际开发中灵活运用。

本章重点：

❑数据结构的实现。

❑Collection集合接口与Iterator迭代器接口。

❑Map映射接口。

❑List链表接口和Set接口。

18.1　数据结构接口及实现

本节讲述数据结构的优点，数据结构其实就是规定数据是以何种形式存储，例如以队列的形式、以散列表的形式，还是以树的形式或者以映像的形式。

每一种存储方式都有不同的优势，因为每一种结构对应着存储不同种类的数据，只要使用了好的数据结构，那么读取或搜索数据将会更快、更准确。

数据按一定的方式组织起来就是数据结构，它体现了数据与数据之间的关系，可以从两个角度来分类：一个是存储结构，另一个是逻辑结构。

18.1.1　数据结构接口的实质

为了能够说明数据结构的含义，先解释下面的一些基本概念。

❑数据是对客观事物的符号的表示，是所有能输入到计算机中并被计算机程序处理的符号的总称。

❑数据元素是数据的基本单位，在计算机程序中通常作为一个整体来处理。一个数据元素由多个数据项组成，数据项是数据不可分割的最小单位。

❑数据结构是相互之间存在一种或多种特定关系的数据元素的集合。数据结构是一个二元组，记为：data_structure=（D, S）。

其中D为数据元素的集合，S是D上关系的集合。

数据元素相互之间的关系称为结构。根据数据元素之间关系的不同特性，通常分为下列4类基本结构：

❑集合：数据元素同属一个集合。

❑线性结构：数据元素间存在一对一的关系。

❑树形结构：结构中元素间是一对多的关系。

❑图（网）状结构：结构中元素间是多对多的关系。

数据的逻辑结构和物理结构：

❑逻辑结构：数据元素之间存在的关系（逻辑关系）称为数据的逻辑结构。

❑物理结构：数据结构在计算机中的表示称为数据的物理结构。

一种逻辑结构可映像成不同的存储结构：顺序存储结构和非顺序存储结构（链式存储结构和散列结构）。

Java语言类库提供了不少接口，其中有很多是关于数据结构的。所谓数据结构的接口，就是规定了此种数据结构如何存储数据。例如常见的数据结构接口：队列接口。

interface Queue

{

void add（Object obj）；

//向队尾插入元素

Object remove（）；

//从队头删除元素

int size（）；

//查看队列的长度

}

可以通过实现这个接口，将数据存储到这种数据结构中，以方便存取。在Java类库中像这样的接口有很多，希望读者在闲暇时间，逐个尝试地编写代码，从而达到对知识的融会贯通。

18.1.2　用实例来熟悉数据结构接口的实现

【实例18.1】下面举一个有关数据结构的接口的实例，通过这个实例，观察Java如何使用数据结构的接口来实现对数据的存储。这个实例会引用到一个数据结构：ArrayList结构，目前还没有讲到这个结构。其实，本例的用意是让读者了解如何通过数据结构的接口，来实现数据存储，并加深对数据接口这个概念的理解。

01 import java.util.*；

02 public class ArraylistTest

03 {

04 public static void main（String[]args）

05 {

06 ArrayList al=new ArrayList（）；

07 al.add（"anson"）；

08 al.add（"John"）；

09 al.add（"Tina"）；

10 for（int i=0；i＜al.size（）；i++）

11 {

12 System.out.println（al.get（i））；

13 }

14 }

15 }

【代码说明】第6行通过一个简单的数组列表ArrayList，创建一个数据结构对象al，第7～9行向数据结构中添加数据。

上述代码编辑时会有一个提示：

D：\＞javac ArraylistTest.java

注意：ArraylistTest.java使用了未经检查或不安全的操作。.

注意：要了解详细信息，请使用-Xlint：unchecked重新编译。

以上只是对于安全性方面的一个提示，编译还会正常进行。如果考虑增加安全性，可以在代码中使用ArrayList＜String＞randomNum=new ArrayList＜String＞（）的形式。

【运行结果】

anson

John

Tina

18.2　Collection集合接口与Iterator迭代器接口

什么是Collection接口？什么是Iterator接口？它们有什么用处？使用频率高吗？带着这些疑问阅读本节，对数据结构将有更深的了解。

18.2.1　熟悉Collection集合接口

Collection接口是数据集合接口，它位于数据结构API的最上部。构成Collection的单位，被称为元素。接口提供了添加、删除元素等管理数据的功能。根据数据管理的方法不同，可将Collection接口分成为3个部分，分别是Map接口、Set接口、List接口，如图18.1所示。

Collection是最基本的集合接口，一个Collection代表一组Object，即Collection的元素（Elements）。一些Collection允许相同的元素而另一些不行，一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类，Java SDK提供的类都继承自Collection的子接口，如List和Set。

所有实现Collection接口的类都必须提供两个标准的构造函数：

（1）无参数的构造函数，用于创建一个空的Collection。

图　18.1　数据结构种类图

（2）带Collection参数的构造函数，用于创建一个新的Collection。这个新的Collection与传入的Collection拥有相同的元素

说明　带参数的构造函数允许用户复制一个Collection。

如何遍历Collection中的每一个元素？不论Collection的实际类型如何，它都支持iterator（）方法，该方法返回迭代子。使用该迭代子即可访问Collection中的每一个元素。方法典型用法如下：

01 Iterator it=collection.iterator（）；//获得一个迭代子

02 while（it.hasNext（））

03 {

04 Object obj=it.next（）；//得到下一个元素

05 }

至于什么是迭代器，将在下一小节中详细讲述。由Collection接口派生的两个子接口是List和Set，下面就讲述这两个接口中的具体内容。

18.2.2　List链表接口和Set接口

List接口和Set接口是两个非常有用的集合接口，日常开发中用到的数组和列表等，基本上都从这两个接口实现而来。本节通过对它们的比较，详细了解这两种集合接口。

1.List接口

List接口是有序的Collection，使用此接口能够精确地控制每个元素插入的位置。用户能够使用索引（元素在List中的位置，类似于数组下标）来访问List中的元素，这类似于Java的数组。

与下面所提到的Set不同，List允许有相同的元素。

除了具有Collection接口必备的iterator（）方法外，List还提供listIterator（）方法，其返回ListIterator接口。与标准的Iterator接口相比，ListIterator多了一些add（）之类的方法，允许添加、删除、设定元素，还能向前或向后遍历。

实现List接口的常用类有LinkedList、ArrayList、Vector和Stack。

（1）LinkedList链表类。LinkedList实现了List接口，允许null元素。此外，LinkedList在首部或尾部提供额外的get（）、remove（）、insert（）方法，这些操作使LinkedList可被用作堆栈（stack）、队列（queue）或双向队列。

LinkedList没有同步方法，如果多个线程同时访问一个List，则必须自己实现访问同步，一种解决方法是在创建List时构造一个同步的List。

List list=Collections.synchronizedList（new LinkedList（……））；

（2）ArrayList数组列表类。ArrayList实现了可变大小的数组，它允许所有元素，包括null。ArrayList没有同步。

每个ArrayList实例都有一个容量（Capacity），即存储元素的数组的大小，这个容量可随着不断添加新元素而自动增加，但是增长算法并没有定义。当需要插入大量元素时，在插入前可调用ensureCapacity（）方法来增加ArrayList的容量，以提高插入效率。与LinkedList一样，ArrayList也是非同步的（unsynchronized）。

（3）Vector存储类。Vector非常类似ArrayList，但Vector是同步的。由Vector创建的Iterator和ArrayList创建的Iterator是同一接口。当Iterator被创建而且正在被使用时，另一个线程改变了Vector的状态（例如添加或删除了一些元素），因Ve c t o r是同步的，这时调用I t e r a t o r的方法将抛出ConcurrentModificationException，因此必须捕获该异常。

（4）Stack栈类。Stack继承自Vector，实现一个后进先出的堆栈。Stack提供5个额外的方法，使得Vector被当作堆栈使用。Stack有基本的push（）、pop（）方法，还有peek（）方法，用来得到栈顶的元素。empty（）方法测试堆栈是否为空。search（）方法检测一个元素在堆栈中的位置。新创建的Stack是空栈。

2.Set接口

Set是一种不包含重复元素的Collection，即任意的两个元素e1和e2比较，结果都不相等，如e1.equals（e2）=false。Set最多有一个null元素。

注意

Set的构造函数有一个约束条件，传入的Collection参数不能包含重复的元素。必须小心操作可变对象（Mutable Object）。如果一个Set中的可变元素改变了自身状态，Object.equals（Object）=true会发生一些问题。

18.2.3　Map映射接口

Map没有继承Collection接口，其提供key到value的映射。Map中不能包含相同的key，每个key只能映射一个value。Map接口提供3种集合的视图，Map的内容可以被当作一组key集合、一组value集合或一组key-value映射。

1.Hashtable哈希表类

Hashtable继承Map接口，实现一个key-value映射的哈希表。任何非空（non-null）的对象都可作为key或value。添加数据使用put（key, value）方法，取出数据使用get（key）方法，这两个基本操作的时间开销为常数。

Hashtable通过initial capacity和load factor两个参数调整性能。通常默认的load factor 0.75较好地实现了时间和空间的均衡，增大loadfactor可以节省空间，但相应的查找时间将增大，这会影响像get和put这样的操作。

【实例18.2】演示Hashtable的简单示例，将1、2、3放到Hashtable中，它们的key分别是“one”、“two”、“three”，详细代码如下所示。

01 Hashtable numbers=new Hashtable（）；

02 numbers.put（"one"，new Integer（1））；

03 numbers.put（"two"，new Integer（2））；

04 numbers.put（"three""，new integer（3））；

05要取出一个数，比如2，用相应的key：

06 Integer n=（Integer）numbers.get（"two"）；

07 System.out.println（"two="+n）；

【代码说明】这里只演示Hashtable的用法，并不是完整的实例，读者可将其添加到main主函数中，自己来测试一下运行效果。

作为key的对象，将通过计算其散列函数确定与之对应的value位置，因此任何key的对象都必须实现hashCode（）和equals（）方法。hashCode（）和equals（）方法继承于根类Object。如果用自定义的类当作key，则要相当小心。按照散列函数的定义，如果两个对象相同，即obj1.equals（obj2）=true，则它们的hashCode必须相同。但如果两个对象不同，则它们的hashCode不一定不同。如果两个不同对象的hashCode相同，这种现象称为冲突。冲突会导致操作哈希表的时间开销增大，所以尽量定义好的hashCode（）方法，这能加快哈希表的操作。

说明

如果相同的对象有不同的hashCode，对哈希表的操作会出现意想不到的结果（期待的get（）方法返回null）。要避免这种问题，只需要牢记一条：要同时复写equals（）方法和hashCode（）方法，而不要只写其中一个。Hashtable是同步的。

2.HashMap哈希映射类

HashMap和Hashtable类似，不同之处在于HashMap是非同步的，并且允许null，即null value和null key。如将HashMap视为Collection时（values（）方法可返回Collection），其迭代子操作时间开销和HashMap的容量成比例。因此，如果迭代操作的性能相当重要，切记不要将HashMap的初始化容量设得过高，或者load factor过低。

3.WeakHashMap弱哈希映射类

WeakHashMap是一种改进的HashMap，它对key实行“弱引用”，如果一个key不再被外部所引用，那么该key可以被GC回收。

总结：

❑如果涉及堆栈、队列等操作。应该考虑用List。对于需要快速插入、删除元素的数据集合，建议使用LinkedList。如果需要快速随机访问元素，应该使用ArrayList。

❑如果程序在单线程环境中，或者访问仅仅在一个线程中进行，考虑非同步的类，其效率较高。如果多个线程可能同时操作一个类，应该使用同步的类。

❑要特别注意对哈希表的操作，作为key的对象要正确复写equals（）和hashCode（）方法。尽量返回接口而非实际的类型，如返回List而非ArrayList，这样如果以后需要将ArrayList换成LinkedList，客户端代码不用改变，这就是针对抽象的编程。

18.2.4　Iterator迭代器接口

Iterator是一个集合的迭代器，通过Iterator访问接口而不用关心集合的实现。Iterator接口中定义了三个方法：

❑hasNext（）：是否还有下一个元素。

❑next（）：返回下一个元素。

❑remove（）：删除当前元素。

那什么是迭代器呢？迭代器指向两个元素中间的位置，当调用hasnext（）方法时，如果返回true，此时调用next（）方法返回下一元素。迭代器指向下两个元素之间的位置，如果要删除下一个元素，必须先next（），再remove（）。

1.迭代器的作用

有些类没有提供get操作，可用迭代器来获得信息。所有Collection接口的子类、子接口都支持Iterator迭代器。

迭代器（Iterator）模式，又称为游标（Cursor）模式。下面给出它的官方定义：提供一种方法访问一个容器（Container）对象中各个元素，而又不需暴露该对象的内部细节，这就是迭代器。

从定义可见，迭代器模式是为容器而生。为了能够在容器内实现遍历，曾经有很多人提出过多种方法。有的认为，对容器对象的访问必然涉及遍历算法，可以将遍历方法塞到容器对象中。有的认为，或者根本不去提供什么遍历算法，让使用容器的人自己去实现。这两种情况好像都能够解决问题。

然而在前一种情况中，容器承受了过多的功能。它不仅要负责自己“容器”内的元素维护（添加、删除等），而且还要提供遍历自身的接口。由于遍历状态保存的问题，不能对一个容器对象同时进行多个遍历。第二种情况却又将容器的内部细节暴露无遗。

而迭代器模式的出现，很好地解决了上面两种情况的弊端。下面详细地介绍迭代器模式。

2.迭代器模式

迭代器模式由以下角色组成：

❑迭代器角色（Iterator）：负责定义访问和遍历元素的接口。

❑具体迭代器角色（Concrete Iterator）：实现迭代器接口，并记录遍历中的当前位置。

❑容器角色（Container）：负责提供创建具体迭代器角色的接口。

❑具体容器角色（Concrete Container）：实现创建具体迭代器角色的接口—这个具体迭代器角色与该容器的结构相关。

下面列举迭代器模式的实现方式。

❑迭代器角色定义了遍历的接口，但是没有规定由谁来控制迭代。在Java Collection的应用中，由客户程序来控制遍历的进程，被称为外部迭代器。还有一种实现方式是由迭代器自身来控制迭代，被称为内部迭代器。外部迭代器要比内部迭代器灵活、强大，而且内部迭代器在Java环境中，可用性很弱。

❑在迭代器模式中，没有规定谁来实现遍历算法。因为既可以在一个容器上使用不同的遍历算法，也可以将一种遍历算法应用于不同的容器。这样就破坏了容器的封装—容器角色就要公开自己的私有属性。在Java中意味着向其他类公开了自己的私有属性。那把它放入容器角色里实现，这样迭代器角色就被架空，仅仅具备存放一个遍历当前位置的功能，但是遍历算法和特定的容器紧紧绑在一起。而在Java Collection的应用中，提供的具体迭代器角色是定义在容器角色中的内部类，这样便保护了容器的封装。同时容器也提供遍历算法接口，用户可以扩展自己的迭代器。

18.2.5　通过实例来认识迭代器的实现

下面通过一个实例，演示如何实现Java Collection中的迭代器。

1.迭代器的代码实现

【实例18.3】迭代器模式的设计代码如下所示。

01 ///迭代器角色，仅仅定义了遍历接口

02 public interface Iterator

03 {

04 boolean hasNext（）；

05 Object next（）；

06 void remove（）；

07 }

08 ///容器角色，这里以List为例。它也仅仅是一个接口，就不罗列出来了

09 ///具体容器角色，是实现了List接口的ArrayList等类。为了突出重点这里只介绍和迭代器相关

10 ///的内容

11 ///具体迭代器角色，它是以内部类的形式出来的。AbstractList是为了将各个具体容器角色的公

12 ///共部分提取出来而存在的

13 public abstract class AbstractList implements List

14 {

15 ……

16 ///这个便是负责创建具体迭代器角色的工厂方法

17 public Iterator iterator（）{

18 return new Itr（）；

19 }

20 ///作为内部类的具体迭代器角色

21 private class Itr implements Iterator

22 {

23 int cursor=0；

24 int lastRet=-1；

25 int expectedModCount=modCount；

26 public boolean hasNext（）

27 {

28 return cursor！=size（）；

29 }

30 public Object next（）

31 {

32 checkForComodification（）；

33 try{

34 Object next=get（cursor）；

35 lastRet=cursor++；

36 return next；

37 }

38 catch（IndexOutOfBoundsException e）{

39 checkForComodification（）；

40 throw new NoSuchElementException（）；

41 }

42 }

43 public void remove（）

44 {

45 if（lastRet==-1）

46 throw new IllegalStateException（）；

47 checkForComodification（）；

48 try

49 {

50 AbstractList.this.remove（lastRet）；

51 if（lastRet＜cursor）

52 cursor--；

53 lastRet=-1；

54 expectedModCount=modCount；

55 }

56 catch（IndexOutOfBoundsException e）{

57 throw new ConcurrentModificationException（）；

58 }

59 }

60 final void checkForComodification（）

61 {

62 if（modCount！=expectedModCount）

63 throw new ConcurrentModificationException（）；

64 }

65 }

【代码说明】至于迭代器模式的使用，要先得到具体容器角色，然后再通过具体容器角色，得到具体迭代器角色。这样就可以使用具体迭代器角色来遍历容器。上述代码并不是完整的程序，只是演示各个角色的创建过程。

说明

本章介绍的大部分都是理论知识，主要是让读者有一个数据结构的概括知识，重要的实例都在下一章介绍。

在实现自己的迭代器时，一般要操作的容器有支持的接口才可以，而且还要注意以下问题：

❑在迭代器遍历的过程中，通过该迭代器进行容器元素的增减操作是否安全？

❑在容器中存在复合对象的情况，迭代器怎样才能支持深层遍历和多种遍历？

以上两个问题对于不同结构的容器角色，各不相同，值得考虑。

2.迭代器的适用情况

由上面的讲述可以看出，迭代器模式给容器的应用带来以下好处：

❑支持以不同的方式遍历一个容器角色，根据实现方式的不同，效果上会有差别。

❑简化了容器的接口，但是在Java Collection中为了提高可扩展性，容器还是提供了遍历的接口。

❑对同一个容器对象，可以同时进行多个遍历，因为遍历状态保存在每一个迭代器对象中。

由此得出迭代器模式的适用范围：

❑访问一个容器对象的内容而无须暴露它的内部表示。

❑支持对容器对象的多种遍历。

❑为遍历不同的容器结构提供一个统一的接口（多态迭代）。

迭代器的出现，避免了在数据结构中频繁地操作数据，同时也避免了代码的复杂性，为程序员提供了很大的方便。在实际开发的很多实例中，都将会使用到迭代器，方便程序员在数据结构中进行查询、添加、删除元素等操作。迭代器模式在现实的应用中很广泛，特别是在以后的数据库程序开发中。

学习完本章后，读者可能觉得概念还是有点模糊。没有关系，本章只是讲述数据结构的一些基本的、概念性的知识，没有涉及具体的实例。但从下一章开始，会使用实例讲解它们的具体用法，并使用实例来验证本章所讲述的知识点。

18.3　常见疑难解答

18.3.1　Collection集合接口和Collections集合类的区别

答：Collection和Collections的区别有以下几点：

❑Collections是java.util下的类，它包含各种有关集合操作的静态方法。

❑Collection是java.util下的接口，它是各种集合结构的父接口。

18.3.2　ArrayList数组列表类和Vector存储类的区别

答：ArrayList和Vector的区别有两点：

❑同步性：Vector是线程安全的，是同步的。而ArrayList是线程不安全的，不是同步的。

❑数据增长：当需要增长时，Vector默认增长为原来一倍，而ArrayList却是原来的一半。

18.3.3　HashMap哈希映射和Hashtable哈希表的区别

答：HashMap和Hashtable的区别有三点：

❑都属于Map接口的类，作用都是将唯一键映射到特定的值上。

❑HashMap类没有分类或排序，它允许一个null键和多个null值。

❑Hashtable类似于HashMap，但是不允许null键和null值，它也比HashMap慢，因为它是同步的。

18.3.4　数据结构的种类有哪些

答：数据结构一般分为两大类，即线性数据结构和非线性数据结构。线性数据结构包括线性表、栈、队列、串、数组和文件。而非线性数据结构包括树、图等。可通过图18.2来展示它们的结构。

图　18.2　数据结构图

18.3.5　List链表接口和Set接口的区别

答：List和Set的区别如下：

❑Set：从Collection接口继承而来，但没有提供新的抽象的实现方法，Set不能包含重复元素。

❑List：是一个有序的集合，可以包含重复的元素，提供了按索引访问的方式。这里的有序就是指有顺序地排放，并不是排序。

18.4　小结

学习本章时，读者首先要明白两个概念：接口和迭代器。理解了它们的概念后，才能知道它们的作用，最后才能明白如何使用它们。学习完本章后，读者需要学会Collection集合接口、List链表接口、Set接口、Map映射接口和Iterator迭代器接口的简单实用。

18.5　习题

1.如何防止出现数组越界的错误？

2.用______方法可获取List对象的元素个数。

3.哪一类仓库是不允许数据重复的？

4.______可以让仓库更安全。

5.______是专用于集合迭代的。

第19章　Java中的数据结构

上一章重点讲述了数据结构、数据结构接口的基本知识，主要是以理论加上少量的实例进行讲解。本章将继续上一章的内容，通过实例来进一步加深对数据结构的认识。本章的实例分析数据结构、各种不同类型的接口，以及这些结构的具体实现过程。

本章重点：

❑链表和数组列表。

❑散列表和散列集。

❑树集和映像。

❑枚举和属性集。

19.1　链表

如果把数组作为一种数据结构，可能对读者来说更容易理解。数组这种结构有一个很大的缺点，数组中的所有元素都按序排列，如果要删除其中一个元素，后面的所有元素都需要依次向前方移动一个位置。如果这个数组元素很多，那么依次移动的次数就会明显增多，从而耗费大量的系统资源。

为了能够解决这个问题，数据结构引入了链表这个结构。下面将讲述有关链表这种数据结构的定义和具体实现。

19.1.1　什么是Java中的链表

本节介绍一种新的数据结构：链表。在Java语言中，链表中的元素存储在一条链的节点上，不像数组是按序存储在一系列连续的空间中。仔细分析图19.1，会明白它们的区别所在。

图　19.1　链表结构和数组结构图

分析为什么链表作为一种数据结构比数组要好？如图19.2所示。

从图19.2可以看出，如果在数组中删除一个元素，那么后面所有的元素都必须向前移动一位。如果元素比较多，那么移动的次数就会更多。从图19.3所示的链表图中可以看出，删除一个元素时，只需将被删除元素的前一个元素的指针，指向被删除元素的后一个元素，使得被删除的元素脱离链表，就相当于删除了元素。链表不需要像数组那样移动元素，这样就节约了系统资源。链表的结构如图19.4所示。

当要删除数组第二个元素时

图　19.2　数组的操作数据图

图　19.3　链表的操作数据图

图　19.4　链表的真正结构图

每个元素都拥有两个指针属性，一个是previous指针，一个是next指针。每一个元素的next指针都会指向下一个元素本身，而下一个元素的previous指针都会指向上一个元素本身。这样，要删除元素就比较简单，只需要让被删元素前面元素的previous指针指向被删元素后面的元素本身，再将被删除元素后面元素的next指针指向被删除元素的前面元素本身即可。

19.1.2　用程序代码段实现对链表的添加

【实例19.1】下面演示一个有关操作链表中数据的实例，代码如下所示。

01 import java.util.LinkedList；

02 import java.util.Iterator；

03 ///创建一个链表类对象List

04 ///创建一个迭代器对象it

05 ///通过迭代器对象it来遍历List对象中的元素

06 ///创建另一个迭代器对象it1来重新遍历List对象中的元素

07 public class file1

08 {

09 public void add（）

10 {

11 LinkedList List=new LinkedList（）；

12 List.add（"王鹏"）；

13 List.add（"王浩"）；

14 List.add（"王杰"）；

15 List.add（"张杰"）；

16 List.add（"李杰"）；

17 List.add（"孙文杰"）；

18 List.add（"赵杰"）；

19 Iterator it=List.iterator（）；

20 System.out.println（"现在添加了如下的同学的姓名："）；

21 while（it.hasNext（））

22 {

23 System.out.println（it.next（））；

24 }

25 System.out.println（"删除某些同学的姓名后，还剩下哪些同学呢？"）；

26 it.remove（）；

27 Iterator it1=List.iterator（）；

28 for（int i=0；i＜List.size（）；i++）

29 {

30 System.out.println（it1.next（））；

31 }

32 }

33 public static void main（String[]args）

34 {

35 File1 f=new file1（）；

36 f.add（）；

37 }

38 }

【代码说明】仔细地分析这个程序段，首先需要讲述迭代器的具体用法。

迭代器中有一个方法：boolean hasNext（），主要用来判断这个链表是否到了结尾，如果到了结尾则返回false，否则返回true。

还有一个方法：object next（），主要将链表的指针指向下一个元素与下下一个元素之间。有一点要强调，链表的指针不是指向某个元素，而是指向某个元素的前面，如图19.5所示。系统指针指向集合中第一个元素的前面。当指针指向链表的尾部时，再调用hasNext（）方法就会报错。

图　19.5　用迭代器来操作数据图

【运行效果】

王鹏

王浩

王杰

张杰

李杰

孙文杰

赵杰

删除某些同学的姓名后，还剩下哪些同学呢？

王鹏

王浩

王杰

张杰

李杰

孙文杰

上面的实例就是因为第一个迭代器在遍历后，已经到了链表的末尾，所以必须再建立一个新的迭代器，重新让指针停留在链表第一个元素的前面。

19.1.3　用程序代码段实现对链表的删除

【实例19.2】下面再演示一个复杂的实例，其流程如图19.6所示。

实例的详细步骤如下：

图　19.6　链表数据结构的操作

（1）设计学生类，代码如下所示。

01 ///创建一个学生类

02 ///name是学生　姓名

03 ///code是学生　学号

04 ///school是学生所在学校

05 ///age是　年龄

06 ///grade是年级

07 ///major是专业

08 ///sexy是　性别

09 ///set方法是设置器

10 ///get方法是访问器

11 ///tostring方法是让对象以字符串的形式输出的方法

12 class student

13 {

14 String name；

15 int age；

16 String sexy；

17 String code；

18 String school；

19 String grade；

20 String major；

21 String address；

22 student x；

23 student y；

24 student（String name）

25 {

26 this.name=name；

27 }

28 public void set（int age, String sexy, String code, String school, String grade, String

29 major, String address）

30 {

31 this.age=age；

32 this.sexy=sexy；

33 this.school=school；

34 this.grade=grade；

35 this.major=major；

36 this.code=code；

37 this.address=address；

38 }

39 public String getname（）

40 {

41 return name；

42 }

43 public String getcode（）

44 {

45 return name；

46 }

47 public String getsexy（）

48 {

49 return sexy；

50 }

51 public int getage（）

52 {

53 return age；

54 }

55 public String getschool（）

56 {

57 return school；

58 }

59 public String getmajor（）

60 {

61 return major；

62 }

63 public String getgrade（）

64 {

65 return grade；

66 }

67 public String getaddress（）

68 {

69 return address；

70 }

71 public String toString（）

72 {

73 String information="学生　姓名："+name+""+"　学号："+code+""+"　性别"+sexy+""+"　年龄：

74"+age+""+"所在学校："+school+""+"所学专业："+major+""+"　所在年级："+grade+""+"　家庭地址：

75"+address；

76 return information；

77 }

78 }

（2）设计一个主运行类，通过创建链表对象和迭代器对象，对元素进行操作。详细代码如下所示。

79 import java.util.LinkedList；

80 import java.util.Iterator；

81 ///主运行类

82 ///st1至st10是学生类的十个创建的对象

83 ///set方法给这十个对象赋值

84 ///list1是创建的链表对象

85 ///it是迭代器对象，通过它来指向链表中的元素

86 ///remove是链表中删除元素的方法，通过它来删除元素

87 public class file2

88 {

89 public static void main（String[]args）

90 {

91 student st1=new student（"王鹏"）；

92 student st2=new student（"王浩"）；

93 student st3=new student（"孙鹏"）；

94 student st4=new student（"孙文君"）；

95 student st5=new student（"谭妮"）；

96 student st6=new student（"赵志强"）；

97 student st7=new student（"王凯"）；

98 student st8=new student（"苏瑞"）；

99 student st9=new student（"张伟"）；

100 student st10=new student（"张杰"）；

101 st1.set（20，"男"，"10000"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

102 st2.set（22，"男"，"10001"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

103 st3.set（21，"男"，"10002"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

104 st4.set（19，"女"，"10003"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

105 st5.set（18，"女"，"10004"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

106 st6.set（24，"男"，"10005"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

107 st7.set（22，"男"，"10006"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

108 st8.set（29，"女"，"10007"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

109 st9.set（25，"女"，"10008"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

110 st10.set（28，"男"，"10009"，"重庆大学"，"大学三年级"，"计算机专业"，"重庆市沙坪坝区"）；

111 try

112 {

113 LinkedList list1=new LinkedList（）；

114 list1.add（st1）；

115 list1.add（st2）；

116 list1.add（st3）；

117 list1.add（st4）；

118 list1.add（st5）；

119 list1.add（st6）；

120 list1.add（st7）；

121 list1.add（st8）；

122 list1.add（st9）；

123 list1.add（st10）；

124 Iterator it=list1.iterator（）；

125 System.out.println（"以下就是所有的同学的信息："）；

126 while（it.hasNext（））

127 {

128 System.out.println（it.next（））；

129 }

130 System.out.println（"其中有几个同学已经转学了！"）；

131 System.out.println（"那么就从数据库中删除他："）；

132 list1.remove（）；

133 list1.remove（）；

134 list1.remove（）；

135 list1.remove（）；

136 list1.remove（）；

137 Iterator it1=list1.iterator（）；

138 while（it1.hasNext（））

139 {

140 System.out.println（it1.next（））；

141 }

142 }

143 catch（Exception e）{}

144 }

145 }

【代码说明】第91～110行创建学生对象，并设置它们的属性值。第113～123行创建链表list1，并将这10个学生对象添加到链表中。第124～129行和第137～141行都是使用迭代器输出链接元素。第132～136行是移除链表中的元素。

【运行效果】

以下就是所有的同学的信息：

学生　姓名：王鹏　学号：10000　性别男　年龄：20所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：王浩　学号：10001　性别男　年龄：22所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：孙鹏　学号：10002　性别男　年龄：21所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：孙文君　学号：10003　性别女　年龄：19所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：谭妮　学号：10004　性别女　年龄：18所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：赵志强　学号：10005　性别男　年龄：24所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：王凯　学号：10006　性别男　年龄：22所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：苏瑞　学号：10007　性别女　年龄：29所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：张伟　学号：10008　性别女　年龄：25所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：张杰　学号：10009　性别男　年龄：28所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

其中有几个同学已经转学了！那么就从数据库中删除他：

学生　姓名：赵志强　学号：10005　性别男　年龄：24所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：王凯　学号：10006　性别男　年龄：22所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：苏瑞　学号：10007　性别女　年龄：29所在学校：重庆大学所学专业：计算机专

业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：张伟　学号：10008　性别女　年龄：25所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

学生　姓名：张杰　学号：10009　性别男　年龄：28所在学校：重庆大学所学专业：计算机专业　所在年级：大学三年级　家庭地址：重庆市沙坪坝区

表19.1罗列的是链表中常用的方法，仅供参考。

19.2　数组列表类

数组列表类就是数组的一个引申类，它继承了数组的特点，并且又在数组基础上有了自己所独特的优势。本节介绍如何使用数据列表类。

19.2.1　什么是数组列表类

数组的容量一旦被初始化设定好，就不可以再更改。这对于正在运行的程序来说，是一种缺陷。在程序设计过程中，经常会遇到一些不确定的因素，导致无法确定一个数组的容量。为了解决这个问题，Java语言引进了数组列表。

数组列表就是一个可以动态变化容量的数组，其根据正在运行的程序的变化，随时改变数组的容量，以满足程序的需要。

ArrayList类是一个库类，定义在java.util包中。所以每次编写程序的时候，都必须引入这个包。数组列表中存放的是Object类型，因此在数组列表中存放的对象类型，以最原型的父类代替。如果要提取其中的元素，都要进行类型转换，将元素还原为它本身的类型。

由于ArrayList是一个可以自动伸缩的数组，所以可以无限制向数组中添加新元素。另外，因为添加的是Object类的子类类型，所以系统会自动完成子类向父类的转换。

如果能预计一个数组列表中需要存储多少元素，那么可以在填充之前，调用ensureCapacity（）方法，使得系统分配一个包含固定容量的内部数组。下面通过大量的实例来讲述这种数据结构。

19.2.2　通过实例熟悉数组列表如何存储数据

在举实例之前，先介绍ArrayList类的一些方法函数。

❑ArrayList（）：这是一个默认构造器，系统默认的初始化容量是10。

❑ArrayList（Collection）：构建一个数组列表的对象。这个数组列表中包含集合的所有元素。

❑ArrayList（int initCapcity）：构建一个指定初始化容量的数组列表对象。

❑void add（int index, Object element）：在指定位置插入指定的元素。

❑boolean add（object o）：在数组列表的末尾追加一个新元素。

❑boolean contains（object element）：测试指定的数组列表中是否包含有指定的元素element，如果包含该元素则返回true，反之返回false。

❑Int size（）：返回数组列表中包含元素的数量。

❑void set（int index, object obj）：设置数组列表指定位置元素的值，它会覆盖原来的内容。要强调的就是index必须在0～size（）-1之间。

❑Object get（int index）：得到指定位置存储的元素值。

❑Object remove（int index）：删除指定位置的元素。

【实例19.3】下面演示一个有关ArrayList的实例，通过这个实例能更好地理解ArrayList数据结构的用法。实例的流程如图19.7所示。

先设计一个教师类，然后在主运行类中创建教师类的对象和数组列表对象，并将教师类对象装载到数组列表对象中，再在主运行类中创建迭代器对象，用来操作数组列表中的元素。主要代码如下：

图　19.7　数组列表数据结构实例

（1）教师类的设计代码。

01 ///创建一个教师类

02 ///schoolname指学校　名称

03 ///classname指　班级名称

04 ///teachername指老师姓名

05 ///teachercode指老师工号

06 ///teachersexy指　性别

07 ///teacherbirthday指出　生年月

08 ///familyaddress指　家庭地址

09 ///set是设置器

10 ///get是访问器

11 ///tostring方法是使得对象能够以字符串形式输出的方法

12 class teacher{

13 private String schoolname；

14 private String classname；

15 private String teachername；

16 private String teachercode；

17 private String teachersexy；

18 private String teacherbirthday；

19 private String familyaddress；

20 public teacher（String teachername, String teachercode, String teachersexy, String

21 teacherbirthday）

22 {

23 this.teachername=teachername；

24 this.teachercode=teachercode；

25 this.teachersexy=teachersexy；

26 this.teacherbirthday=teacherbirthday；

27 }

28 public String getname（）

29 {

30 return teachername；

31 }

32 public String getcode（）

33 {

34 return teachercode；

35 }

36 public String getsexy（）

37 {

38 return teachersexy；

39 }

40 public String getbirthday（）

41 {

42 return teacherbirthday；

43 }

44 public void setschoolname（String schoolname）

45 {

46 this.schoolname=schoolname；

47 }

48 public void setclassname（String classname）

49 {

50 this.classname=classname；

51 }

52 53

public void setfamilyaddress（String familyaddress）

54 {

55 this.familyaddress=familyaddress；

56 }

57 public String getschoolname（）

58 {

59 return schoolname；

60 }

61 public String getclassname（）

62 {

63 return classname；

64 }

65 public String getfamilyaddress（）

66 {

67 return familyaddress；

68 }

69 public String toString（）

70 {

71 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

72"+teachername+""+"　教师工号：+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

73"+teacherbirthday+""+"　家庭地址："+familyaddress；

74 return infor；

75 }

76 }

（2）主运行类通过数组列表，实现数据的输出。

77 import java.util.ArrayList；

78 ///主运行类

79 ///al是创建的一个数组列表对象

80 ///t1至t6是针对教师类创建出来的六个对象

81 ///set设置器赋值给这六个对象

82 ///add方法是数组列表中添加元素的方法

83 ///remove方法是数组列表中删除元素的方法

84 public class test1

85 {

86 public static void main（String[]args）

87 {

88 ArrayList al=new ArrayList（）；

89 teacher t1=new teacher（"赵浩"，"1234001"，"男"，"1981-01-02"）；

90 teacher t2=new teacher（"黎平"，"1234002"，"男"，"1982-08-09"）；

91 teacher t3=new teacher（"王鹏"，"1234003"，"男"，"1982-11-22"）；

92 teacher t4=new teacher（"宋波"，"1234004"，"女"，"1982-11-02"）；

93 teacher t5=new teacher（"章伟"，"1234005"，"男"，"1980-01-12"）；

94 teacher t6=new teacher（"孙君"，"1234006"，"女"，"1981-09-22"）；

95 t1.setschoolname（"重庆大学"）；

96 t1.setclassname（"计算机三班"）；

97 t1.setfamilyaddress（"重庆沙坪坝"）；

98 t2.setschoolname（"重庆大学"）；

99 t2.setclassname（"计算机三班"）；

100 t2.setfamilyaddress（"重庆沙坪坝"）；

101 t3.setschoolname（"重庆大学"）；

102 t3.setclassname（"计算机三班"）；

103 t3.setfamilyaddress（"重庆沙坪坝"）；

104 t4.setschoolname（"重庆大学"）；

105 t4.setclassname（"计算机三班"）；

106 t4.setfamilyaddress（"重庆沙坪坝"）；

107 t5.setschoolname（"重庆大学"）；

108 t5.setclassname（"计算机三班"）；

109 t5.setfamilyaddress（"重庆沙坪坝"）；

110 t6.setschoolname（"重庆大学"）；

111 t6.setclassname（"计算机三班"）；

112 t6.setfamilyaddress（"重庆沙坪坝"）；

113 teacher t=new teacher（"孟凡良"，"34512"，"男"，"1954-09-23"）；

114 t.setschoolname（"成都科技大学"）；

115 t.setclassname（"机械系三班"）；

116 t.setfamilyaddress（"成都市区"）；

117 //通过设置器赋值给每个对象

118 al.add（t1）；//把每个对象添加到数组列表中去

119 al.add（t2）；

120 al.add（t3）；

121 al.add（t4）；

122 al.add（t5）；

123 al.add（t6）；

124 System.out.println（"这个小组有"+al.size（）+"个教师。"）；

125 //输出数组列表中的元素个数

126 for（int i=0；i＜al.size（）；i++）

127 {

128 System.out.println（（teacher）al.get（i））；//输出数组列表中的元素，以字符串形式

129 }

130 System.out.println（"对不起，系统出错了！有个教师信息错了，需要改正。"）；

131 al.set（5，t）；

132 System.out.println（"经过我们的审核后，教师信息如下："）；

133 for（int i=0；i＜al.size（）；i++）

134 {

135 System.out.println（（teacher）al.get（i））；

136 }

137 al.remove（2）；//删除数组列表中的元素

138 al.remove（4）；

139 System.out.println（"有两个教师辞职了，所以剩下教师信息为："）；

140 for（int i=0；i＜al.size（）；i++）

141 {

142 System.out.println（（teacher）al.get（i））；//输出剩下的数组列表中的元素

143 }

144 }

145 }

【代码说明】第12～76行创建一个teacher类，第88行创建一个ArrayList对象al。第118～123行将创建的teacher对象添加到al中。第133～136行和第140～143行都是输出al中的元素。

【运行效果】

这个小组有6个教师。

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234003　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234004　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234006　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝

对不起，系统出错了！有个教师信息错了，需要改正。经过我们的审核后，教师信息如下：

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234003　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234004　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

学校　名称：成都科技大学　班级名称：机械系三班　教师姓名：孟凡良　教师工号：34512　性别：男出　生年月：1954-09-23　家庭地址：成都市区

有两个教师辞职了，所以剩下教师信息为：

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234004　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

以上的实例演示ArrayList数组列表的用法。其实这个实例并不难，先构造几个类，然后为每个类构造对象，最后相互调用。

19.3　散列表

前面介绍了链表和数组列表的知识，本节将介绍另一种数据结构：散列表。散列表跟链表和数组列表比起来，有什么不同？有什么优点和缺点呢？

19.3.1　什么是散列表

在链表和数组列表中，要想查找某个特定的元素，就必须从头开始遍历。如果一个链表或者一个数组列表拥有的元素数量很大，那么就需要耗费大量的系统资源，去遍历整个数据结构。如何克服这个弊病？此时，就引进了另一个数据结构：散列表。

散列表通过“键-值对应”的形式存储元素。它是一个无序的数据结构，跟链表和数组列表不同，链表和数组列表都是有序的数据结构。

注意

有序数据结构的最大缺点就是无法控制元素出现的顺序，但同时正是因为它的无序，使得它可以快速查找特定的元素。

散列表通过一定的函数关系（散列函数）计算出对应的函数值，以这个值作为存储在散列表中的地址。从这个地址就可以直接获取这个元素，所以散列表对元素的查找非常高效。

当散列表中的元素存放的太满时，就必须再散列，将产生一个新的散列表。所有的元素放到新的散列表中，原先的散列表将被删除。在Java语言中，通过负载因子来决定何时对散列表进行散列，例如，如果负载因子是0.75，即散列表中已经有75%的位置被放满，那么将进行再散列。

负载因子越高（越接近1），内存使用率越高，元素的寻找时间越长。负载因子越低（越接近0），元素寻找的时间越短，内存浪费越多。散列表的默认负载因子是0.75。下一节将通过实例分析散列表的应用。

19.3.2　通过实例熟悉散列表如何存储数据

在举实例之前，先介绍散列表的构造器和常用的方法。只有了解这些方法，才能更好地应用散列表来存储数据。

散列表的构造器代码如下所示：

❑Hashtable（）

构建一个空的散列表，初始容量为11。负载因子为0.75。

❑Hashtable（int initalCapacity, float loadFactor）

指定初始化容量和负载因子，构造一个散列表。

❑Hashtable（Map t）

根据映像所包含的元素，构建一个散列表。

散列表的主要方法：

❑object put（object key, object vaule）：put方法是向一个散列表中添加元素。由于在散列表中是根据所添加元素的键值来定位元素，这个键值是唯一的。针对这个方法，务必要记住这个键值一定是对象型数据。

❑boolean containskey（object key）和boolean containvalue（object value）：这两个方法是测试散列表中是否包含指定的键值和值。

❑Object remove（object key）：根据指定的键值从散列表中删除对应键的元素。

❑Collection values（）：返回散列表中所包含元素的集合，是元素的集合，而不是key的集合。

【实例19.4】下面将针对上面的方法和构造器，演示一个有关散列表的实例。实例的流程如图19.8所示。

先设计一个教师类，然后，在主运行类中创建教师类的对象和散列表对象，并将教师类对象装载到散列表对象中，最后在主运行类中操作散列表中的元素。这里没有使用到迭代器，因为散列表中的元素不像链表或数组列表中一样有序。实例的主要代码如下：

图　19.8　散列表数据结构的实例

（1）教师类的设计代码。

01 ///创建一个教师类

02 ///schoolname指学校　名称

03 ///classname指　班级名称

04 ///teachername指老师姓名

05 ///teachercode指老师工号

06 ///teachersexy指　性别

07 ///teacherbirthday指出　生年月

08 ///familyaddress指　家庭地址

09 ///set是设置器

10 ///get是访问器

11 ///tostring方法是使得对象能够以字符串形式输出的方法

12 class teacher

13 {

14 private String schoolname；

15 private String classname；

16 private String teachername；

17 private String teachercode；

18 private String teachersexy；

19 private String teacherbirthday；

20 private String familyaddress；

21 public teacher（String teachername, String teachercode, String teachersexy, String

22 teacherbirthday）

23 {

24 this.teachername=teachername；

25 this.teachercode=teachercode；

26 this.teachersexy=teachersexy；

27 this.teacherbirthday=teacherbirthday；

28 }

29 public String getname（）

30 {

31 return teachername；

32 }

33 public String getcode（）

34 {

35 return teachercode；

36 }

37 public String getsexy（）

38 {

39 return teachersexy；

40 }

41 public String getbirthday（）

42 {

43 return teacherbirthday；

44 }

45 public void setschoolname（String schoolname）

46 {

47 this.schoolname=schoolname；

48 }

49 public void setclassname（String classname）

50 {

51 this.classname=classname；

52 }

53 54

public void setfamilyaddress（String familyaddress）

55 {

56 this.familyaddress=familyaddress；

57 }

58 public String getschoolname（）

59 {

60 return schoolname；

61 }

62 public String getclassname（）

63 {

64 return classname；

65 }

66 public String getfamilyaddress（）

67 {

68 return familyaddress；

69 }

70 public String toString（）

71 {

72 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

73"+teachername+""+"　教师工号："+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

74"+teacherbirthday+""+"　家庭地址："+familyaddress；

75 return infor；

76 }

77 }

（2）主运行类中，在不使用迭代器对象的情况下，操作散列表中数据。

78 import java.util.Hashtable；

79 ///主运行类

80 ///ht是创建的一个哈希表对象

81 ///t1至t6是针对教师类创建出来的六个对象

82 ///set设置器赋值给这六个对象

83 ///put方法是散列表中添加元素的方法

84 ///remove方法是散列表中删除元素的方法

85 public class test2

86 {

87 public static void main（String[]args）

88 {

89 Hashtable ht=new Hashtable（）；

90 teacher t1=new teacher（"赵浩"，"1234001"，"男"，"1981-01-02"）；

91 teacher t2=new teacher（"黎平"，"1234002"，"男"，"1982-08-09"）；

92 teacher t3=new teacher（"王鹏"，"1234003"，"男"，"1982-11-22"）；

93 teacher t4=new teacher（"宋波"，"1234004"，"女"，"1982-11-02"）；

94 teacher t5=new teacher（"章伟"，"1234005"，"男"，"1980-01-12"）；

95 teacher t6=new teacher（"孙君"，"1234006"，"女"，"1981-09-22"）；

96 t1.setschoolname（"重庆大学"）；

97 t1.setclassname（"计算机三班"）；

98 t1.setfamilyaddress（"重庆沙坪坝"）；

99 t2.setschoolname（"重庆大学"）；

100 t2.setclassname（"计算机三班"）；

101 t2.setfamilyaddress（"重庆沙坪坝"）；

102 t3.setschoolname（"重庆大学"）；

103 t3.setclassname（"计算机三班"）；

104 t3.setfamilyaddress（"重庆沙坪坝"）；

105 t4.setschoolname（"重庆大学"）；

106 t4.setclassname（"计算机三班"）；

107 t4.setfamilyaddress（"重庆沙坪坝"）；

108 t5.setschoolname（"重庆大学"）；

109 t5.setclassname（"计算机三班"）；

110 t5.setfamilyaddress（"重庆沙坪坝"）；

111 t6.setschoolname（"重庆大学"）；

112 t6.setclassname（"计算机三班"）；

113 t6.setfamilyaddress（"重庆沙坪坝"）；

114 teacher t=new teacher（"孟凡良"，"34512"，"男"，"1954-09-23"）；

115 t.setschoolname（"成都科技大学"）；

116 t.setclassname（"机械系三班"）；

117 t.setfamilyaddress（"成都市区"）；

118 ht.put（"zh"，t1）；//在哈希表中添加元素

119 ht.put（"lp"，t2）；

120 ht.put（"wp"，t3）；

121 ht.put（"sb"，t4）；

122 ht.put（"zw"，t5）；

123 ht.put（"sj"，t6）；

124 System.out.println（"这个小组有"+ht.size（）+"个教师。"）；

125 //输出哈希表中元素个数

126 System.out.println（ht.values（））；

127 System.out.println（"我需要查找一个教师的信息。"）；

128 //输出哈希表中的元素内容

129 if（ht.containsKey（"wh"））

130 {

131 System.out.println（"找到了此教师的信息，如下："）；

132 System.out.println（（teacher）ht.get（"ww"））；

133 }

134 else

135 {

136 System.out.println（"没有找到此教师的信息！"）；

137 }

138 ht.remove（"lp"）；//删除哈希表中的元素

139 ht.remove（"sj"）；

140 System.out.println（"由于有些教师离开了学校，经过我们的审核后，教师信息如下："）；

141 System.out.println（ht.values（））；//输出哈希表中剩下的元素内容

142 }

143 }

【代码说明】分析以上的程序段，发现其与前面介绍的链表和数组列表有些不同。首先不可以通过迭代器来对其进行访问，因为它是无序的。另外访问它时，最关键的就是键值。

散列表主要是通过键值进行一些散列值的计算，计算的结果作为这个对象的地址。所以对于拥有大量数据的数据库来说，使用散列表存储数据，比使用链表和数组列表会更方便。

注意　在散列表中，不允许有两个相同的元素，如有相同的元素，程序会将其作为一个元素来处理。

【运行效果】

这个小组有6个教师。

[学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234006　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏

教师工号：1234003　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝，学校名

称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波教师

工号：1234004　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝]我需要查找一个教师的信息。

没有找到此教师的信息！

由于有些教师离开了学校，经过我们的审核后，教师信息如下：

[学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234003　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩

教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝，学校名

称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234004　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝]

19.4　散列集

上一节介绍了散列表，本节将介绍散列集，两种数据结构只是一字之差。那么它们有什么区别？散列集又有什么可取之处呢？本节将通过实例来展示散列集的作用。

19.4.1　什么是散列集

散列表和散列集这两种数据结构，功能基本相同，不过它们实现的接口不一样。散列表实现的是Map（映像）接口，而散列集实现了Set接口。另外，散列表是线性同步，而散列集是非线性同步。

散列集既然实现了Set接口，也就实现了Collection接口，所以它是一个集合。仍然是add方法添加元素，接下来介绍散列集的构造器和常用方法函数。

散列集的构造器：

❑HashSet（）

创建一个空的散列集对象。

❑HashSet（collection c）

创建一个包含有collection集合中所有元素的散列集对象。

❑HashSet（int initialCapacity）

创建一个初始容量为initialCapacity的散列集对象。

❑HashSet（int initialCapacity, float loadFactor）

指定初始化容量和负载因子，构造一个散列集。它的初始容量是16，负载因子是0.75。

散列集的常用方法如下：

❑boolean add（o）：添加一个元素到散列集中。如果这个元素在散列集中不存在，就直接添加它，否则就返回false。

❑boolean remove（o）：删除一个元素。如果这个元素存在，就删除它，否则就返回false。

❑boolean isempty（）：判断集合是否为空。

当然散列集的方法不止这些，此处只是列出其中常用的。在后面的程序中，将会遇到一些，到时候再继续介绍。

19.4.2　通过实例熟悉散列集如何存储数据

【实例19.5】本节将通过一个实例，熟悉散列集的用法。

说明　散列集可以采用迭代器进行遍历。散列集和散列表一样，都不能拥有相同的元素。

散列集通过内部散列码计算元素存储地址，这一点与散列表一样，只不过散列集没有键值。下面针对散列集的数据结构举一个实例。实例的流程如图19.9所示。

先设计一个教师类，然后，在主运行类中创建教师类的对象和散列集对象，并将教师类对象装载到散列集对象中，再在主运行类中操作散列集中的元素。这里又开始使用到迭代器，因为散列集中的元素像链表或数组列表一样，都继承Collection类。实例步骤如下：

图　19.9　散列集数据结构的实例

（1）设计教师类，代码如下所示。

01 import java.util.HashSet；

02 import java.util.Iterator；

03 ///创建一个教师类

04 ///schoolname指学校　名称

05 ///classname指　班级名称

06 ///teachername指老师姓名

07 ///teachercode指老师工号

08 ///teachersexy指　性别

09 ///teacherbirthday指出　生年月

10 ///familyaddress指　家庭地址

11 ///set是设置器

12 ///get是访问器

13 ///tostring方法是使得对象能够以字符串形式输出的方法

14 15 class teacher

16 {

17 private String schoolname；

18 private String classname；

19 private String teachername；

20 private String teachercode；

21 private String teachersexy；

22 private String teacherbirthday；

23 private String familyaddress；

24 public teacher（String teachername, String teachercode, String teachersexy, String

25 teacherbirthday）

26 {

27 this.teachername=teachername；

28 this.teachercode=teachercode；

29 this.teachersexy=teachersexy；

30 this.teacherbirthday=teacherbirthday；

31 }

32 public String getname（）

33 {

34 return teachername；

35 }

36 public String getcode（）

37 {

38 return teachercode；

39 }

40 public String getsexy（）

41 {

42 return teachersexy；

43 }

44 public String getbirthday（）

45 {

46 return teacherbirthday；

47 }

48 public void setschoolname（String schoolname）

49 {

50 this.schoolname=schoolname；

51 }

52 public void setclassname（String classname）

53 {

54 this.classname=classname；

55 }

56 57

public void setfamilyaddress（String familyaddress）

58 {

59 this.familyaddress=familyaddress；

60 }

61 public String getschoolname（）

62 {

63 return schoolname；

64 }

65 public String getclassname（）

66 {

67 return classname；

68 }

69 public String getfamilyaddress（）

70 {

71 return familyaddress；

72 }

73 public String toString（）

74 {

75 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

76"+teachername+""+"　教师工号："+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

77"+teacherbirthday+""+"　家庭地址："+familyaddress；

78 return infor；

79 }

80 }

（2）在主运行类中，利用迭代器对象和散列集对象，实现对数据的操作。详细代码如下所示。

81 ///主运行类

82 ///hs是创建的一个散列集对象

83 ///t1至t6是针对教师类创建出来的六个对象

84 ///set设置器赋值给这六个对象

85 ///add方法是散列集中添加元素的方法

86 ///remove方法是散列集中删除元素的方法

87 ///it是迭代器对象，用来在数据结构中确定元素位置

88 public class test3

89 {

90 public static void main（String[]args）

91 {

92 HashSet hs=new HashSet（）；//创建一个散列集对象

93 teacher t1=new teacher（"赵浩"，"1234001"，"男"，"1981-01-02"）；

94 teacher t2=new teacher（"黎平"，"1234002"，"男"，"1982-08-09"）；

95 teacher t3=new teacher（"王鹏"，"1234003"，"男"，"1982-11-22"）；

96 teacher t4=new teacher（"宋波"，"1234004"，"女"，"1982-11-02"）；

97 teacher t5=new teacher（"章伟"，"1234005"，"男"，"1980-01-12"）；

98 teacher t6=new teacher（"孙君"，"1234006"，"女"，"1981-09-22"）；

99 t1.setschoolname（"重庆大学"）；

100 t1.setclassname（"计算机三班"）；

101 t1.setfamilyaddress（"重庆沙坪坝"）；

102 t2.setschoolname（"重庆大学"）；

103 t2.setclassname（"计算机三班"）；

104 t2.setfamilyaddress（"重庆沙坪坝"）；

105 t3.setschoolname（"重庆大学"）；

106 t3.setclassname（"计算机三班"）；

107 t3.setfamilyaddress（"重庆沙坪坝"）；

108 t4.setschoolname（"重庆大学"）；

109 t4.setclassname（"计算机三班"）；

110 t4.setfamilyaddress（"重庆沙坪坝"）；

111 t5.setschoolname（"重庆大学"）；

112 t5.setclassname（"计算机三班"）；

113 t5.setfamilyaddress（"重庆沙坪坝"）；

114 t6.setschoolname（"重庆大学"）；

115 t6.setclassname（"计算机三班"）；

116 t6.setfamilyaddress（"重庆沙坪坝"）；

117 //通过设置器赋值给每个对象

118 hs.add（t1）；//往散列集中添加元素

119 hs.add（t2）；

120 hs.add（t3）；

121 hs.add（t4）；

122 hs.add（t5）；

123 hs.add（t6）；

124 System.out.println（"这个小组有"+hs.size（）+"个教师。"）；

125 //输出散列集中的元素个数

126 Iterator it=hs.iterator（）；//新建一个迭代器对象

127 while（it.hasNext（））

128 {

129 System.out.println（it.next（））；

130 }

131 hs.remove（t3）；//删除散列集中元素

132 hs.remove（t4）；

133 System.out.println（"由于有些教师离开了学校，经过我们的审核后，教师信息如下："）；

134 Iterator it1=hs.iterator（）；//新建一个迭代器对象

135 while（it1.hasNext（））

136 {

137 System.out.println（it1.next（））；

138 }

139 System.out.println（"这些教师今天都离职了，所有教师信息都可以删除了！"）；

140 hs.remove（t1）；//删除散列集中的元素

141 hs.remove（t2）；

142 hs.remove（t5）；

143 hs.remove（t6）；

144 if（hs.isEmpty（））

145 {

146 System.out.println（"这里把教师信息都删除了。"）；

147 }

148 else

149 {

150 System.out.println（"系统报错了！"）；

151 }

152 }

153 }

【代码说明】第92行创建一个散列集对象，第118～123行往散列集中添加教师对象。第126～130行使用迭代器输出对象。

【运行效果】

这个小组有6个教师。

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234003　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234006　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234004　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝

由于有些教师离开了学校，经过我们的审核后，教师信息如下：

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234005　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234006　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234001　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

这些教师今天都离职了，所有教师信息都可以删除了！这里把教师信息都删除了。

从上面的例子可以看出，散列集同前面的链表和数组列表，在编写代码时几乎一样。那么它们有什么区别呢？

【实例19.6】下面通过一个程序实例查看它们的区别。

01 import java.util.HashSet；

02 import java.util.LinkedList；

03 04 public class test4

05 {

06 public static void main（String[]args）

07 {

08 long time=0；

09 HashSet hs=new HashSet（）；

10 LinkedList ll=new LinkedList（）；

11 long starttime=System.currentTimeMillis（）；

12 for（int i=0；i＜10000；i++）

13 {

14 hs.add（new Integer（i））；

15 }

16 System.out.println（System.currentTimeMillis（）-starttime）；

17 for（int i=0；i＜10000；i++）

18 {

19 ll.add（new Integer（i））；

20 }

21 System.out.println（System.currentTimeMillis（）-starttime）；

22 }

23 }

【代码说明】从结果可以看出，使用散列集进行数据处理，比使用链表进行数据处理花费的时间更短。这样可以节约系统资源。在这个程序中，需要说明的是System.currentTimeMillis（）方法，它显示系统当前的时间。使用System.currentTimeMillis（）-starttime计算两个时间段的时间差，即处理数据所需要花费的时间。

【运行效果】

15 31

【实例19.7】下面再举一个实例，说明如何对数组列表与散列集进行比较。

01 import java.util.HashSet；

02 import java.util.ArrayList；

03 04 public class test5

05 {

06 public static void main（String[]args）

07 {

08 long time=0；

09 HashSet hs=new HashSet（）；

10 ArrayList al=new ArrayList（）；

11 long starttime=System.currentTimeMillis（）；

12 for（int i=0；i＜10000；i++）

13 {

14 hs.add（new Integer（i））；

15 }

16 System.out.println（System.currentTimeMillis（）-starttime）；

17 for（int i=0；i＜10000；i++）

18 {

19 al.add（new Integer（i））；

20 }

21 System.out.println（System.currentTimeMillis（）-starttime）；

22 }

23 }

【代码说明】从结果可以看出，使用散列集进行数据处理，系统花费的时间更短，比使用数组列表进行数据处理速度更快，这样可以节约系统资源。所以在处理大量数据的时候，通常使用散列集。

【运行效果】

16 18

19.5　树集

前面讲过链表、数组列表、散列表、散列集，本节将讲述另一种数据结构：树集。树集这种数据结构用在什么情况下？它与前面几种数据结构有什么区别？本节通过实例讲述树集这种数据结构，并逐个解答上面的问题。

19.5.1　什么是树集

树集有点像散列集，但与散列集不同，树集是一种有序的数据结构。树集可以使用任何次序在这种数据结构中添加元素。当遍历时，元素出现次序是有序的。不过这种次序由系统自动完成。

那么系统如何对其进行自动排序？系统在每添加一个元素时，将按照字典顺序比较相应的字符串，按升序的方式来完成排序。

注意

使用树集这种数据结构的对象，必须要实现Comparable接口，只有实现了这种接口的对象，才能调用Comparable接口中的CompareTo（）方法来排序。

还是先看看树集的构造器和常用的方法函数。树集的构造器方法如下所示：❑TreeSet（）

创建一个空的树集对象。

❑TreeSet（collection c）

创建一个树集对象，并且通过指定的比较器给它的元素排序。

❑TreeSet（sortedSet element）

创建一个树集，并把有序集element的所有元素插入树中。它使用与有序集element相同的元素比较器。

树集的常用方法函数如下所示：

❑boolean add（object o）：添加一个对象元素到树集中去。

❑boolean remove（object o）：从树集中删除一个对象元素。

读者在学习树集的同时，可以自行将树集与前面所学习的几种数据结构进行比较，看看它们之间的相同点和不同点。这样，才能知道在什么场合下，使用哪种数据结构最好。

19.5.2　通过实例熟悉树集如何存储数据

【实例19.8】本节将通过实例，熟悉树集在数据存储方面的作用。下面看一个有关树集的实例。实例流程如图19.10所示。

先设计一个教师类，然后，在主运行类中创建教师类的对象和树集对象，并将教师类对象装载到树集对象中，再在主运行类中操作树集中的元素。这里还是使用迭代器，因为树集中的元素像链表或数组列表一样，都继承Collection类。实例步骤如下：

图　19.10　树集数据结构实例

（1）设计教师类，代码如下所示。

01 import java.util.TreeSet；

02 import java.util.Iterator；

03 ///创建一个教师类并且实现了Comparable接口

04 ///schoolname指学校　名称

05 ///classname指　班级名称

06 ///teachername指老师姓名

07 ///teachercode指老师工号

08 ///teachersexy指　性别

09 ///teacherbirthday指出　生年月

10 ///familyaddress指　家庭地址

11 ///set是设置器

12 ///get是访问器

13 ///compareTo方法是用来按照某个字段进行排序的方法

14 class teacher implements Comparable

15 {

16 private String schoolname；

17 private String classname；

18 private String teachername；

19 private int teachercode；

20 private String teachersexy；

21 private String teacherbirthday；

22 private String familyaddress；

23 public teacher（String teachername, int teachercode, String teachersexy, String

24 teacherbirthday）

25 {

26 this.teachername=teachername；

27 this.teachercode=teachercode；

28 this.teachersexy=teachersexy；

29 this.teacherbirthday=teacherbirthday；

30 }

31 public int compareTo（Object o）

32 {

33 teacher t=（teacher）o；

34 return（t.teachercode-teachercode）；

35 36

}

37 public String getname（）

38 {

39 return teachername；

40 }

41 public int getcode（）

42 {

43 return teachercode；

44 }

45 public String getsexy（）

46 {

47 return teachersexy；

48 }

49 public String getbirthday（）

50 {

51 return teacherbirthday；

52 }

53 public void setschoolname（String schoolname）

54 {

55 this.schoolname=schoolname；

56 }

57 public void setclassname（String classname）

58 {

59 this.classname=classname；

60 }

61 62

public void setfamilyaddress（String familyaddress）

63 {

64 this.familyaddress=familyaddress；

65 }

66 public String getschoolname（）

67 {

68 return schoolname；

69 }

70 public String getclassname（）

71 {

72 return classname；

73 }

74 public String getfamilyaddress（）

75 {

76 return familyaddress；

77 }

78 public String toString（）

79 {

80 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

81"+teachername+""+"　教师工号："+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

82"+teacherbirthday+""+"　家庭地址："+familyaddress；

83 return infor；

84 }

85 }

（2）在主运行类中，通过迭代器对象和树集对象来操作数据。代码如下所示。

86 ///主运行类

87 ///ts是创建的一个树集对象

88 ///t1至t6是针对教师类创建出来的六个对象

89 ///set设置器赋值给这六个对象

90 ///add方法是树集中添加元素的方法

91 ///remove方法是树集中删除元素的方法

92 ///it是迭代器对象，用来在数据结构中确定元素位置

93 public class test6

94 {

95 public static void main（String[]args）

96 {

97 TreeSet ts=new TreeSet（）；//创建一个树集对象

98 teacher t1=new teacher（"赵浩"，1234011，"男"，"1981-01-02"）；

99 teacher t2=new teacher（"黎平"，1234002，"男"，"1982-08-09"）；

100 teacher t3=new teacher（"王鹏"，1234033，"男"，"1982-11-22"）；

101 teacher t4=new teacher（"宋波"，1234014，"女"，"1982-11-02"）；

102 teacher t5=new teacher（"章伟"，1234025，"男"，"1980-01-12"）；

103 teacher t6=new teacher（"孙君"，1234016，"女"，"1981-09-22"）；

104 t1.setschoolname（"重庆大学"）；

105 t1.setclassname（"计算机三班"）；

106 t1.setfamilyaddress（"重庆沙坪坝"）；

107 t2.setschoolname（"重庆大学"）；

108 t2.setclassname（"计算机三班"）；

109 t2.setfamilyaddress（"重庆沙坪坝"）；

110 t3.setschoolname（"重庆大学"）；

111 t3.setclassname（"计算机三班"）；

112 t3.setfamilyaddress（"重庆沙坪坝"）；

113 t4.setschoolname（"重庆大学"）；

114 t4.setclassname（"计算机三班"）；

115 t4.setfamilyaddress（"重庆沙坪坝"）；

116 t5.setschoolname（"重庆大学"）；

117 t5.setclassname（"计算机三班"）；

118 t5.setfamilyaddress（"重庆沙坪坝"）；

119 t6.setschoolname（"重庆大学"）；

120 t6.setclassname（"计算机三班"）；

121 t6.setfamilyaddress（"重庆沙坪坝"）；

122 //通过设置器赋值给对象

123 ts.add（t1）；//将对象添加到树集中

124 ts.add（t2）；

125 ts.add（t3）；

126 ts.add（t4）；

127 ts.add（t5）；

128 ts.add（t6）；

129 System.out.println（"这个小组有"+ts.size（）+"个教师。"）；

130 //输出树集中元素个数

131 Iterator it=ts.iterator（）；//创建一个迭代器

132 while（it.hasNext（））

133 {

134 System.out.println（it.next（））；

135 }

136 ts.remove（t3）；//删除树集中元素

137 ts.remove（t4）；

138 System.out.println（"由于有些教师离开了学校，经过我们的审核后，教师信息如下："）；

139 Iterator it1=ts.iterator（）；

140 while（it1.hasNext（））

141 {

142 System.out.println（it1.next（））；

143 }

144 System.out.println（"这些教师今天都离职了，所有教师信息都可以删除了！"）；

145 ts.remove（t1）；//删除树集中元素

146 ts.remove（t2）；

147 ts.remove（t5）；

148 ts.remove（t6）；

149 if（ts.isEmpty（））

150 {

151 System.out.println（"这里把教师信息都删除了。"）；

152 }

153 else

154 {

155 System.out.println（"系统报错了！"）；

156 }

157 }

158 }

【代码说明】第97行创建一个数集对象ts，第98～121行创建教师对象，并设置教师对象的属性值。第123～128行将教师对象添加到树集中。第131～135行创建迭代器并输出树集中的元素。

【运行效果】

这个小组有6个教师。

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234033　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234025　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234016　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234014　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234011　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

由于有些教师离开了学校，经过我们的审核后，教师信息如下：

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234025　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234016　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234011　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝

这些教师今天都离职了，所有教师信息都可以删除了！这里把教师信息都删除了。

从这个程序的运行结果来看，输出的数据都经过排序，这也是树集的一大特点。

19.6　映像

集合是一种可以快速找到已经存在的元素的数据结构。但如果数据库中拥有大量的数据，一般不用集合，因为它会耗费系统大量的资源和时间去遍历整个数据结构。

前面讲过散列表，其实散列表就是一种映像。下面将详细讲述映像及散列映像、树映像，并通过实例让读者对映像的概念有更深的认识。

19.6.1　什么是映像

映像是一种采用“键值-值”的对应方式存储的数据结构形式。在映像中，除了散列表，还有树映像和散列映像。由于映像不能使用迭代器，所以映像拥有get方法函数。无论树映像，还是散列映像或散列表，它们的使用方法都差不多。下面通过实例熟悉树映像。

19.6.2　通过实例熟悉映像如何存储数据

【实例19.9】本小节将通过实例，学习如何通过映像存储和操作数据。树映像实例的流程如图19.11所示。

程序先设计一个教师类，然后，在主运行类中创建教师类的对象和树映像对象。并将教师类对象装载到树映像对象中，再在主运行类中操作树映像中的元素。实例创建步骤如下：

图　19.11　树映像数据结构实例

（1）设计教师类，代码如下所示。

01 ///创建一个教师类对象并且实现了Comparable接口，用来排序

02 ///通过设置器设置了学校　名称、　班级名称、　教师姓名、工号、　性别、出　生年月、　家庭地址

03 class teacher

04 {

05 private String schoolname；

06 private String classname；

07 private String teachername；

08 private int teachercode；

09 private String teachersexy；

10 private String teacherbirthday；

11 private String familyaddress；

12 public teacher（String teachername, int teachercode, String teachersexy, String

13 teacherbirthday）

14 {

15 this.teachername=teachername；

16 this.teachercode=teachercode；

17 this.teachersexy=teachersexy；

18 this.teacherbirthday=teacherbirthday；

19 }

20 21

public String getname（）

22 {

23 return teachername；

24 }

25 public int getcode（）

26 {

27 return teachercode；

28 }

29 public String getsexy（）

30 {

31 return teachersexy；

32 }

33 public String getbirthday（）

34 {

35 return teacherbirthday；

36 }

37 public void setschoolname（String schoolname）

38 {

39 this.schoolname=schoolname；

40 }

41 public void setclassname（String classname）

42 {

43 this.classname=classname；

44 }

45 46

public void setfamilyaddress（String familyaddress）

47 {

48 this.familyaddress=familyaddress；

49 }

50 public String getschoolname（）

51 {

52 return schoolname；

53 }

54 public String getclassname（）

55 {

56 return classname；

57 }

58 public String getfamilyaddress（）

59 {

60 return familyaddress；

61 }

62 public String toString（）

63 {

64 String infor="学校　名称："+schoolname+""+"　班级名称："+classname+""+"　教师姓名：

65"+teachername+""+"　教师工号："+teachercode+""+"　性别："+teachersexy+""+"出　生年月：

66"+teacherbirthday+""+"　家庭地址："+familyaddress；

67 return infor；

68 }

69 }

（2）在主运行类中，通过树映像操作数据，详细代码如下所示。

70 import java.util.TreeMap；

71 ///主运行类

72 ///tm是创建的一个树映像对象

73 ///t1至t6是针对教师类创建出来的六个对象

74 ///set设置器赋值给这六个对象

75 ///put方法是树映像中添加元素的方法

76 ///remove方法是树映像中删除元素的方法

77 public class test7

78 {

79 public static void main（String[]args）

80 {

81 TreeMap tm=new TreeMap（）；//创建一个树映像对象

82 teacher t1=new teacher（"赵浩"，1234011，"男"，"1981-01-02"）；

83 teacher t2=new teacher（"黎平"，1234002，"男"，"1982-08-09"）；

84 teacher t3=new teacher（"王鹏"，1234033，"男"，"1982-11-22"）；

85 teacher t4=new teacher（"宋波"，1234014，"女"，"1982-11-02"）；

86 teacher t5=new teacher（"章伟"，1234025，"男"，"1980-01-12"）；

87 teacher t6=new teacher（"孙君"，1234016，"女"，"1981-09-22"）；

88 t1.setschoolname（"重庆大学"）；

89 t1.setclassname（"计算机三班"）；

90 t1.setfamilyaddress（"重庆沙坪坝"）；

91 t2.setschoolname（"重庆大学"）；

92 t2.setclassname（"计算机三班"）；

93 t2.setfamilyaddress（"重庆沙坪坝"）；

94 t3.setschoolname（"重庆大学"）；

95 t3.setclassname（"计算机三班"）；

96 t3.setfamilyaddress（"重庆沙坪坝"）；

97 t4.setschoolname（"重庆大学"）；

98 t4.setclassname（"计算机三班"）；

99 t4.setfamilyaddress（"重庆沙坪坝"）；

100 t5.setschoolname（"重庆大学"）；

101 t5.setclassname（"计算机三班"）；

102 t5.setfamilyaddress（"重庆沙坪坝"）；

103 t6.setschoolname（"重庆大学"）；

104 t6.setclassname（"计算机三班"）；

105 t6.setfamilyaddress（"重庆沙坪坝"）；

106 //通过设置器赋值给对象

107 tm.put（"zh"，t1）；//添加对象到树映像

108 tm.put（"lp"，t2）；

109 tm.put（"wp"，t3）；

110 tm.put（"sb"，t4）；

111 tm.put（"zw"，t5）；

112 tm.put（"sj"，t6）；

113 System.out.println（"这个小组有"+tm.size（）+"个教师。"）；

114 System.out.println（tm.values（））；

115 tm.remove（"lp"）；//删除树映像中的元素

116 tm.remove（"sb"）；

117 System.out.println（"帮我查找一下有没有孙君这个教师"）；

118 if（tm.containsKey（"sj"））

119 {

120 System.out.println（"这个教师是存在的，他的信息如下："）；

121 System.out.println（（teacher）tm.get（"sj"））；

122 }

123 else

124 {

125 System.out.println（"这里没有这个教师。"）；

126 }

127 System.out.println（"由于有些教师离开了学校，经过我们的审核后，教师信息如下："）；

128 System.out.println（tm.values（））；

129 System.out.println（"这些教师今天都离开了，所有教师信息都可以删除了！"）；

130 tm.remove（"zh"）；

131 tm.remove（"sj"）；

132 tm.remove（"zw"）；

133 tm.remove（"wp"）；

134 if（tm.isEmpty（））

135 {

136 System.out.println（"这里把教师信息都删除了。"）；

137 }

138 else

139 {

140 System.out.println（"系统报错了！"）；

141 }

142 }

143 }

【代码说明】第81行创建树映像对象tm，第107～112行添加对象到tm中，第113行中的tm.size（）方法获取树映像对象的元素个数。第114行tm.values（）方法输出树映像对象的所有元素。这里没有使用到迭代器，因为树映像中的元素是无序的。

【运行效果】

这个小组有6个教师。

[学校　名称：重庆大学　班级名称：计算机三班　教师姓名：黎平　教师工号：1234002　性别：男出　生年月：1982-08-09　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：宋波　教师工号：1234014　性别：女出　生年月：1982-11-02　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君

教师工号：1234016　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝，学校名

称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234033　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩　教师工号：1234011　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：章伟教师

工号：1234025　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝]帮我查找一下有没有孙君这个教师

这个教师是存在的，他的信息如下：

学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234016　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝

由于有些教师离开了学校，经过我们的审核后，教师信息如下：

[学校　名称：重庆大学　班级名称：计算机三班　教师姓名：孙君　教师工号：1234016　性别：女出　生年月：1981-09-22　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：王鹏　教师工号：1234033　性别：男出　生年月：1982-11-22　家庭地址：重庆沙坪坝，学校　名称：重庆大学　班级名称：计算机三班　教师姓名：赵浩

教师工号：1234011　性别：男出　生年月：1981-01-02　家庭地址：重庆沙坪坝，学校名

称：重庆大学　班级名称：计算机三班　教师姓名：章伟　教师工号：1234025　性别：男出　生年月：1980-01-12　家庭地址：重庆沙坪坝]

这些教师今天都离开了，所有教师信息都可以删除了！这里把教师信息都删除了。

在运行结果中，输出的所有数据都是按姓名排序的数据。

注意　树映像的排序是按照关键字来排序的，与值无关，这一点与树集不一样。

19.7　枚举

枚举Enumeration接口有两个方法：一个是hasElements，另一个是nextElement。这两个方法的用法与迭代器中的hasnext和next的用法基本一样。在实际开发中，能遇到枚举的机会不是很多。

19.8　属性集

属性集（property set）是一种特殊类型的映像结构，Properties继承自Hashtable。它有三个特点：❑关键字和值都是字符串。

❑属性集可以用文件保存，也可以从文件中装入。

❑有两个属性列表：主属性列表和默认属性列表。

实现属性集的类就是properties，它位于包java.util中，实现了Map接口。在这里将简单地介绍有关属性集的一些基本知识。

【实例19.10】java.util.Properties类用来在一个文件中存储键—值对，其中键和值用等号分隔，如以下代码所示。

name=wp

code=2001

将以上代码装载到Properties对象后（可以暂时保存到D盘下的sample.properties文件，如果没有，新建一个），就可以找到两个键（name和code）和两个值（name的值是wp, code的值是2001）。代码如下所示。

01 import java.util.*；

02 import java.io.*；

03 public class LoadSample{

04 public static void main（String args[]）throws Exception{

05 Properties prop=new Properties（）；

06 FileInputStream fis=

07 new FileInputStream（"sample.properties"）；

08 prop.load（fis）；

09 prop.list（System.out）；

10 System.out.println（"\nThe foo property："+

11 prop.getProperty（"foo"））；

12 }

13 }

【代码说明】上述代码显示如何装载属性文件，并列出其当前的一组键和值。只需传递这个文件的InputStream给load（）方法，就会将每一个键-值对添加到Properties实例中。然后用list（）列出所有属性，或者用getProperty（）获取单独的属性。

【运行效果】

--listing properties--

name=wp

code=2001

The foo property：null

注意

在list（）方法的输出中，键-值对的顺序与它们在输入文件中的顺序不一样，Properties类在散列表中储存一组键-值对，所以不能保证顺序一致。

19.9　常见疑难解答

19.9.1　哪些是线程安全的数据结构

答：在集合框架中，有些类是线程安全的，这些都在JDK1.1中。在JDK1.2之后，就出现了许多非线程安全的类。

下面是这些线程安全的同步的类。

❑Vector：比ArrayList多了个同步化机制（线程安全）。

❑Statck：堆栈类，先进后出。

❑Hashtable：比HashMap多了个线程安全。

❑Enumeration：枚举，相当于迭代器。

除了这些之外，其他的都是非线程安全的类和接口。线程安全类的方法是同步的，每次只能访问一个，是重量级对象，效率较低。对于非线程安全的类和接口，在多线程中需要程序员自己处理线程安全问题。只要在编译时提示：

Note：test7.java uses unchecked or unsafe operations

Note：Recompile with-Xlint：unchecked for details.

这些提示就说明使用了非线程安全的类或接口。

19.9.2　Vector是什么样的数据结构

答：没有讲述它的原因，是因为目前使用它的频率不高。一般用数组列表代替它，因为它们的使用方法几乎一样，唯独不同的就在线程安全方面。数组列表是非线程安全类，在实现线程编程时，要自己处理安全问题，而Vector则是线程安全类，自动会处理安全问题。

Vector类提供实现可增长数组的功能，随着更多元素加入其中，数组变得更大。在删除一些元素之后，数组变小。Vector有三个构造函数：

public Vector（int initialCapacity, int capacityIncrement）

public Vector（int initialCapacity）

public Vector（）

Vector运行时创建一个初始的存储容量initialCapacity，存储容量是以capacityIncrement变量定义的增量增长。初始的存储容量和capacityIncrement可以在Vector的构造函数中定义。第2个构造函数只创建初始存储容量。第3个构造函数既不指定初始的存储容量，也不指定capacityIncrement。Vector类提供的访问方法，支持类似数组的运算，也支持与Vector大小相关的运算。类似数组的运算允许在向量中增加、删除和插入元素。

下面对向量增、删、插的功能进行举例描述。增加向量的代码：

addElement（Object obj）

把组件加到向量尾部，同时大小加1，向量容量比以前大1。

insertElementAt（Object obj, int index）

把组件加到指定索引处，此后的内容向后移动1个单位。

setElementAt（Object obj, int index）

把组件加到指定索引处，此处的内容被代替。

removeElement（Object obj）///把向量中含有本组件内容移走

removeAllElements（）///把向量中所有组件移走，向量大小为0

19.10　小结

如果读者在大学时学过数据结构，则对本章的知识不会陌生。不管使用什么开发语言，这些结构的功能和技术要点都不会变，变的只是代码的一种形式。通过本章的学习，读者应该可以开发简单的链表、散列表等。数据结构知识会显得有些抽象，希望读者不要在心理上充满畏惧，要亲自动手练习本章中的实例。

19.11　习题

1.ArrayList与Vector的区别有哪些？

2.实现枚举的接口是什么？

3.如何对树集进行自动排序？

第20章　XML基础

本章重点讲述XML的基本概念，以及其在实际开发工作中的作用。XML对于Java的Web开发起着举足轻重的作用，其代替了HTML在网页设计中的地位。XML是一种数据描述语言，它的出现打破了呆板的网页设计模式。

本章重点：

❑认识XML文档。

❑学习设计一个好的XML文档。

❑XML处理器和解析器。

❑XML中DTD的结构。

20.1　XML的产生及与HTML的比较

XML的全称“Extensible Markup Language”，是可扩展的标记语言。为什么说它是可扩展的？它又扩展谁？其实它由标准通用标记语言（Standard Generalized Markup Language, SGML）扩展而来，XML是SGML的简化版本。

SGML功能非常强大，可以定义标记语言的元语言。由于它非常复杂，不适合在Web上应用，所以使用XML代替SGML在互联网上的应用。

HTML是一种标记语言，在HTML里面有很多标签，而XML允许创建自己的标签。其实XML并不是标记语言，它是用来创建标记语言的元语言。XML不是HTML的替代品，也不是HTML的升级，它只是HTML的补充，为HTML扩展更多的功能。

XML用来定义类似HTML的标记语言，然后再用这个标记来显示信息。那么使用XML有什么优点呢？

现在网络应用越来越广泛，仅靠HTML单一的文件类型，处理千变万化的文档和数据已经力不从心。HTML本身的语法十分不严密，XML作为Web 2.0必需的数据传输和交互工具，其语法非常严密。XML使得在网络上使用SGML语言变得更加简单和直接。

【实例20.1】为了让读者能够区别XML和HTML，本节将分别举例进行比较。下面是一段HTML代码。

01＜html＞

02 ＜head＞

03 ＜title＞静夜思＜/title＞

04 ＜/head＞

05 ＜body＞

06 ＜h2＞＜Font size=3 Color="read"＞静夜思＜/Font＞＜/h2＞

07 ＜b＞作者：李白＜/b＞＜br＞

08 ＜hr Color="blue"＞

09 ＜p＞

10 ＜b＞＜i＞＜Font size=3 Color="green"＞床前明月光，疑是地上霜。＜br＞

11 举头望明月，低头思故乡。＜/Font＞＜/i＞＜/b＞

12 ＜/body＞

13＜/html＞

【代码说明】＜html＞是所有HTML文档的最外层标记，必须有一个对应的＜/html＞标记表示结束。＜head＞＜/head＞表示文件头，＜body＞＜/body＞表示内容体。

【运行效果】运行结果如图20.1所示。

【实例20.2】下面再看使用XML文档的实例。

01＜?xml version="1.0"encoding="gb2312"?＞

02＜人物＞

03令狐冲

04＜！--这是一个令无数少女为之倾倒的偶像--＞

05 ＜籍贯＞华山＜/籍贯＞

06 ＜　性别＞男＜/　性别＞

07 ＜　年龄＞24＜/　年龄＞

08＜/人物＞

【代码说明】第1行是XML文档的标记，后面会详细介绍。第4行是XML文档的注释。

【运行效果】在浏览器中打开XML文档，如图20.2所示。

图　20.1　一个HTML实例

图　20.2　XML文档在浏览器中

通过上面两个实例可以看出，HTML必须按照规定的标记书写，不能够自己创造，而在XML中可以自己创造标记。至于XML到底按照何种格式进行书写，后面将详细地介绍。

20.2　XML的编辑工具

XML文档的编辑可以使用UltraEdit编辑器，根据UltraEdit的特点和用法，可以知道用其编辑XML文档非常方便。编辑界面如图20.3所示。

图　20.3　XML编辑工具界面

编辑好XML文档后，通过按钮可以看到XML文档的网页形式，如图20.4中鼠标所指的按钮。

图　20.4　一个编辑好的XML文档

20.3　XML文档

本节介绍如何编写XML文档，以及XML文档的结构形式。并且通过实例一步步讲解，让读者能真正掌握XML的精髓。

XML文档由实体（存储单元）组成，用来定义标记的标记语言，可用来定义自己所需要的标记集。HTML只支持英文标记，而XML可以支持多种语言的标记。

现在把整个文档进行划分，可以看出一个文档（包括HTML文档）总共分为三个部分，也可以说是三大要素，即文档数据、文档结构、文档样式。

下面以HTML为例分析文档的三要素。

＜table border=2 align=center＞……//文档样式

＜caption＞清华大学菜价表＜/caption＞

……

＜tr＞

＜td align=center＞鱼香肉丝＜/td＞

＜td align=center＞3.50＜/td＞……//文档数据

……

＜/tr＞

＜/table＞……//文档结构

为了能够让XML的文档更具可读性，XML文档采取了数据与文档样式分离的原则。XML文档只提供数据，而XSL包括数据样式，文档的结构则使用DTD。DTD在后面的章节中详细介绍，而XSL则留待有兴趣的读者自行查阅资料。

20.3.1　XML的声明

XML声明就是宣布此文档是XML文档。那么如何进行声明呢？声明的语法如下所示。

＜?xml版本信息（编码信息）（文档独立性信息）?＞

下面分别解释每个元素的含义。

❑版本信息：是指XML目前使用的是1.0版还是1.1版，一般用1.0版本。

❑编码信息：就是指文档使用的是何种语言。如果使用中文，则可以通过下列方式表示。encoding=“gb2312”：如果不设定编码信息，默认使用英文。

❑文档独立性信息：文档独立指当前文档是否依赖外部文档。如果依赖可以通过下列方式表示：

standalone=“yes”，如果不独立，将其设置为“no”。

通过以上的介绍，下面举一个实例演示XML文档的声明。

＜?xml version=1.0 encoding="gb2312"standalone="yes"?＞

20.3.2　文档类型的声明

在说到文档类型声明之前，先简单提一个概念：DTD。DTD是一种保证XML文档格式正确的有效方法，可以通过比较XML和DTD文档来检查文档是否符合规范，元素和标签使用是否正确，这就是DTD的作用。

文档类型的声明共有两种：

❑一种是在其他文件中定义文档类型：＜！DocTYPE MYDOC SYSTEM"mydoc.dtd"＞

此句指出该文档类型名为MYDOC，当此文档结构不是很清楚时，必须要查询“mydoc.dtd”文档才能知道。

❑另一种就是在XML文档中直接定义文档类型：

＜！DocTYPE BIRDS[＜！ELEMENT AUCTIONBLOCK（ITEM, BIRDS）＞]＞

上述代码像前面的实例一样，直接在XML文档中定义文档结构。

20.3.3　元素

元素的构成有很多个部分，下面逐个讲述。在讲述每个部分时，都会配有实例，帮助读者更好地理解这些概念。

1.非空元素构成

元素=起始标记+元素内容+结束标记，如下所示：

＜BID ID=200138＞

＜PRICE＞60000＜/PRICE＞

＜TIME＞3：00：00＜/TIME＞

＜/BID＞

2.空元素构成

❑无元素内容的就称为空元素。

❑空元素=“＜”+元素名称（属性名值对）+“/＞”。

❑空元素的属性值必须指明，不能缺少。

上面提到了“＜”和“/＞”，这是标记。

3.标记

标记分为起始标记和结束标记。

❑起始标记=“＜”+标记名称（属性名值对）+“＞”。

❑结束标记=“＜/”+标记名称+“＞”

4.元素的内容

元素内容的构成方式：元素内容=（子元素|字符数据|字符数据段|引用|处理指令|注释）

下面将分别描述。

（1）子元素：它也是元素，被嵌套在上层元素之内，只不过是一个相对概念。如果在这个子元素内再嵌套另外一个元素，那么这个子元素同时也是父元素。

（2）字符数据：文本的内容没有标记，也没有实体的引用，这样就称为字符数据。除了字符数据外，其余的内容都使用了标记或实体引用符号。字符数据不能包含“^”、“＜”、“＆”、“]]”。在预定义实体时，需要使用实体引用，预定义实体不能在XML文档中直接使用某些字符数据，而必须通过规定的实体引用来代替（以“＆”开头，以“；”结尾）。举个有关字符数据的实例。

＜BID＞

＜PRICE＞6000＜/PRICE＞

＜/BID＞

这里的6000就是字符数据。

（3）字符数据段：这段字符数据只是出现在文档内部，但在输出时，XML程序不对其作任何操作，它将原封不动地被输出。字符数据段的用处：由于在某些应用环境下，希望嵌入一些HTML文本。程序员不希望XML对其进行修改，只想传递这些文本和脚本。其表示方法为：

字符数据段="＜！[CDATA["+字符数据+"]]＞"

下面是有关字符数据段的实例。

＜?xml version=1.0 encoding="gb2312"？＞

＜root＞

＜！[CDATA[＜html＞＜head＞＜/head＞＜body＞我是一个优秀的程序员＜/body＞＜/html＞

]]＞

＜/root＞

“＜html＞＜head＞＜/head＞＜body＞我是一个优秀的程序员＜/body＞＜/html＞”就是一个字符数据段。

（4）引用：引用是用符号代表指定的内容，或者用符号代表不能直接使用的其他符号，其分为实体引用和字符引用。实体引用就是用一个符号代替一段很长的内容，解析时又会被还原成原来的字符实体。而字符引用就是将一些字符及键盘上无法输入的字符，用字符引用。其引用的格式如下：

❑实体引用：“＆”+实体引用名+“；”，例如＆company；。

❑字符引用：“＆#”+字符的十六进制或十进制的ASCII值+“；”，例如＆#174；。

下面是有关引用的实例。

＜?xml version=1.0 encoding="gb2312"?＞

＜！DOCTYPE就业信息[＜！ENTITY company"上海嘉鉴信息技术有限公司"＞]＞

＜就业信息＞

＜公司名称＞＆company＜/公司名称＞

＜/就业信息＞

上面的实例中使用了一个声明，其格式如下：

＜！ENTITY实体引用名需被引用的实体＞

20.3.4　注释

注释比较简单，它在代码中只不过是对代码的一种解释。但是希望读者能养成一种习惯，就是写代码时，要记住在代码旁边给予一定的注释。这样写出来的代码段就比较容易被他人阅读。注释的格式是：

＜！--注释的内容--＞

注意　在注释的内容中，不能出现“--＞”这样的符号，避免编译代码时出错。

20.3.5　处理指令

处理指令允许文档中包含由应用程序来处理的指令，即在XML文档中，可能会包含一些非XML格式的数据，这些数据XML无法处理，此时就可以使用处理指令，调用其他的程序来处理这些数据。例如要处理Execl/CSS。

处理指令的语法是以“＜?”开头，以“?＞”结尾。下面是一个常见的处理指令的实例。

＜?xml-stylesheet href="hello.css"type="text/css"?＞

此处的“xml-stylesheet”就是处理指令，它调用Execl或者使用CSS来处理表格数据，因为这些数据XML无法处理。

20.3.6　空白处理

在XML文档中，可以在元素中使用一个特殊的属性xml：space，来通知应用程序保留此元素的空白，在使用时必须要进行声明。xml：space属性必须被声明为Enumerated（枚举）类型，它的值必须是“default”和“preserve”两者之一，或者两者都取。

❑default：表示对此元素使用应用程序的默认空白处理模式。

❑preserve：指示应用程序保留所有的空白。

如果一个元素使用了xml：space属性，将适用于该元素内容中的所有元素，除非被另一个xml：space属性的实例所覆盖。

20.3.7　行尾处理

在XML空白字符中，有两个标准的ASCII码行尾控制字符，一个是回车（#xA），一个是换行（#xD）。在XML处理器解析前，要将所有的两字符序列#xD#Xa和单独的字符都转换成#xA。

20.3.8　语言标识

在文档处理中，标识出其内容所使用的自然或人工语言，非常有用。可以在文档中插入一个属性xml：lang，来指定文档所使用的语言。

xml：lang属性一旦设定，将适合于它所在元素中的所有属性及元素的内容，除非被元素内容中另一个元素的xml：lang属性所覆盖。

20.3.9　一个简单的有关XML的实例

【实例20.3】由于还没有学习DTD文档，所以下面的实例非常简单。只是帮助读者熟悉XML文档的一些基础格式。

01＜?xml version="1.0"encoding="gb2312"?＞

02＜留言本＞

03 ＜留言记录＞

04 ＜留言者姓名＞KAI＜/留言者姓名＞

05 ＜电子邮件＞kai@hostx.org＜/电子邮件＞

06 ＜网址＞http：//www.17xml.com＜/网址＞

07 ＜留言内容＞我们相距万里，但是我们心心相映，咔咔：_）＜/留言内容＞

08 ＜/留言记录＞

09＜/留言本＞

【代码说明】第1行是XML文档的声明。第2～9行是自定义的标签。

【运行效果】运行结果如图20.5所示。

图　20.5　一个XML实例的运行结果

20.4　格式良好的XML

什么才是格式良好的XML文档呢？如果一个XML文档有且只有一个根元素，符合XML元素的嵌套规则，满足XML规范中所定义的所有格式正确性的约束，每一个已分析实体都格式正确，则称之为格式良好的XML文档。

那么需要满足什么样的格式约束才算格式良好的XML？下面给出了详细的说明。

（1）文档必须从XML声明开始

❑XML声明必须位于该文件的最开始位置。

❑XML必须紧跟在“＜?”之后，中间不能有空格等字符。

（2）唯一的根元素

❑根元素必须唯一。

❑根元素嵌套其他所有的后代元素。

❑根元素必须有起始标记和结束标记。

❑XML文档中的其他非元素节点不一定包含在根元素中。

（3）标记必须是闭合的

起始标记必须有一个相应的结束标记与之对应。

（4）空标记的约定

❑空标记必须用“/＞”来结束。

❑空标记可以带有属性。

（5）层层嵌套

❑子元素必须嵌套在父元素内，不能互相交错。

❑同层元素必须互相并列，不能互相嵌套。

（6）区分大小写：起始标记与结束标记大小写必须要分清。

（7）属性设定：属性赋值时都必须使用引号。

（8）特殊的字符表示法：预定义实体用实体引用方式。

如果能够满足以上所列出的内容，可以说这篇XML算是格式良好的XML。其实以上列出来的所有项，都是有关一些语法方面的注意点。XML对语法的要求比较严格，希望读者能够按照相关的语法规则编写XML文档。

20.5　什么是DTD

DTD（Documnet Type Definition）就是一个规范XML文档结构的文档，其实，它起着一个规范XML中数据的数据结构的作用。下面将详细讲述如何编写DTD文档。

20.5.1　在XML文档中加入DTD

DTD的定义是什么呢？通过以下的说明，学习DTD的规范。

（1）文档类型的定义：定义允许什么或不允许什么在文档中出现。预先规定文档中元素的结构、属性类型和实体引用等，可直接在文档中定义DTD，或引用外部DTD。DTD不一定是必须的，只是需要的时候可以定义DTD。

（2）DTD的调用：合法的XML文档必须遵循某一类文档的结构声明，一旦声明就会与此类文档相应结构关联起来。DTD的调用就是指定文档使用什么样的DTD，它出现在文档的XML声明后，基本元素之前，DTD可以包含在XML文档中，也可以在外部定义，然后在XML文档中直接引用。

【实例20.4】下面先看一个DTD文档在XML内部的实例。

01＜?xml version="1.0"encoding="gb2312"standalone="yes"?＞

02＜！Docutype就业信息[＜！ELEMENT就业信息（#PCDATA）＞]＞

03＜就业信息＞

04暂无信息

05＜/就业信息＞

【代码说明】此处的＜！Docutype就业信息[＜！ELEMENT就业信息（#PCDATA）＞]＞就是一个内部定义的DTD文件。

【实例20.5】下面再看一个调用外部DTD文档的实例。

文件名：Jobinfo.dtd

01＜！ELEMENT Jobinfo（#PCDATA）＞

02＜?xml version="1.0"encoding="gb2312"standalone="yes"?＞

03＜！DOCTYPE Jobinfo SYSTEM"Jobinfo.dtd"＞

04＜就业信息＞

05暂无信息

06＜/就业信息＞

【代码说明】此处的＜！DOCTYPE Jobinfo SYSTEM“Jobinfo.dtd”＞就是一个外部的DTD文档，在XML文档中再调用这个DTD文档。

20.5.2　DTD的结构

DTD的结构包括元素类型的声明、属性表的声明、记号的声明。下面将逐一介绍。

1.元素类型的声明

元素类型的声明定义元素的名称和元素的内容，定义文档中允许什么元素和不允许什么元素、元素出现的次序。如果没有明确允许，那就是禁止。元素类型声明的语法如下所示：

＜！ELEMENT元素（元素内容说明）＞

其中元素内容说明有五种形式，分别是EMPTY、ANY、混合内容、#PCDATA、元素内容。下面将分别讲述它们的使用场所。

❑EMPTY：空元素的内容。

❑ANY：任意元素的内容。

❑混合内容：可以出现字符数据，子元素或者两者的混合体。

❑#PCDATA：只能出现字符数据。

❑元素内容：只能出现子元素，不能直接出现字符数据。

（1）#PCDATA的内容

只有字符数据才能作为元素的内容，其声明语法：

＜！ELEMENT元素名（#PCDATA）＞

【实例20.6】下面是一个有关它的实例。

01＜?xml version="1.0"encoding="gb2312"standalone="yes"?＞

02＜！Docutype就业信息[＜！ELEMENT就业信息（#PCDATA）＞]＞

03＜就业信息＞

04暂无信息

05＜/就业信息＞

如果上面的实例修改如下就是错误的。

01＜?xml version="1.0"encoding="gb2312"standalone="yes"?＞

02＜！Docutype就业信息[＜！ELEMENT就业信息（#PCDATA）＞]＞

03＜就业信息＞

04 ＜公司名称＞

05 暂无信息

06 ＜/公司名称＞

07＜/就业信息＞

（2）元素内容

在元素内部只能出现指定子元素，不允许出现字符或混合内容。看一个实例，代码如下。

＜！ELEMENT就业信息（公司名，公司简介，公司地址，邮编，电话，传真，E-MAIL，联系人，招聘职位*）＞

分析：在实例的最后有一个“*”，这个符号代表什么呢？

❑“*”：代表元素可以出现零次或多次。

❑“+”：代表元素可以出现一次或多次。

❑“?”：代表可以出现零次或一次。

没有任何标识符的元素只能出现一次。下面再举一个有关元素内容的实例。

＜！ELEMENT招聘职位（职位名，月薪？，招聘人数，招聘要求*，工作地点，发布日期，负责人+）＞

分析：职位名只出现一次，招聘要求可出现零次或多次，月薪可出现零次或一次，负责人必然出现一次以上，招聘人数只出现一次。

（3）EMPTY内容

常包含一些非字符数据，如图像数据、音乐数据等。其声明语法如下：

＜！ELEMENT元素名EMPTY＞

（4）ANY内容

所有可能的元素以及可析字符数据，都可以是所声明的元素的内容。

＜！ELEMENT元素名ANY＞

（5）混合内容

元素既可以含有可析字符数据，也可以含有标记文本。其声明语法如下：

＜！ELEMENT元素名（#PCDATA|子元素1|子元素2|子元素3）＞

2.属性表的声明

属性由“=”分隔开的属性名值对构成，它只能出现在元素标记的内部，其包含了有关元素的信息。属性表的声明语法如下：

＜！ATTLIST对应的元素名属性名属性取值类型　属性默认值＞

在XML文档中：

＜月薪　货币单位="人民币"＞6000＜/月薪＞

对应的DTD文档中：

＜！ELEMENT月薪（#PCDATA）＞

＜！ATTLIST月薪货币单位CDATA"人民币"＞

（1）属性的取值类型

下面将学习属性取值类型。属性取值类型有很多种，如下所示：

CDATA：可以解析的字符数据。

Enumerated：枚举型，取值必须从中选出。

ENTITY：在DTD中声明的实体。

ENTITIES：在DTD中声明的若干个实体。

ID：取值在文档中必须是唯一的。

IDREF：文档中某个元素的ID属性值。

NMTOKEN：任意不含空格的XML名称。

NMTOKENS：由空格分开的多个XML名称。

NOTATION：在DTD中声明的记号名。

（2）默认声明

默认声明可以有四种默认设置：

#REQUIRED：说明必须为元素提供该属性。

#IMPLIED：说明元素可以包含该属性，也可以不包含该属性。

#FIXED+默认值：说明一个固定的属性默认值，文档的编写者不能修改该属性的值。

默认值：同上一种几乎差不多。只不过该属性值可以改变。

3.记号的声明

所谓记号声明，就是用记号标识非XML格式数据，应用程序就利用记号来识别和处理数据，记号用于描述非XML格式的数据。在DTD中，NOTATION声明为特殊的数据类型制作记号。记号声明的语法：

＜！NOTATION记号名　SYSTEM"外部标识"＞

例如：

＜！NOTATION TIFF SYSTEM"Image/tiff"＞

20.5.3　几个简单有关DTD的实例

【实例20.7】下面将举几个简单的实例，让读者进一步熟悉DTD文档的编写。

01＜?xml version="1.0"encoding="gb2312"?＞

02＜！DOCTYPE company[

03＜！ELEMENT company（employee|manager）*＞///声明一个元素及其子元素

04＜！ELEMENT employee（name）＞///声明一个元素及其子元素

05＜！ELEMENT manager EMPTY＞///声明了manager的类型

06＜！ELEMENT name（#PCDATA）＞///声明它是一个字符串类型

07＜！ATTLIST employee sn ID#REQUIRED＞///规定它必须被提供属性值

08＜！ATTLIST manager mgrid IDREF#REQUIRED＞///规定它必须被提供属性值，但必须是其中一

09 ///个元素的属性值

10]＞

11＜company＞

12 ＜employee sn="300212"＞

13 ＜name＞赵浩＜/name＞

14 ＜/employee＞

15 ＜employee sn="300213"＞

16 ＜name＞李丽＜/name＞

17 ＜/employee＞

18 ＜manager mgrid="300212"/＞

19＜/company＞

【代码说明】这个实例其实很简单，在company元素中有两个职员。每个职员拥有一个唯一的sn号。而职员中一个是经理。

【运行效果】浏览器中的效果如图20.6所示。

图　20.6　浏览器中的效果

【实例20.8】下面举个有关借书的实例。其代码如下：

01＜?xml version="1.0"encoding="gb2312"?＞

02＜！DOCTYPE library[

03＜！ELEMENT library（books|records）＞///声明一个元素及其子元素

04＜！ELEMENT books（book+）＞///声明一个元素及其子元素

05＜！ELEMENT book（title）＞///声明一个元素及其子元素

06＜！ELEMENT title（#PCDATA）＞///声明它是一个字符串类型

07＜！ELEMENT records（item*）＞///声明一个元素及其子元素

08＜！ELEMENT item（date, person）＞///声明一个元素及其子元素

09＜！ELEMENT date（#PCDATA）＞///声明它是一个字符串类型

10＜！ELEMENT person EMPTY＞

11＜！ATTLIST book bookid ID#REQUIRED＞///规定它必须被提供属性值

12＜！ATTLIST person name CDATA#REQUIRED＞

13＜！ATTLIST person borrowed IDREFS#REQUIRED＞///规定它必须被提供属性值，但必须是其中

14 ///一个元素的属性值

15]＞

16＜library＞

17 ＜books＞

18 ＜book bookid="1-1-2"＞

19 ＜title＞XML详解＜/title＞

20 ＜/book＞

21 ＜book bookid="1-1-3"＞

22 ＜title＞Java程序设计入门＜/title＞

23 ＜/book＞

24 ＜book bookid="1-1-1"＞

25 ＜title＞C程序设计入门＜/title＞

26 ＜/book＞

27 ＜/books＞

28 ＜records＞

29 ＜item＞

30 ＜date＞2007-9-3＜/date＞

31 ＜person name="anson"borrowed="1-1-2 1-1-1"/＞

32 ＜/item＞

33 ＜item＞

34 ＜date＞2007-3-3＜/date＞

35 ＜person name="John"borrowed="1-1-2 1-1-3"/＞

36 ＜/item＞

37 ＜/records＞

38＜/library＞

【代码说明】上面的XML文档列出了某个人借书的一些情况。其实熟悉HTML的读者，可以发现XML文档与HTML最大的不同点在于：XML可以自定义标记，而HTML不能自定义标记，其标记都是事先规定好的，并且不容更改。

【运行效果】图20.7所示的效果直接在浏览器中查看。

图　20.7　浏览器中的效果

20.6　有效的XML

一个遵守XML语法，并且遵守相应DTD文件约束的XML文档，称为有效的XML文档。将XML文档和它的DTD文件进行比较分析，看是否符合DTD规则的过程称为验证，这个过程通过一个支持有效性检查的XML处理器来完成。

20.7　XML处理器/解析器

什么是XML处理器/解析器？它主要用于分析处理XML文档，验证XML文档是否符合XML的规范。合乎规范的XML处理器可以分为两类：进行验证的和不进行验证的。

进行验证的处理器，在检查文档是否符合“格式良好的”基本要求的基础上，进一步结合DTD，检查文档是否符合DTD中对文档结构的规定，判断这个文档是否有效。处理器必须读取和处理整个DTD和文档中引用的外部已分析实体，并报告出文档与DTD声明相冲突的地方，以及不满足DTD有效性约束的地方。

不进行验证的处理器，只负责检查XML文档所引用的内部DTD子集是否满足“格式良好的”语法规定。这类处理器不对外部DTD文档进行分析，不检查XML文档的有效性，只对内部DTD文档进行处理。

支持对XML文档进行有效性检查的XML处理器，有以下两个：

❑Apache的Xerces。

❑Oracle XML Parser。

本章只是对XML作了一个很简单的介绍，其实它的知识远远不止这些，有兴趣的读者可以查阅其他相关的书籍。

20.8　常见疑难解答

20.8.1　XML与HTML的区别

答：它们之间有着很多共同点和不同点，下面具体分析。

❑XML不是要替换HTML，实际上XML可以视作是对HTML的补充。XML和HTML的目标不同：HTML的设计目标是显示数据并集中于数据外观，而XML的设计目标是描述数据并集中于数据的内容。

❑与HTML相似，XML不进行任何操作，虽然XML标记可用于描述订单项的结构，但它不包含可用于发送或处理该订单，以及确保按该订单交货的任何代码。程序中必须编写代码，来实际对XML格式数据的操作。

❑与HTML不同，XML标记由架构或文档的作者定义，并且是无限制的。HTML标记则是预定义的，HTML作者只能使用当前HTML标准所支持的标记。

20.8.2　XML有哪些显示数据的方式

答：有多种方式可用于显示（或提供）XML数据，数据绑定的机制可与样式表一起使用，以可视形式展示XML数据，并添加交互性。以下是显示XML的几种方法：

❑XSLT（可扩展样式表语言）

❑CSS（级联样式表）

❑IE浏览器

20.9　小结

XML是一种数据表示的格式，所有语言都增加了与XML的交互技术。虽然有人说XML将来会取代数据库，但从目前来看，大型数据库的存在还是必须的。因为用XML描述复杂关系型数据还是有一定困难的。希望通过本章的学习，读者能认识XML文档，了解它的DTD结构，会写简单的XML片段。

20.10　习题

1.代表XML文档的声明语句是______。

2.XML中的注释标记是______。

3.写一个简单的书籍卖场的XML文档。

4.简述XML和DTD的关系。

第五篇　Java高级应用篇

第21章　JDBC及其应用

什么是JDBC？它与数据库有什么联系？它与Java开发有什么联系？在现实的开发工作中，JDBC起着什么作用？带着这些疑问，开始本章的学习之旅。本章将会通过大量的实例，让读者能够很熟练地掌握JDBC。为了能够更清晰地理解其概念，本章还采取了大量的截图，让读者通过截图，清楚地看到实际的操作。同时希望读者能够一边学习、一边练习。

本章重点：

❑数据库的基础知识。

❑SQL数据库查询语言。

❑JDBC的基础编程知识。

❑事务的处理和预查询。

21.1　数据库基础知识

JDBC是连接数据库和Java应用程序的一个纽带，下面先介绍有关数据库的知识。

数据库在应用程序中占有相当重要的地位，几乎所有的系统都必须要有数据。数据库发展到现在已经相当成熟了，由原来的Sybase数据库，发展到现在的SQL Server、Oracal等高级数据库。

21.1.1　什么是数据库

数据库是依照某种数据模型组织起来，并存放二级存储器中的数据集合。这种数据集合具有如下特点：

❑尽可能不重复。

❑以最优方式为某个特定组织提供多种应用服务。

❑其数据结构独立于使用它的应用程序。

❑对数据的增删改和检索由统一软件进行管理和控制。

从发展的历史看，数据库是数据管理的高级阶段，它是由文件管理系统发展起来的，数据库的基本结构分三个层次，反映了观察数据库的三种不同角度。

❑物理数据层。它是数据库的最内层，是物理存储设备上实际存储的数据的集合，这些数据是原始数据，同时也是用户加工的对象，由内部模式描述的指令操作处理的位串、字符和字组成。

❑概念数据层。它是数据库的中间一层，是数据库的整体逻辑表示，指出了每个数据的逻辑定义及数据间的逻辑联系，是存储记录的集合。它所涉及的是数据库所有对象的逻辑关系，不是它们的物理情况，而是数据库管理员概念下的数据库。

❑逻辑数据层。它是用户所看到和使用的数据库，表示了一个或一些特定用户使用的数据集合，即逻辑记录的集合。

数据库不同层次之间的联系是通过映射进行转换的。数据库主要有以下特点：

❑实现数据共享。

❑数据的独立性。

❑数据一致性和可维护性，以确保数据的安全性和可靠性。

21.1.2　数据库的分类及功能

数据库系统一般基于某种数据模型，可以分为层次型、网状型、关系型、面向对象型等。

（1）层次型数据库：是一组通过链接而互相联系在一起的记录。树结构图是层次数据库的模式。层次模型的特点是记录之间的联系是通过指针实现，表示的是对象的联系。其缺点是无法反映多对象的联系，并且由于层次顺序的严格和复杂，导致数据的查询和更新操作复杂，因此应用程序的编写也比较复杂。层次型数据库模型如图21.1所示。

图　21.1　层次型数据库模型

（2）网状数据库：是基于网络模型建立的数据库。网络模型，是使用网格结构表示实体类型、实体间联系的数据模型。网状模型的特点是记录之间的联系通过指针实现，多对多的联系容易实现。缺点是编写应用程序比较复杂，程序员必须熟悉数据库的逻辑结构，如图21.2所示。

（3）关系数据库：是基于关系模型建立的数据库。关系模型由一系列表格组成，用表格来表达数据集，用外键（关系）来表达数据集之间的联系。在后面会详细讲述它。

图　21.2　网状数据库模型

（4）面向对象型数据库：是建立在面向对象模型基础之上。面向对象模型中最基本的概念是对象和类。对象是现实世界中实体的模型化，共享同一属性集和方法集的所有对象构成一个类。类可以有嵌套结构。系统中的所有类组成一个有根、有向无环图，称为类层次。

21.1.3　关系数据库的设计

1.数据库设计过程

数据库技术是信息资源管理最有效的手段。数据库设计对于一个给定的应用环境，通过构造最优的数据库模式，建立数据库及其应用系统，通过在数据库中有效存储、操作数据，从而满足用户信息要求和处理要求。

数据库设计中需求分析阶段综合各个用户的应用需求（现实世界的需求）。在概念设计阶段形成独立于机器特点和独立于各个DBMS产品的概念模式（信息世界模型）。在逻辑设计阶段可以将需求关系转换成具体的数据库产品支持的数据模型如关系模型，从而形成数据库逻辑模式，然后根据用户处理的要求、安全性的考虑，在基本表的基础上再建立必要的视图（VIEW）形成数据的外模式。在物理设计阶段根据DBMS特点和处理的需要，进行物理存储安排、设计索引、形成数据库内模式。

2.需求分析阶段

❑需求收集和分析，结果得到数据字典描述的数据需求（和数据流图描述的处理需求）。

❑需求分析的重点是调查、收集与分析用户在数据管理中的信息要求、处理要求、安全性与完整性要求。

❑需求分析的方法：调查组织机构情况、调查各部门的业务活动情况、协助用户明确对新系统的各种要求、确定新系统的边界。

21.1.4　数据库设计技巧

本节介绍一些数据库设计方面的技巧，主要包括4点，如下所示。

1.设计数据库之前（需求分析阶段）

❑理解客户需求，询问用户如何看待未来需求变化，让客户解释其需求，而且随着开发的继续，还要经常询问客户，从而保证其需求仍然在开发的目的之中。

❑了解企业业务，从而可以在以后的开发阶段节约大量的时间。

❑定义标准的对象命名规范。

❑数据库各种对象的命名必须规范。

2.表设计原则

表是由行和列组成，每一行称为一条记录，列称为一个字段。数据的标准化有助于消除数据库中的数据冗余。标准化有好几种形式，但Third Normal Form（3NF）通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说，遵守3NF标准的数据库的表设计原则如下。

❑某个表只包括其本身基本的属性。

❑当不是它们本身所具有的属性时需进行分解。

❑表之间的关系通过外键相连接。

❑它有一组表专门存放通过键连接起来的关联数据。

3.键选择原则

键就是表中的字段。什么是主键呢？主键被挑选出来作为表的唯一标识。那什么是外键呢？外键就是两张表通过一个键产生联系，而这个键又在两张表中，其中在一张表中是主键，那么这个键在另一张表中，就被称为外键。

键设计4原则：

❑为关联字段创建外键

❑所有的键都必须唯一

❑避免使用复合键

❑外键总是关联唯一的键字段

4.索引使用原则

索引与书籍中的索引类似，在一本书中，可以通过索引快速查找所需内容，而无须阅读整本书。在数据库中，索引使DB（DataBase）程序无须对整个表进行扫描，就可以找到所需数据。

❑索引是从数据库中获取数据的最高效方式之一，95%的数据库性能问题都可以采用索引技术得到解决。

❑逻辑主键使用唯一的成组索引，对系统键（作为存储过程）采用唯一的非成组索引，对任何外键列采用非成组索引。考虑数据库的空间有多大，表如何进行访问，还有这些访问是否主要用作读写。

❑大多数数据库都索引自动创建的主键字段，但是可别忘了索引外键，它们也是经常使用的键，例如运行查询显示主表和所有关联表的某条记录，就用得上它。

❑不要索引memo/note字段，不要索引大型字段（有很多字符），这样作会让索引占用太多的存储空间。

❑不要索引常用的小型表。

❑不要为小型数据表设置任何键，假如它们经常有插入和删除操作就更不能这样做，对这些插入和删除操作的索引维护，可能比扫描表空间消耗更多的时间。

数据库的一些基本知识就介绍这么多，从下一节开始，将讲述什么是JDBC，以及如何使用JDBC将Java程序与数据库相连接。

21.2　JDBC的概念

JDBC是一个有关数据库连接的工具。本节介绍JDBC的概念和如何使用JDBC将Java程序与数据库进行连接。

21.2.1　什么是JDBC

JDBC就是Java DataBase Connectivity，即Java数据库连接。它主要完成下面几个任务。

❑与数据库建立一个连接。

❑向数据库发送SQL语句。

❑处理数据库返回的结果。

❑使用Java语言和JDBC工具包开发程序，是独立于平台和厂商的。

JDBC就是将Java语言编写出来的程序与数据库相连接。接下来，将详细讲述如何利用JDBC为程序连接数据库。

21.2.2　JDBC驱动程序的分类

JDBC被称为数据源驱动，其具备什么特点？又与ODBC有什么关系或区别呢？

1.JDBC和ODBC的区别

在JDBC的概念中，涉及另一个概念：ODBC，所以，在讲述JDBC的驱动程序分类之前，首先介绍什么是ODBC。ODBC是指Open DataBase Connectivity，即开放数据库互连，它建立了一组规范，并且提供了一组对数据库访问的标准API（应用程序编程接口），这些API利用SQL来完成其大部分任务。ODBC也提供了对SQL的支持。

JDBC（Java DataBase Connectivity）是Java与数据库的接口规范。它由Java语言编写的类和接口组成，大致分为两类：

❑针对Java程序员的JDBC API。

❑针对数据库开发商的底层的JDBC driver API。

2.JDBC的结构和种类

JDBC驱动程序由实施了这些接口的类组成，JDBC的总体结构有4个组件：应用程序、驱动程序管理器、驱动程序和数据源。

（1）第一类是将JDBC转换成ODBC驱动器，依靠ODBC驱动器和数据库通信。在这种方式下，ODBC驱动程序和桥代码必须出现在用户的每台机器中，这种类型的驱动程序最适合于企业网（这种网络上客户机的安装不是主要问题），或者是用Java编写的三层结构的应用程序服务器代码。

（2）第二类是本地API一部分用Java来编写的驱动程序。这种类型的驱动程序把客户机API上的JDBC调用转换为Oracle、Sybase、Informix、DB2或其他DBMS的调用。

注意　像桥驱动程序一样，这种类型的驱动程序要求将某些二进制代码加载到每台客户机上。

（3）第三类是JDBC网络纯Java驱动程序。这种驱动程序将JDBC转换为与DBMS无关的网络协议，这种协议又被某个服务器转换为一种DBMS协议。这种网络服务器中间件能够将它的纯Java客户机连接到多种不同的数据库上，所用的具体协议取决于提供者。

通常，这是最为灵活的JDBC驱动程序。所有这种解决方案的提供者，都提供适合于Intranet用的产品。为了使这些产品支持Internet，它们必须处理Web所提出的安全性、通过防火墙的访问等额外要求，几家提供者正将JDBC驱动程序加到他们现有的数据库中间件产品中。

（4）第四类是本地协议纯Java驱动程序：这种类型的驱动程序将JDBC调用直接转换为DBMS所使用的网络协议，这将允许从客户机机器上直接调用DBMS服务器，是Intranet访问的一个很实用的解决方法。由于许多这样的协议都是专用的，因此数据库提供者自己将是主要来源。

第（3）、（4）类驱动程序将成为从JDBC访问数据库的首选方法。第（1）、（2）类驱动程序在直接的纯Java驱动程序还没有上市前，将会作为过渡方案来使用。对第（1）、（2）类驱动程序可能会有一些变种，这些变种要求有连接器，但通常这些是更加不可取的解决方案。第（3）、（4）类驱动程序提供了Java的所有优点，包括自动安装。

21.2.3　JDBC的用处

JDBC的引入有其非常重要的作用：

❑程序员可以使用Java开发基于数据库的应用程序，在遵守Java语言规则的同时，可以使用标准的SQL语句访问任何数据库。

❑如果数据库厂商提供较底层的驱动程序，程序员可以在自己的软件中，使用比较优化的驱动程序。

其实JDBC和ODBC是统一模型，那为什么不采用ODBC呢？因为ODBC过于复杂难以掌握，也比较难部署。

数据库连接对动态网站来说是最为重要的部分。Java中连接数据库的技术是JDBC（Java DataBase Connectivity）。很多数据库系统带有JDBC驱动程序，Java程序就通过JDBC驱动程序与数据库相连，执行查询、提取数据等操作。Sun公司还开发了JDBC-ODBC bridge，用此技术，Java程序就可以访问带有ODBC驱动程序的数据库。目前，大多数数据库系统都带有ODBC驱动程序，所以，Java程序能访问诸如Oracle、Sybase、MS SQL Server和MS Access等数据库。

21.3　SQL数据库技术

在很多情况下，遇到的数据库大部分都是SQL。那么SQL到底是什么呢？为什么在现实的开发中，它的使用频率如此之高？本节将详细地讲述SQL数据库。

21.3.1　什么是SQL数据库

SQL（发音为字母S-Q-L或sequel）是结构化查询语言（Structured Query Language）的缩写。SQL是一种专门用来与数据库通信的语言。

与其他语言（如Java、Visual Basic这样的程序设计语言）不一样，SQL由很少的词构成，这是有意而为的。SQL能够很好地完成一项任务—提供一种从数据库中读写数据的简单有效的方法。

SQL有如下优点：

❑SQL不是某个特定数据库供应商专有的语言，几乎所有重要的DBMS都支持SQL，所以，此语言几乎能与所有数据库打交道。

❑SQL简单易学。它的语句全都是由具有很强描述性的英语单词组成，而且这些单词的数目不多，这就是它简单易学的主要原因。

❑SQL看上去尽管很简单，但实际上是一种强有力的语言，灵活使用其语言元素，可以进行非常复杂和高级的数据库操作。

21.3.2　如何操作SQL数据库

标准SQL只包含9种语句：

数据查询：select

数据定义：create, drop, delete

数据操纵：insert, update, delete

数据控制：grant, revoke

❑数据查询语言用于查询数据结构。

❑数据定义语言用于数据结构。

❑数据操纵语言用于检索和修改数据结构。

❑数据控制语言用于规定DB用户的各种权限。

1.数据查询语句

查询语句是DB中最频繁执行的活动。在SQL中，使用select语句可在需要的表单中检索数据，在进行检索之前，必须知道需要的数据存储在哪里，select语句可由多个查询子句组成。

select的结构：

select[all|distinct][into new_table_name]

from[表名|视图名]

〔where搜索条件〕

group by把查到的按什么标准分组

〔having搜索条件〕

〔order by按什么顺序排序〕〔升序|降序〕

说明如下：

❑all：指明查询结果中可以显示值相同的列，all是系统默认值。

❑distinct：指明查询结果中如有值相同的列，只显示其中的一列，对distinct来说，NULL被认为相同的值。

❑into子句用于把查询结果存放到一个新建表中。不过要注意select……into句式不能与compute子句一起使用。新表是由select子句指定的列构成。

❑from子句指定需要进行数据查询的表。

❑where子句指定数据检索的条件，以限制返回的数据。

❑group by子句指定查询结果的分组条件。

❑having子句：指定分组搜索条件。它通常与group by子句一起使用。

❑order by子句：指定查询结果的排序方式。

2.数据操纵语句

数据插入语句：

insert into＜表名＞〔列名〕value〔对应列的值〕

数据修改语句：

update＜表名＞set＜列名＞=＜表达式＞〔where＜条件＞〕

数据删除语句：

delete〔from〕{表名|视图名}〔where子句〕

3.数据定义语言

创建DB表：create table

创建DB表的索引：create index

删除DB中的表：drop table

删除DB表中的索引：drop index

删除表中所有的行：truncate

增加表列：alter table

这一节中介绍了SQL中经常使用到的一些语句，后面会通过举例，让读者熟悉其操作过程。

21.3.3　安装SQL Server 2005

本节介绍如何安装SQL Server 2005，具体步骤如下所示。

（1）把光盘放入光驱，弹出自动选择菜单。在此菜单中可以选择自己需要安装的选项，如图21.3所示。

（2）因为需要安装SQL，所以选择“安装服务器组件、工具、联机丛书和示例”，使用它可以安装SQL Server 2005软件。其他的则是一些帮助文档、说明文档之类的安装，选择后弹出如图21.4所示的界面。

（3）选择“我接受许可条款和条件”复选框。其实在安装所有的软件时，希望读者能养成一个习惯，就是要详细阅读用户的许可协议，这样做的好处是对此软件的了解会加深。选择之后，弹出如图21.5所示界面。

（4）单击“安装”按钮，系统便开始安装软件了，如图21.6所示，此时安装软件可能从最外围基础软件开始。

图　21.3　SQL 2005数据库开始界面

图　21.4　最终协议许可协议界面

图　21.5　安装必要组件界面

图　21.6　安装必要组件过程界面

（5）一段时间后，会出现如图21.7所示的界面。

（6）安装完这些组件后，系统将会进入到SQL Server程序的安装，如图21.8所示。读者不要以为前面都是在安装SQL Server，其实不然，前面安装的都是一些支持本软件的组件。

图　21.7　安装必要组件完成界面

图　21.8　系统配置检查界面

（7）系统扫描到计算机配置完成后，便会正式安装SQL Server软件，弹出如图21.9所示的界面。

（8）单击“下一步”按钮，弹出的界面如图21.10所示。

（9）在图21.10中，将把前面扫描过的计算机硬件和软件信息统一显示在显示框中，让用户能够很清晰自己系统中哪些是不符合安装要求的，直接单击“下一步”按钮，会弹出界面，如图21.11所示。

图　21.9　SQL正式安装向导界面

图　21.10　系统检查配置界面

（10）在图21.11中，系统计算硬盘的剩余空间后，看是否能安装SQL Server软件，直接单击“下一步”按钮，弹出输入注册码的界面。

（11）此时输入姓名、公司名称以及注册码的信息，单击“下一步”按钮，弹出的界面如图21.12所示。

图　21.11　SQL计算使用空间界面

图　21.12　选择安装组件界面

（12）在图21.12中，将让用户选择安装哪些额外组件，默认是不选择的。单击“下一步”按钮，会出现如图21.13所示界面。

（13）在图21.13中，系统将会把一些错误和使用情况设置信息展示给用户看，让用户知道自己目前安装软件的情况。单击“下一步”按钮，弹出如图21.14所示的界面。

（14）当选择了要安装的客户组件后，在图21.14的文本区中会显示出需要安装的额外组件。单击“安装”按钮，会弹出如图21.15所示的界面。

图　21.13　错误和使用情况报告设置界面

图　21.14　准备安装界面

（15）此时，要经过漫长的等待，直到所有的组件全部安装完成后，弹出如图21.16所示的界面。

图　21.15　安装SQL程序界面

图　21.16　安装过程中的界面

（16）单击“下一步”按钮，会出现如图21.17所示界面，此时单击“完成”按钮。

图　21.17　安装完成界面

至此，SQL Server 2005数据库安装完毕了，在这个版本的安装过程中，需要安装一些其他支持本软件的组件，这与以往的版本不同。下一小节将讲述如何去使用SQL创建数据库。

21.3.4　使用SQL创建数据库

无论SQL Server 2005还是其他的版本，它们都是使用SQL语言，所以它们使用的基本点都是一样的。SQL语言的重要性，读者在以后的工作中会体会到。为了帮助读者尽快熟悉SQL语言的知识。下面将讲述如何通过SQL语句对数据库进行操作。

注意　SQL语言不区分大小写。

【实例21.1】创建一个新的数据库，名称为“schoolmanage”。接着再新建一张表，名称为“teacherinfo”。通过插入的方式将数据导入表中。

（1）创建一个数据库：

create database schoolmanage

（2）创建好了一个数据库，接下来就需要创建一张表，名称为“teacherinfo”。再为这张表创建字段。

create table teacherinfo

（name char（8）null，

code char（8）null，

sexy char（8）null，

age char（8）null，

birthday char（8）null，

address char（8）null，

salary char（8）null，

）

（3）通过上面的创建，在表中就有了字段名称。到目前为止，表中还没有数据。下面通过插入命令来给这张表添加数据。

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'王鹏'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'宋江'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'李丽'，'2001'，'女'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'钱敏'，'2001'，'女'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'朱庭'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'祖海'，'2001'，'女'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'周浩'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'张平'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'孙军'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'王俊'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'李鹏'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'孙鹏'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'王海'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'周洁'，'2001'，'女'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'宋平'，'2001'，'女'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'吴浩'，'2001'，'男'，'23'，'1975-9-1'，'重庆市'，'3000'）；

insert into teacherinfo（name, code, sexy, age, birthday, address, salary）values（'武芬'，'2001'，'女'，'23'，'1975-9-1'，'重庆市'，'3000'）；

【代码说明】teacherinfo是指数据库中的表，而括号内的就是字段，在values后面的括号内，就是这些字段的值。这样就创建了一个简单的有数据的数据库。

下面再看看如何去操作这些数据，现在想要查询全部教师中的女士，可以使用下面的SQL语句来实现它。

select*from teacherinfo where sexy='女'

这里的where后面跟着的是条件，因为要查询全部的女士，所以只要　性别为女就可以了。现在，需要删除一些信息，操作如下。

delete*from teacherinfo where sexy='女'

上面的语句是把表中所有的女教师信息删除，这里的where同上面一样，也是标识条件。很多实际开发中的语句比这要复杂得多，限于篇幅和本书的宗旨，此处不再详细介绍SQL语句的一些复杂应用，有兴趣的读者可以自行查阅一些相关资料。

21.4　JDBC基本编程

本节将讲述如何进行JDBC的编程工作。相信读者通过大量的实例，一定会掌握如何连接到数据库、如何去操作数据库等。

21.4.1　如何建立数据库

如何建立数据库呢？建立数据库总共有以下几个步骤：设定数据源、建立数据库的URL、建立会话，接下来将详细讲述。

1.设定ODBC数据源

ODBC是建立数据库最基本的一个步骤。下面将利用上一节中所创建的数据库，来设定ODBC数据源。

（1）单击“开始”|“控制面板”|“管理工具”，弹出“管理工具”窗口，如图21.18所示。

（2）选择“数据源”后，出现ODBC数据源管理器的界面，如图21.19所示。

图　21.18　选择数据源

（3）在这个界面中，是一些系统默认的数据源。由于上例中使用的是SQL数据库，而在用户数据源中没有，所以单击“添加”按钮，打开如图21.20所示界面。

图　21.19　数据源管理器界面

图　21.20　创建新数据源界面

（4）选择“SQL Server”选项，单击“完成”按钮，弹出如图21.21所示的对话框。

（5）填写数据库名称，连接数据库服务器是哪一个，接着单击“下一步”按钮，弹出如图21.22所示的界面。

图　21.21　给数据源命名

图　21.22　设置数据源的登录

（6）单击“下一步”按钮，会出现如图21.23所示的界面。此时先更改默认的数据库，这里可以选择前面创建好的数据库，然后单击“下一步”按钮。

（7）最后会出现一个测试的界面，如图21.24所示。单击“测试数据源”按钮，如果测试成功，说明操作成功。单击“确定”按钮退出这个操作向导。

图　21.23　确认数据库名称

图　21.24　设置数据源完成界面

至此，数据源已经创建完毕。

2.建立数据库的URL

刚才建立的ODBC数据源，如何登录呢？

Class.forName（"sun.jdbc.odbc.jdbcodbcDriver"）；

3.建立会话

数据库建立连接后，要想操纵数据库，必须跟数据库建立一个会话。所谓会话，就是从建立一个数据库连接，到关闭数据库连接所进行的所有动作的总称。可以通过如下的方式得到一个会话。

Statement st=con.createStatement（）；

此处Statement是一个有关建立对话的接口，所以它不能产生对象，只能通过连接类Connection的createStatement（）方法来获得它的对象。

经过以上的步骤，数据库就连接了。下面将通过实例来学习如何操作数据库。

21.4.2　如何操作数据库

既然连接上了数据库，本节将介绍如何操作数据库。操作数据库主要是查询，下面学习如何进行查询。

查询操作是数据库中最基本的语句。通过如下的语句，可以对数据库执行查询，查询的结果是以结果集ResultSet的形式返回。有关ResultSet集，可以参考API文档。

String sql="select*from表名"

ResultSet result=st.executeQuery（sql）；

在表中，提取数据可以使用结果集ResultSet中的方法来提取。如果表中的列都是字符型数据，那么可以使用getString（）方法。具体的可以查询相关的API，而在getString（）中的参数则可以是这个表中的列名，也可以是表中的列的序号。

【实例21.2】下面举一个有关操作数据库的实例，这个实例的前提是在前面已经建立ODBC数据源。

01 import java.sql.*；

02 public class sqltest

03 {

04 private Connection con；

05 public static void main（String[]args）

06 {

07 sqltest test=new sqltest（）；

08 Connection con=test.getConnection（）；///定义一个数据连接

09 String sql="select*from teacherinfo"；///定义一个SQL查询语句

10 test.getStudent（con, sql）；

11 }

12 public void getStudent（Connection con, String sql）

13 {

14 try

15 {

16 Statement st=con.createStatement（）；///定义一个结果集

17 ResultSet rs=st.executeQuery（sql）；

18 while（rs.next（））

19 {

20 String name1=rs.getString（1）；

21 String code1=rs.getString（2）；

22 String sexy1=rs.getString（3）；

23 String age1=rs.getString（4）；

24 System.out.println（"\n姓名："+name1+"\t　学号："+code1+"\t　性别："+sexy1+"\t　年龄："+age1）；

25 }

26 st.close（）；

27 con.close（）；

28 }

29 catch（Exception e）{e.printStackTrace（）；}

30 }

31 public Connection getConnection（） ///连接数据库

32 {

33 String url1="jdbc：odbc：schoolmanage"；///定义数据库的URL

34 String username=""；///用户名

35 String password=""；///密码

36 try

37 {

38 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

39 ///登录到建立的数据源上去，也可以说登录到数据库上去

40 con=DriverManager.getConnection（url1，username, password）；

41 }

42 catch（SQLException e）

43 {e.printStackTrace（）；

44 }

45 catch（ClassNotFoundException ex）

46 {ex.printStackTrace（）；}

47 return con；

48 }

49 }

【代码说明】第33行是指向前面创建的ODBC数据源，第38～40行是通过ODBC登录到数据库。第31～48行用来获取数据库连接，第12～30行用来获取所有的数据库数据。

【运行效果】

姓名：王鹏　学号：2001　性别：男　年龄：23

姓名：宋江　学号：2001　性别：男　年龄：23

姓名：李丽　学号：2001　性别：女　年龄：23

姓名：钱敏　学号：2001　性别：女　年龄：23

姓名：朱庭　学号：2001　性别：男　年龄：23

姓名：祖海　学号：2001　性别：女　年龄：23

姓名：周浩　学号：2001　性别：男　年龄：23

姓名：张平　学号：2001　性别：男　年龄：23

姓名：孙军　学号：2001　性别：男　年龄：23

姓名：王俊　学号：2001　性别：男　年龄：23

姓名：李鹏　学号：2001　性别：男　年龄：23

姓名：孙鹏　学号：2001　性别：男　年龄：23

姓名：王海　学号：2001　性别：男　年龄：23

姓名：周洁　学号：2001　性别：女　年龄：23

姓名：宋平　学号：2001　性别：女　年龄：23

姓名：吴浩　学号：2001　性别：男　年龄：23

姓名：武芬　学号：2001　性别：女　年龄：23

以上的实例将前面所有讲过的数据库方面的知识联系起来了。

【实例21.3】下面结合前面的数据结构举一个实例。在这个实例中存在数据结构的知识和数据库的知识。为了能够让读者看懂这个实例，下面将分步骤来讲述这个程序。

（1）构造一个学生类，代码如下所示。

01 import java.sql.*；

02 import java.util.Vector；

03 ///创建一个学生类

04 ///stname1是指学生　姓名

05 ///stcode1是指　学号

06 ///stsexy1是指　性别

07 ///stmajor1是指专业

08 ///stbirthday是指出　生年月

09 ///staddress1是指　家庭地址

10 ///set是设置器

11 ///get是访问器

12 ///tostring是让对象以字符串形式输出的方法

13 class student1 //创建一个学生类

14 {

15 private String stname1；

16 private String stcode1；

17 private String stsexy1；

18 private String stmajor1；

19 private String stbirthday1；

20 private String staddress1；

21 public student1（String name, String code）

22 {

23 stname1=name；

24 stcode1=code；

25 }

26 public String getstudentname（）

27 {

28 return stname1；

29 }

30 public String getstudentcode（）

31 {

32 return stcode1；

33 }

34 public void setstudentsex（String sexy）

35 {

36 stsexy1=sexy；

37 }

38 public String getstudentsex（）

39 {

40 return stsexy1；

41 }

42 public void setstudentmajor（String major）

43 {

44 stmajor1=major；

45 }

46 public String getstudentmajor（）

47 {

48 return stmajor1；

49 }

50 public void setstudentbirthday（String birthday）

51 {

52 stbirthday1=birthday；

53 }

54 public String getstudentbirthday（）

55 {

56 return stbirthday1；

57 }

58 public void setstudentaddress（String address）

59 {

60 staddress1=address；

61 }

62 public String getstudentaddress（）

63 {

64 return staddress1；

65 }

66 public String toString（）

67 {

68 String information="学生　姓名："+stname1+"　学号："+stcode1+"　性别："+stsexy1+"出　生年月：

69"+stbirthday1+"专业："+stmajor1+"\n"；

70 return information；

71 }

72 }

（2）构造一个连接数据库的类。在这个类中，有连接数据库的方法，也有查询数据库的方法。详细代码如下所示。

73 ///这是一个对象的存储类

74 ///getstudent方法是将数据库中的字段数据提取出来赋值给Vector数据结构中的对象

75 ///st是用来返回数据库结果的对象

76 ///rs是指结果集对象

77 ///rs.getString方法是从数据库表中提取字段值

78 ///v是指vector的对象

79 ///sql是指用来查询的一个sql查询语句

80 class querytable

81 {

82 public Vector getstudent（Connection con）

83 {

84 Vector v=new Vector（）；

85 try

86 {

87 Statement st=con.createStatement（）；

88 String sql="select*from studentinfo"；

89 ResultSet rs=st.executeQuery（sql）；

90 while（rs.next（））

91 {

92 ///通过结果集将数据库中的字段赋值给字符串。再将字符串赋值给学生对象

93 String name=rs.getString（1）；

94 String code=rs.getString（2）；

95 String sexy=rs.getString（3）；

96 String major1=rs.getString（4）；

97 String birthday=rs.getString（5）；

98 student1 ss=new student1（name, code）；

99 ss.setstudentsex（sexy）；

100 ss.setstudentmajor（major1）；

101 ss.setstudentbirthday（birthday）；

102 //ss.setstudentaddress（address）；

103 v.add（ss）；///再将对象存储到Vector数据结构中去

104 }

105 st.close（）；

106 con.close（）；

107 }

108 catch（Exception e）

109 {

110 e.printStackTrace（）；

111 }

112 return v；

113 }

114 }

115 ///用来连接数据库的类

116 ///con是指定用来连接数据库的连接类

117 ///username是进入数据库的用户名

118 ///password是指进入数据库的密码

119 ///url1是连接数据库的URL

120 class dbconnection ///创建一个连接数据库的类

121 {

122 private Connection con；

123 public Connection getConnection（）

124 {

125 String url1="jdbc：odbc：schoolmanage"；

126 String username=""；

127 String password=""；

128 try

129 {

130 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

131 con=DriverManager.getConnection（url1，username, password）；

132 }

133 catch（SQLException e）{e.printStackTrace（）；

134 }

135 catch（ClassNotFoundException ex）{ex.printStackTrace（）；

136 }

137 return con；

138 }

139 }

（3）在主运行类中，将数据库中的表数据输出。详细代码如下所示。

140 ///主运行类

141 ///db是一个连接数据库的类的对象

142 ///v1是一个vector数据结构的对象

143 ///query是一个连接数据库并将数据导入的一个类的对象

144 public class studentsql

145 {

146 public static void main（String[]args）

147 {

148 try

149 {

150 dbconnection db=new dbconnection（）；///创建一个数据库连接

151 Connection con1=db.getConnection（）；

152 querytable query=new querytable（）；

153 Vector v1=query.getstudent（con1）；///将所有数据库中数据存储到Vector数据结构中

154 System.out.println（"目前vector中的学生个数为："+v1.size（））；

155 for（int i=0；i＜（v1.size（））；i++）

156 {

157 System.out.println（（student1）v1.get（i））；///输出所有的学生对象

158 }

159 }catch（Exception e）{e.printStackTrace（）；}

160 }

161 }

【代码说明】上述代码是先建立一个学生类，然后，将学生类对象放入数据结构Vector中，再连接数据库，从数据库中取数据，放入到Vector中，这样就可以从Vector中直接取数据了。这个实例也是一个比较通用的实例，希望读者能好好地分析它，因为在后面还要用到。

说明

本例读取的是数据库中的studentinfo表，如果没有，需要自己创建一个，可以根据前面介绍的teacherinfo表的插入语句直接改变。

【运行效果】

目前vector中的学生个数为：17

学生　姓名：王鹏　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：宋江　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：李丽　学号：2001　性别：女出　生年月：1975-9-1专业：计算机

学生　姓名：钱敏　学号：2001　性别：女出　生年月：1975-9-1专业：计算机

学生　姓名：朱庭　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：祖海　学号：2001　性别：女出　生年月：1975-9-1专业：计算机

学生　姓名：周浩　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：张平　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：孙军　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：王俊　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：李鹏　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：孙鹏　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：王海　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：周洁　学号：2001　性别：女出　生年月：1975-9-1专业：计算机

学生　姓名：宋平　学号：2001　性别：女出　生年月：1975-9-1专业：计算机

学生　姓名：吴浩　学号：2001　性别：男出　生年月：1975-9-1专业：计算机

学生　姓名：武芬　学号：2001　性别：女出　生年月：1975-9-1专业：计算机

21.4.3　高级SQL类型

一直以来处理的数据都是数字、字符串等，但在实际应用中，可能会向数据库中存储一些复杂类型，如图像等数据。在SQL中，BLOB代表二进制位的大型对象类型，CLOB代表字符大型对象，可以分别通过getBlob或getClob等方法来处理这些大型对象。这里限于篇幅，不再针对它进行举例，有兴趣的读者可以查阅相关资料。

21.5　事务处理

在实际工作中，对数据库的操作并不能保证完全不出错，一旦出错了，如何能保证数据的完整性和统一性呢？本节将引入一个新的概念：事务。事务跟数据库中数据完整性和统一性有什么联系？如何进行事务处理？本节通过详细的实例，来解决这方面的困扰。

21.5.1　什么是事务处理

事务是SQL中的单个逻辑工作单元，一个事务内的所有语句被作为整体执行，遇到错误时，可以回滚事务，取消事务所作的所有改变，从而可以保证数据库的一致性和可恢复性。

一个事务逻辑工作单元必须具有以下4种属性，如下所示：

❑原子性：一个事务必须作为一个原子单位，它所作的数据修改操作要不全部执行，要不全部取消。

❑一致性：当事务完成后，数据必须保证处于一致性的状态。

❑隔离性：一个事务所作的修改必须能够跟其他事务所作的修改分离开来，以免在并发处理时，发生数据错误。

❑永久性：事务完成后，它对数据库所作的修改应该被永久保持。

在实际工作中，有可能会出现一种情况：要向一个数据库中插入若干条记录，如果当插入一半数据时，突然系统出错了，那么数据库中已经有了这一半的数据。如果再重新插入，很有可能会引起数据库出错，此时，就需要使用事务的概念。在一个事务中允许数据库进行回滚操作，即让前面的插入工作全部作废，这样重新插入的时候就不会出错了。

数据库的所有操作都是建立在Connection连接类基础上的，事务管理也是在其上进行的，若要管理一个事务，可以在进行数据库操作动作之前，将数据库自动提交设为false，即con.setAutoCommit（false）。在数据库操作没有任何异常现象出现前提下，再将事务提交：con.commit（），在提交完毕后，要记住将事务管理的控制权还给数据库：con.setAutoCommit（true）。下一小节将举一个有关事务的实例。

21.5.2　事务处理的过程演练

【实例21.4】本节将通过一个实例，演示事务处理中的回滚功能。实例代码如下所示。

01 import java.sql.*；

02 ///通过连接数据库来获取数据

03 ///将setAutoCommit（false）设为false，即不自动提交。

04 ///rollback进行回滚

05 public class sqltest1

06 {

07 private Connection con；

08 public static void main（String[]args）

09 {

10 try

11 {

12 sqltest1 test=new sqltest1（）；

13 Connection con=test.getConnection（）；

14 con.setAutoCommit（false）；

15 String sql="select*from teacherinfo"；

16 String sql1="insert into teacherinfo values（'朱雪莲'，'674322'，'女'，'24'，'1981-1-6'，'上海市'，'5000'）"；

17 System.out.println（"插入数据后的数据是："）；

18 test.getStudent1（sql1）；

19 test.getStudent（con, sql）；

20 con.rollback（）；

21 System.out.println（"回滚数据后的数据是："）；

22 test.getStudent（con, sql）；

23 }

24 catch（Exception e）{}

25 }

26 public void getStudent1（String sql）

27 {

28 try

29 {

30 Statement st=con.createStatement（）；

31 st.executeUpdate（sql）；

32 st.close（）；

33 con.close（）；

34 }

35 catch（Exception e）{}

36 }

37 ///通过结果集的getString方法来获得表中的数据字段

38 ///将所有获得来的字段输出

39 public void getStudent（Connection con, String sql）

40 {

41 try

42 {

43 Statement st=con.createStatement（）；

44 ResultSet rs=st.executeQuery（sql）；

45 while（rs.next（））

46 {

47 String name1=rs.getString（1）；

48 String code1=rs.getString（2）；

49 String sexy1=rs.getString（3）；

50 String age1=rs.getString（4）；

51 System.out.println（"\n姓名："+name1+"\t　学号："+code1+"\t　性别："+sexy1+"\t　年龄："+age1）；

52 }

53 st.close（）；

54 con.close（）；

55 }

56 catch（Exception e）{e.printStackTrace（）；}

57 }

58 ///用来连接数据库的类

59 ///con是指定用来连接数据库的连接类

60 ///username是进入数据库的用户名

61 ///password是指进入数据库的密码

62 ///url1是连接数据库的URL

63 public Connection getConnection（）

64 {

65 String url1="jdbc：odbc：schoolmanage"；

66 String username=""；

67 String password=""；

68 try

69 {

70 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

71 con=DriverManager.getConnection（url1，username, password）；

72 }

73 catch（SQLException e）{e.printStackTrace（）；}

74 catch（ClassNotFoundException ex）{ex.printStackTrace（）；}

75 return con；

76 }

77 }

【代码说明】第63～76行创建数据库的连接，这里通过第65行还是指定了前面创建的ODBC。第26～36行和第39～57行创建了两个getStudent（）方法，使用的参数不同，处理的功能也不同。第20行使用了事务的回滚功能。

【运行效果】

插入数据后的数据是：

姓名：王鹏　学号：2001　性别：男　年龄：23

姓名：宋江　学号：2001　性别：男　年龄：23

姓名：李丽　学号：2001　性别：女　年龄：23

姓名：钱敏　学号：2001　性别：女　年龄：23

姓名：朱庭　学号：2001　性别：男　年龄：23

姓名：祖海　学号：2001　性别：女　年龄：23

姓名：周浩　学号：2001　性别：男　年龄：23

姓名：张平　学号：2001　性别：男　年龄：23

姓名：孙军　学号：2001　性别：男　年龄：23

姓名：王俊　学号：2001　性别：男　年龄：23

姓名：李鹏　学号：2001　性别：男　年龄：23

姓名：孙鹏　学号：2001　性别：男　年龄：23

姓名：王海　学号：2001　性别：男　年龄：23

姓名：周洁　学号：2001　性别：女　年龄：23

姓名：宋平　学号：2001　性别：女　年龄：23

姓名：吴浩　学号：2001　性别：男　年龄：23

姓名：武芬　学号：2001　性别：女　年龄：23

姓名：朱雪莲　学号：674322　性别：女　年龄：24

回滚数据后的数据是：

姓名：王鹏　学号：2001　性别：男　年龄：23

姓名：宋江　学号：2001　性别：男　年龄：23

姓名：李丽　学号：2001　性别：女　年龄：23

姓名：钱敏　学号：2001　性别：女　年龄：23

姓名：朱庭　学号：2001　性别：男　年龄：23

姓名：祖海　学号：2001　性别：女　年龄：23

姓名：周浩　学号：2001　性别：男　年龄：23

姓名：张平　学号：2001　性别：男　年龄：23

姓名：孙军　学号：2001　性别：男　年龄：23

姓名：王俊　学号：2001　性别：男　年龄：23

姓名：李鹏　学号：2001　性别：男　年龄：23

姓名：孙鹏　学号：2001　性别：男　年龄：23

姓名：王海　学号：2001　性别：男　年龄：23

姓名：周洁　学号：2001　性别：女　年龄：23

姓名：宋平　学号：2001　性别：女　年龄：23

姓名：吴浩　学号：2001　性别：男　年龄：23

姓名：武芬　学号：2001　性别：女　年龄：23

经过回滚后，原先插入的最后一条数据已经没有了。这是为了保证数据的完整性和一致性，而进行的数据库的回滚操作。

21.6　预查询

预查询顾名思义就是预先查询。有的软件中，输入一个人的姓名后，它的全部数据都会出现，这就是使用现在要讲述的预查询。

可以先建立一个查询条件：

string sql="select name, code, sexy, age from teacherinfo where name=?"

接下来，要介绍一个接口“PreparedStatement”，而这里的预查询就是通过这个接口实现。可以通过下列方式来获得这个接口的对象。

PreparedStatement pre=con.PreparedStatement（sql）

此时就建立好一个预查询对象了，如果要查询，可以利用下面的方法实现。

Pre.setString（字段的列序号，此列中的一个要查询的值）；

例如，要查询一个叫“王鹏”的人的信息，就可以使用下列代码实现。

Pre.setString（1，"王鹏"）；

再如，要查询一个　学号是23001的人的信息，就可以使用下列代码实现。

string sql="select name, code, sexy, age from teacherinfo where code=?"

PreparedStatement pre=con.PreparedStatement（sql）

Pre.setString（2，"23001"）；

21.7　常见疑难解答

21.7.1　操作数据库的具体步骤是什么

1.创建Statement对象

建立了到特定数据库的连接之后，就可用该连接发送SQL语句，Statement对象用Connection的方法createStatement创建，如下所示：

Connection con=DriverManager.getConnection（url，"sunny"，""）；

Statement stmt=con.createStatement（）；

为了执行Statement对象，发送到数据库的SQL语句将被作为参数提供给Statement的方法：

ResultSet rs=stmt.executeQuery（"select a, b，c from Table2"）；

使用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（）用于执行返回多个结果集、多个更新计数或二者组合的语句，多数程序员不需要该高级功能。

执行语句的所有方法都会关闭所调用的Statement对象的当前结果集，这意味着在重新执行Statement对象之前，需要完成对当前ResultSet对象的处理。

注意

继承了Statement接口中所有方法的PreparedStatement接口，都有自己的executeQuery（）、executeUpdate（）和execute（）方法。Statement对象本身不包含SQL语句，因而必须给Statement.execute（）方法提供SQL语句作为参数。PreparedStatement对象并不将SQL语句作为参数提供给这些方法，因为它们已经包含预编译SQL语句。CallableStatement对象继承这些方法的PreparedStatement形式，对于这些方法的PreparedStatement或CallableStatement版本，使用查询参数将抛出SQLException。

2.语句完成

当连接处于自动提交模式时，其中所执行的语句在完成时将自动提交或还原。语句在已执行且所有结果返回时，即认为已完成。对于返回一个结果集的executeQuery（）方法，在检索完ResultSet对象的所有行时该语句完成。对于方法executeUpdate（），当它执行时语句即完成，但在少数调用方法execute（）的情况中，在检索所有结果集或它生成的更新计数之后，语句才完成。

有些DBMS将存储过程中的每条语句视为独立的语句，而另外一些则将整个过程视为一个复合语句。在启用自动提交时，这种差别就变得非常重要，因为它影响什么时候调用commit（）方法。在前一种情况中，每条语句单独提交，在后一种情况中，所有语句同时提交。

3.关闭Statement对象

Statement对象将由Java垃圾收集程序自动关闭。而一种好的编程风格，应在不需要Statement对象时，显式地关闭它们，这将立即释放DBMS资源，有助于避免潜在的内存问题。

21.7.2　数据库中的视图、图表、默认值、规则、触发器、存储过程的意义

❑视图看上去跟表一模一样。具有一组命名的字段和数据项，但实际上，它是一个虚拟表，在库中并不存在。它是由查询DB产生的，限制了用户能看到和修改的数据，由此可见，视图可以用来控制用户对数据的访问，并能简化数据的显示，即通过视图只需要显示那些需要的数据信息。

❑图表就是数据库表之间的关系示意图，利用它可以编辑表与表之间的关系。

❑规则就是对DB表中的数据信息进行限制，有一点需要强调的是它限定的只能是表中的列字段。

❑默认值就是当在表中创建列或插入数据时，对没有指定具体值的列，赋予默认设定好的值。

❑触发器是一个用户自定义的SQL事务命令的集合，当对一个表进行插入、更改、删除时，这组命令会自动执行。

❑存储过程是为完成特定的功能而汇集在一起的一组SQL程序语句，经编译后存储在DB中的SQL程序。

21.8　小结

本章处理的主要是数据库中的数据。数据库是一个复杂的数据承载仓库，读者学习本章时，可以先看看SQL Server的相关书籍，了解一下基本的数据操作语言T-SQL，这样在操作数据时可能会更顺手。本章重点讲解了如何使用JDBC操作数据，最后还讲解了事务处理的方法和技巧。

21.9　习题

1.用来进行查询的SQL关键字是______。

2.用来进行插入的SQL关键字是______。

3.说明获取数据连接的两种方式。

4.什么时候使用PreparedStatement？

5.JDBC中，查询返回的是什么对象？

6.怎样将事务设置为手动？

第22章　Swing组件与布局管理器

本章介绍Swing图形设计。它与Awt有什么区别和渊源呢？带着这些疑问阅读本章。本章本着以实例为主的原则，让读者能更加透彻地理解和掌握Swing组件的使用，并且通过比较，观察两个不同图形设计类的异同之处。

本章重点：

❑认识Swing。

❑简单图形控件的设计。

❑布局管理器的设计和使用。

❑Swing的实例应用。

22.1　什么是Swing编程

由于Awt本身有很多不完善的地方，所以一个全新的GUI用户类库出现了，这就是Swing。然而Swing并没有完全代替Awt，例如事件模型，Swing仍然采用Awt的事件模型，其本身没有事件模型的类，Swing其实就是Awt的一个优化版本。

22.2　MVC设计模式基础

在Swing中，每一个组件都有三个要素。

❑内容：例如，按钮的状态（是否被按下）、文本框内的文本等。

❑外观：组件所表现出来的效果，例如颜色、形状。

❑行为：组件对事件的处理方式，接受事件后如何处理。

这三个要素的关系很复杂，所以程序员不要让一个对象具备太多的功能，这跟MVC的模式实现有关。MVC模式包括下面三个部分：

❑模型（M）：用于存储内容，它只关心组件的内容。

❑视图（V）：用于实现组件的外观。

❑控制器（C）：用于实现组件的事件处理。

模型只是负责存储内容，它并没有任何用户界面，模型只是负责处理业务逻辑，它并不关心如何显示给用户，只是为视图提供原始的数据而已。视图可以显示模型的一部分，一个模型可以有多个视图。控制器是负责处理用户输入事件，例如用户单击鼠标、文本框输完后按Enter键等，这些都将交给控制器来负责。如果用户单击滚动条，那么控制器就把动作交给了视图和模型，因为随着滚动条的移动，视图和模型都在改变。

22.3　简单框架设计及实例

本节将通过Swing类库来设计框架。框架是一个界面最外围的元素，必须有框架，才能在框架上添加其他的元素。

22.3.1　创建一个空白的框架

在Awt库中有一个Frame类与框架对应，而在Swing中与之对应的是JFrame，它是Frame的扩展，同时它也是一个容器。

【实例22.1】下面将通过一个实例，来分析如何利用Swing创建一个空白的框架。

01 import javax.swing.JFrame；

02 ///创建一个顶层框架类

03 ///WIDTH参数是指框架的宽度

04 ///HEIGHT参数是指框架的长度

05 ///setTitle用来设置框架左上角的标题

06 ///setSize用来设置框架的大小

07 public class Swingtest

08 {

09 static final int WIDTH=300；

10 static final int HEIGHT=200；

11 public static void main（String[]args）

12 {

13 JFrame jf=new JFrame（）；

14 jf.setSize（WIDTH, HEIGHT）；

15 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

16 jf.show（）；

17 }

18 }

【代码说明】第1行引入了javax.swing.JFrame包，第13行通过JFrame类创建了一个框架。第16行显示这个框架。

【运行效果】运行结果如图22.1所示。

先来看看引入包的情况。在这里引入的包是javax.swing.JFrame, javax表示这是java的一个扩展包。那么为什么可以关闭窗口呢？这通过调用setDefaultCloseOperation方法做到。在这个方法内有4个不同参数，也可以说是4个常量，具体如下所示。

图　22.1　使用Swing的类库创建出来的框架

❑DO_NOTHING_ON_CLOSE：当窗口关闭时，什么也不做。

❑DISPOSE_ON_CLOSE：当窗口关闭时，强制Java虚拟机释放创建窗口所占的资源。

❑HIDE_ON_CLOSE：当窗口关闭时，实际上是将该窗口隐蔽起来了。

❑EXIT_ON_CLOSE：当窗口关闭时，强制Java虚拟机释放程序所占用的资源。

说明　DISPOSE_ON_CLOSE和EXIT_ON_CLOSE的区别在于前者只关闭窗口就可以，后者则是关闭窗口后，就退出程序。

22.3.2　创建框架的其他工作

创建一个空框架后，接下来就是给框架设定一个标题。设置标题使用方法：

public void setTitle（String title）；

这是个设置器，通常有了设置器，一定会有访问器。

public String getTitle（）；

如果框架没有设置框架标题，就返回一个空的字符串。

在很多应用程序中，窗口都是位于屏幕中央，而上例中，所得到的框架位于左上角，那么如何来定位框架呢？

在Java中规定屏幕的左上角顶点为原点，水平为x轴，垂直为y轴，所以屏幕左上角的坐标就是（0，0）。如果屏幕的分辨率是1024×768像素，那么右下角的坐标就是（1024，768），而默认的框架从左上角开始。使用方法如下。

public void setLocation（int x, int y）；

那么如何将框架放到屏幕中间呢？现在教读者一个方法：先通过函数获得屏幕的高度和宽度。

int width=screenSize.width；

int height=screenSize.height；

然后再计算框架左上角的坐标。

int x=（width-WIDTH）/2；

int y=（height-HEIGHT）/2；

那么如何能得到屏幕的“screenSize”方法呢？这就要使用ToolKit类中的“getScreenSize（）”方法。

ToolKit kit=Toolkit.getDefaultToolkit（）；

Dimension screenSize=kit.getScreenSize（）；

这样就将框架定位到中心位置了。

【实例22.2】下面通过实例演示上面的理论知识。

01 import javax.swing.JFrame；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 public class Swingtest1

10 {

11 static final int WIDTH=300；

12 static final int HEIGHT=200；

13 public static void main（String[]args）

14 {

15 JFrame jf=new JFrame（）；

16 jf.setSize（WIDTH, HEIGHT）；

17 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

18 jf.setTitle（"学生管理系统"）；

19 Toolkit kit=Toolkit.getDefaultToolkit（）；

20 Dimension screenSize=kit.getScreenSize（）；

21 int width=screenSize.width；

22 int height=screenSize.height；

23 int x=（width-WIDTH）/2；

24 int y=（height-HEIGHT）/2；

25 jf.setLocation（x, y）；

26 jf.show（）；

27 }

28 }

【代码说明】第11～12行设置了两个高度和宽度的常量。第25行代码非常关键，是将窗口居中的意思。第16行设置窗口的大小。第26行显示窗口。

【运行效果】运行结果如图22.2所示。

图　22.2　一个居中在屏幕中央的框架

在使用应用软件时，希望能改变框架的大小，这里学习如何对框架进行缩放。

public void setResizable（boolean resizable）

如果不允许用户缩放框架大小，只需将resizable设置为false，如果一个框架不知道是否可以缩放，可以使用下列的方法看看是否可以缩放。

boolean isResizable（）

如果返回true，则允许用户改变框架的大小，反之，则不允许改变。

22.4　简单图形按钮控件的设计及实例

本节将详细地讲述几种按钮控件的设计，并且通过详细的实例让读者有一个很清晰的认识。

22.4.1　Swing中的按钮控件

根据上一节的介绍，可以推出按钮的创建过程，具体如下所示。

JButton button=new JButton（buttontext）

但是在API文档中，会发现JButton有5种构造器，分别如下所示。

JButton（）

JButton（Action a）

JButton（Icon icon）

JButton（String text）

JButton（String text, Icon icon）

下面这种构造器是最常用的构造器：

JButton button=new JButton（）

这个默认的按钮有特定的大小、颜色与外观，都由模型来完成。再看下面这个构造器：

JButton button=new JButton（Action a）；

可以采用一个Action对象构成一个按钮，其实还可以通过指定一个按钮的字符串、按钮的图标，来创建按钮对象，所以Swing中的内容比Awt要丰富。

22.4.2　按钮的模型

前面提到了模型的概念，本节将讲述按钮模型的概念。大多数组件的模型类，实现了一个以Model结尾的接口名字，例如按钮就实现了一个ButtonModel的接口。实现了此接口的类可以定义按钮的多种状态。在Swing库中包含了一个DefaultButtonModel类，这个类就实现了ButtonModel接口，也就是按钮的默认状态。分析ButtonModel接口中的方法，看看按钮模型所维护的各种数据。

❑getActionCommand（）：同按钮相关联的动作命令字符串。

❑getMnemonic（）：按钮的快捷键。

❑isArmed（）：如果按钮被按下并且鼠标仍在按钮上则返回true。

❑isEnabled（）：如果按钮可用则返回true。

❑isPressed（）：如果按钮被按下并且鼠标按钮尚未释放则返回true。

❑isRollover（）：如果鼠标在按钮上则返回true。

❑isSelected（）：如果按钮已经被选择（用于复选框即单选框）则返回true。

随着以后的学习，读者可以发现按钮模型可用于下压按钮、单选按钮、复选框、菜单项等。虽然这些组件在外观上不同，但是作为模型却是一样，所以说，JButton类实际上是一个封装类，它将视图、控制器、模型结合在一起，从而维护一个按钮对象。

22.4.3　添加按钮

在Swing中，一般组件都是添加到JPanel中，然后，再将JPanel组件添加到顶层窗口中。如下面的例子，先将组件添加到panel中。

panel.add（button）

然后将面板panel添加到容器中。

Container.add（panel）

【实例22.3】下面演示一个有关按钮的实例。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///添加两个按钮b1，b2

10 public class Swingtest2

11 {

12 static final int WIDTH=300；

13 static final int HEIGHT=200；

14 public static void main（String[]args）

15 {

16 JFrame jf=new JFrame（）；

17 jf.setSize（WIDTH, HEIGHT）；

18 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

19 jf.setTitle（"学生管理系统"）；

20 Toolkit kit=Toolkit.getDefaultToolkit（）；

21 Dimension screenSize=kit.getScreenSize（）；

22 int width=screenSize.width；

23 int height=screenSize.height；

24 int x=（width-WIDTH）/2；

25 int y=（height-HEIGHT）/2；

26 jf.setLocation（x, y）；

27 JButton b1=new JButton（"确定"）；

28 JButton b2=new JButton（"取消"）；

29 Panel p=new Panel（）；

30 p.add（b1）；

31 p.add（b2）；

32 jf.add（p）；

33 jf.show（）；

34 }

35 }

【代码说明】第16～19行创建窗口，并设置窗口的标题和大小，第22～26行设置窗口居中。第29行创建Panel，然后在第30～31行将两个按钮添加到Panel中，第32行是将Panel添加到窗口中。

【运行效果】运行结果如图22.3所示，从结果中并看不到Panel。

22.4.4　如何添加单选按钮

单选按钮也被称为radioButton，它通过JRadioButton类实现。在一些数据库系统软件中，会出现“　性别”单选按钮，通过选择不同的单选按钮，来实现不同　性别的选择。

【实例22.4】下面通过实例演示如何设计单选按钮。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p，并且在其中添加三个按钮jr1、jr2、jr3

10 ///将p添加到顶层框架中

11 public class Swingtest3

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JRadioButton jr1=new JRadioButton（"忽略"）；

29 JRadioButton jr2=new JRadioButton（"继续"）；

30 JRadioButton jr3=new JRadioButton（"跳过"）；

31 Panel p=new Panel（）；

32 p.add（jr1）；

33 p.add（jr2）；

34 p.add（jr3）；

35 jf.add（p, BorderLayout.SOUTH）；

36 jf.show（）；

37 }

38 }

【代码说明】第17～20行创建窗口并设置窗口的大小和标题，第28～34行创建3个单选按钮，并将其添加到Panel中，第35行指定Panel在窗口中的位置。

【运行效果】运行结果如图22.4所示。

图　22.4　添加没有调整的单选按钮

在上面的情况中，3个单选按钮都可以被选中。如果希望一个单选按钮被选中，其他的自动被置为未选中状态，那就使用到按钮组。

ButtonGroup group=new ButtonGroup（）；

【实例22.5】下面修改前面的实例看看结果如何。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p，并且在其中添加三个按钮jr1、jr2、jr3

10 ///将p添加到顶层框架中

11 public class Swingtest4

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JRadioButton jr1=new JRadioButton（"忽略"）；

29 JRadioButton jr2=new JRadioButton（"继续"）；

30 JRadioButton jr3=new JRadioButton（"跳过"）；

31 ButtonGroup bg=new ButtonGroup（）；

32 Panel p=new Panel（）；

33 bg.add（jr1）；

34 bg.add（jr2）；

35 bg.add（jr3）；

36 p.add（jr1）；

37 p.add（jr2）；

38 p.add（jr3）；

39 jf.add（p, BorderLayout.SOUTH）；

40 jf.show（）；

41 }

42 }

【代码说明】第3 1行的设置和上例不同，这里先设置了一个ButtonGroup，然后将3个单选按钮添加到ButtonGroup中，这样生成的单选按钮组中只能有一项被选中。

【运行效果】运行结果如图22.5所示。

图　22.5　添加调整过的单选按钮

22.4.5　复选按钮

复选按钮使用JCheckbox类实现，它跟单选按钮的区别就是一个可以多选，一个只能单选。

【实例22.6】下面通过实例看看复选按钮的用法。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p，并且在其中添加三个按钮jc1、jc2、jc3

10 ///将p添加到顶层框架中

11 public class Swingtest5

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JCheckBox jc1=new JCheckBox（"忽略"）；

29 JCheckBox jc2=new JCheckBox（"继续"）；

30 JCheckBox jc3=new JCheckBox（"跳过"）；

31 jc1.setSelected（true）；

32 Panel p=new Panel（）；

33 p.add（jc1）；

34 p.add（jc2）；

35 p.add（jc3）；

36 jf.add（p, BorderLayout.SOUTH）；

37 jf.show（）；

38 }

39 }

【代码说明】第28～30行创建了3个复选框，第31行设置jc1这个复选框被选中。第36行指明Panel在窗口中的位置。

【运行效果】运行结果如图22.6所示。

22.4.6　ToggleButton按钮

图　22.6　添加复选按钮

ToggleButton按钮就是当单击按钮时，按钮会呈现被按下的状态，再单击一下，可以回复原先状态，其使用JToggleButton类来实现。其实它的使用与前面所有的按钮控件一样，只不过功能不同而已。

【实例22.7】下面看一个有关单击按钮的实例。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p，并且在其中添加三个按钮jt1、jt2、jt3

10 ///将p添加到顶层框架中

11 public class Swingtest6

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JToggleButton jt1=new JToggleButton（"忽略"）；

29 JToggleButton jt2=new JToggleButton（"继续"）；

30 JToggleButton jt3=new JToggleButton（"跳过"）；

31 Panel p=new Panel（）；

32 p.add（jt1）；

33 p.add（jt2）；

34 p.add（jt3）；

35 jf.add（p, BorderLayout.SOUTH）；

36 jf.show（）；

37 }

38 }

【代码说明】第28～30行创建了3个ToggleButton按钮，使用方式与其他控件类似，这里不再复述。第35行将Panel添加到窗口并设置其显示位置。

【运行效果】在上面的代码中，出现了3个ToggleButton按钮，当按下任意一个按钮后，按钮就会发生一定的变化。默认情况下，就是按钮的颜色变深。使用这种按钮可以在应用程序中，告诉用户这个按钮已经单击过了，运行结果如图22.7所示。当单击“忽略”按钮后，如图22.8所示，这个按钮的颜色变深了，这就是ToggleButton按钮。

图　22.7　添加ToggleButton按钮

图　22.8　单击了忽略后的ToggleButton按钮

22.5　简单文本输入组件的设计及实例

Swing类库中的文本框，通过JTextField类和JTextArea类实现，下面将详细介绍它们的使用。

22.5.1　文本域的设计

把一个文本域添加到窗口的步骤，首先是将文本域添加到一个面板中，再将这个面板添加到容器中。文本域的构造器如下：

❑JTextField textField=new JTextField（）；

创建一个没有内容的文本域。

❑JTextField textField=new JTextField（String str）；

创建一个有str内容的文本域。

❑JTextField textField=new JTextField（int columns）；

创建一个有columns列的文本域。

【实例22.8】下面通过简单实例，演示如何设计一个文本域。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p并且将文本域对象添加到其中

10 ///将p添加到顶层框架

11 public class Swingtest7

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JTextField jt=new JTextField（10）；

29 Panel p=new Panel（）；

30 p.add（jt）；

31 jf.add（p, BorderLayout.CENTER）；

32 jf.show（）；

33 }

34 }

【代码说明】第23～26行设置窗口居中，第28行创建一个文本框，第31行将Panel添加到窗体中，并设置显示的位置。

【运行效果】程序运行结果如图22.9所示。

图　22.9　添加文本域组件

22.5.2　密码域的设计

密码域一般用在登录窗口等地方。Swing类库中使用JPasswordField类实现密码域，它的作用就是让所有输入内容以“*”形式出现，这样就不会看到用户的密码了。

【实例22.9】下面演示一个有关密码域的实例。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p并且将密码域对象添加到其中

10 ///将p添加到顶层框架

11 12 public class Swingtest8

13 {

14 static final int WIDTH=300；

15 static final int HEIGHT=200；

16 public static void main（String[]args）

17 {

18 JFrame jf=new JFrame（）；

19 jf.setSize（WIDTH, HEIGHT）；

20 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

21 jf.setTitle（"学生管理系统"）；

22 Toolkit kit=Toolkit.getDefaultToolkit（）；

23 Dimension screenSize=kit.getScreenSize（）；

24 int width=screenSize.width；

25 int height=screenSize.height；

26 int x=（width-WIDTH）/2；

27 int y=（height-HEIGHT）/2；

28 jf.setLocation（x, y）；

29 JPasswordField jp=new JPasswordField（10）；

30 Panel p=new Panel（）；

31 p.add（jp）；

32 jf.add（p, BorderLayout.CENTER）；

33 jf.show（）；

34 }

35 }

【代码说明】第29行创建密码文本框，这里没有指定显示字符，而是使用了默认的“*”来显示用户输入的内容。我们经常在银行的ATM机上碰到的密码输入框就是这个效果。

图　22.10　添加密码域

【运行效果】运行结果如图22.10所示。

22.5.3　如何设计文本区

文本区相当于多行文本框，使用JTextArea类实现。下面是它的构造器。

❑JTextArea textarea=new JTextArea（int x, int y）

创建一个x行y列的文本区。

❑JTextArea textarea=new JTextArea（String str）

创建一个有初始文本str的文本区。

当文本过长，超过了显示范围，应该把多余的剪掉。可通过下面的方法自动换行，以保证多余文本不会被剪掉。

textArea.setLineWrap（true）；

【实例22.10】下面演示一个有关文本区的实例。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p并且将文本区对象添加到其中

10 ///将p添加到顶层框架

11 public class Swingtest9

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JTextArea jt=new JTextArea（5，5）；

29 Panel p=new Panel（）；

30 p.add（jt）；

31 jf.add（p, BorderLayout.CENTER）；

32 jf.show（）；

33 }

34 }

【代码说明】第28行创建一个5行5列的文本区，第31行将其添加到窗口中，并设置显示的位置。

【运行效果】运行结果如图22.11所示。

图　22.11　添加文本区

22.6　标签组件的设计及实例

标签组件的设计非常简单，通过JLabel类实现。下面是标签组件的构造器。

❑JLabel jl=new JLabel（）；

创建一个空的标签对象。

❑JLabel jl=new JLabel（String str）；

创建一个有字符串str的标签对象。

❑JLabel jl=new JLabel（String str, constant Location）；

创建一个带有字符串的标签，并且能够设定内容的对齐方式。对齐方式Location有4种，分别是LEFT、RIGHT、CENTER、NORTH。

【实例22.11】下面看一个有关它的实例。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p并且将标签对象添加到其中

10 ///将p添加到顶层框架

11 public class Swingtest10

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 JLabel jl=new JLabel（"学生管理"，JLabel.RIGHT）；

29 Panel p=new Panel（）；

30 p.add（jl）；

31 jf.add（p, BorderLayout.CENTER）；

32 jf.show（）；

33 }

34 }

【代码说明】第28行创建一个标签，其中构造器使用了参数JLabel.RIGHT，表示右对齐。

【运行效果】运行结果如图22.12所示。

图　22.12　添加标签

22.7　选择组件的设计及实例

选择组件有很多种，在这里主要讲述组合列表框。组合列表框就相当于常说的下拉列表框，它使用JComboBox类实现。如何设计一个组合列表框呢？

【实例22.12】要使用数组来设计，下面通过一个实例来说明。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///创建一个容器类对象p并且将JComboBox对象添加到其中

10 ///将p添加到顶层框架

11 public class Swingtest11

12 {

13 static final int WIDTH=300；

14 static final int HEIGHT=200；

15 public static void main（String[]args）

16 {

17 JFrame jf=new JFrame（）；

18 jf.setSize（WIDTH, HEIGHT）；

19 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 jf.setTitle（"学生管理系统"）；

21 Toolkit kit=Toolkit.getDefaultToolkit（）；

22 Dimension screenSize=kit.getScreenSize（）；

23 int width=screenSize.width；

24 int height=screenSize.height；

25 int x=（width-WIDTH）/2；

26 int y=（height-HEIGHT）/2；

27 jf.setLocation（x, y）；

28 String[]a=new String[5]；

29 a[0]="王浩"；

30 a[1]="张敏"；

31 a[2]="李浩"；

32 a[3]="孙军"；

33 a[4]="周平"；

34 JComboBox jc=new JComboBox（a）；

35 Panel p=new Panel（）；

36 p.add（jc）；

37 jf.add（p, BorderLayout.SOUTH）；

38 jf.show（）；

39 }

40 }

【代码说明】第28～33行创建了一个数组，其中包含5个元素。第34行创建选择组件，第37行将其添加到窗口中。

【运行效果】运行结果如图22.13所示。

图　22.13　添加组合列表框

22.8　菜单的组件设计

菜单的设计是每个窗体必须要注意的事情，因为设计好的菜单，可以让使用者直观地了解系统的功能。我们通过Word中的菜单，就可以大概知道Word能完成哪些功能。

【实例22.13】下面通过一个实例，学习如何在Swing中创建一个菜单。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///通过ToolKit类中的方法，将顶层框架放置到屏幕中央

09 ///通过构造菜单条对象menubar1，菜单对象menu1、menu2、menu3及菜单项对象来构造一个菜单

10 ///创建一个容器类对象p并且将菜单对象添加到其中

11 ///将p添加到顶层框架

12 public class Swingtest12

13 {

14 static final int WIDTH=300；

15 static final int HEIGHT=200；

16 public static void main（String[]args）

17 {

18 JFrame jf=new JFrame（）；

19 jf.setSize（WIDTH, HEIGHT）；

20 jf.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

21 jf.setTitle（"学生管理系统"）；

22 Toolkit kit=Toolkit.getDefaultToolkit（）；

23 Dimension screenSize=kit.getScreenSize（）；

24 int width=screenSize.width；

25 int height=screenSize.height；

26 int x=（width-WIDTH）/2；

27 int y=（height-HEIGHT）/2；

28 jf.setLocation（x, y）；

29 JMenuBar menubar1=new JMenuBar（）；

30 jf.setJMenuBar（menubar1）；

31 JMenu menu1=new JMenu（"文件"）；

32 JMenu menu2=new JMenu（"编辑"）；

33 JMenu menu3=new JMenu（"视图"）；

34 JMenu menu4=new JMenu（"帮助"）；

35 menubar1.add（menu1）；

36 menubar1.add（menu2）；

37 menubar1.add（menu3）；

38 JMenuItem item1=new JMenuItem（"打开"）；

39 JMenuItem item2=new JMenuItem（"保存"）；

40 JMenuItem item3=new JMenuItem（"打印"）；

41 JMenuItem item4=new JMenuItem（"退出"）；

42 menu1.add（item1）；

43 menu1.add（item2）；

44 menu1.addSeparator（）；

45 menu1.add（item3）；

46 menu1.addSeparator（）；

47 menu1.add（item4）；

48 jf.show（）；

49 }

50 }

【代码说明】第29行创建一个菜单栏，第31～34行创建4个菜单，第35～37行将菜单添加到菜单栏。第38～41行设计4个菜单项，第42～47将菜单项添加到菜单中，其中第44行表示添加一个间隔条。

【运行效果】运行结果如图22.14所示。

图　22.14　添加菜单

22.9　对话框的种类及用法

对话框应该不是什么陌生的概念，在平时应用中会经常遇到，如在Windows中我们就经常看到对话框的身影。本节将讲述如何设计Swing类库中的对话框。

对于选项对话框，Swing提供了一个很方便的类JOptionPane，该类能够让程序员不需要编写代码，就可以生成一个对话框。其主要提供了一些简单的对话框，用于收集用户的一个简单信息。

JOptionPane类有4个静态的方法，下面列出这些简单的对话框。

❑showMessageDialog：显示一条消息并且等待用户单击OK。

❑showConfirmDialog：显示一条消息并且得到确认。

❑showOptionDialog：显示一条消息并且得到用户在一组选项中选择。

❑showInputDialog：显示一条消息并且得到用户的一行输入。

一个典型的对话框主要包括以下几个部分：

❑一个图标

❑一条消息

❑一个或多个选项按钮

❑对话框标题

对话框的消息类型主要有下列5种。

❑ERROR_MESSAGE

❑INFORMATION_MESSAGE

❑WARNING_MESSAGE

❑QUESTION_MESSAGE

❑PLAIN_MESSAGE

可以指定一条消息显示在对话框上，该消息可以是字符串、图标、一个用户界面组件或者其他对象。下面列出各种消息对象的显示方式：

❑String：绘制该字符串。

❑Icon：显示该图标。

❑Component：显示该组件。

❑Object[]：逐一显示每个对象，依次叠加。

❑其他对象：调用toString（）方法，显示相应的结果字符串。

调用showMessageDialog和showInputDialog时，只能得到标准按钮，分别是OK和OK/CANCEL。而调用shwoConfirmDialog时，可以根据不同的需要，选择如下4种选项类型中的一种。

❑DEFAULT_OPTION

❑YES_NO_OPTION

❑YES_NO_CANCEL_OPTION

❑OK_CANCEL_OPTION

【实例22.14】下面演示一个有关构造对话框的实例。

01 import java.awt.*；

02 import javax.swing.*；

03 public class Swingtest13 extends Frame

04 {

05 public static void main（String[]args）

06 {

07 JOptionPane.showConfirmDialog（null，"这是错误信息！"，"这是错误信息！

08"，JOptionPane.YES_NO_CANCEL_OPTION）；

09 }

10 }

【代码说明】第3行表示该类继承自Frame类，第7～8行弹出对话框，这里有3个按钮，分别对应YES、NO、CANCEL。

【运行效果】运行结果如图22.15所示。

图　22.15　添加对话框

创建一个对话框不难，关键是当用户选择按钮时，系统能区分这个动作。通常系统会返回一个整数以区分不同的动作，整数值从0开始。例如，YES_NO_OPTION中0表示YES，1表示NO。

22.10　复杂布局管理器的种类及用法

布局管理就是在界面中，设计哪些控件对齐，哪些控件在同一个范围内，还有控件该在什么地方显示。本节将介绍一些复杂布局管理器，其中包括箱式布局管理器和网格组布局管理器。

22.10.1　箱式布局的设计

箱式布局比GridLayout布局要灵活的多，这也是它比较实用的地方。Swing提供的BOX类就是箱式布局类，它的默认布局管理器就是BoxLayout，在箱式布局管理器中包括了两种箱子：一种是水平箱，另外一种是垂直箱。

说明　GridLayout是最简单的一种布局，其包含在java.awt.*中，这里没有单独介绍。

创建一个水平箱：

Box horBox=Box.createHorizontalBox（）；

创建一个垂直箱：

Box verBox=Box.createVerticalBox（）；

创建好箱子后，就可以像添加其他组件一样，添加控件，代码如下所示。

horBox.add（okButton）；

verBox.add（cancelButton）；

两种箱子的区别在于组件的排列顺序上，水平箱是按照从左到右的顺序排列，而垂直箱按照从上到下的顺序排列。对于箱式布局管理器，最关键的就是每个组件的三个尺寸。

❑首选尺寸，即组件显示时的宽度和高度。

❑最大尺寸，即组件能显示的最大宽度和最大高度。

❑最小尺寸，即组件被显示的最小高度和最小宽度。

下面是水平箱式布局管理器中组件排列的几个重点：

❑计算最高组件的最大高度，尝试把所有的组件都增加到这个高度。如果有某些组件不能达到这个高度，那么在Y轴上对齐要通过“getAlignmentY”方法得到，该方法返回一个介于0（按顶部对齐）和1（按底部对齐）之间的浮点数。组件默认是0.5，也就是中线对齐。

❑得到每个组件的首选宽度，然后把所有的首选宽度合计起来。

❑如果首选宽度总和小于箱的宽度，那么所有的组件都会相应地延伸，直到适应这个箱子的宽度。组件从左到右排列，并且相邻两个组件之间没有多余的空格。

前面介绍过，箱式布局组件之间没有空隙，那么就要通过一个称为填充物的组件来提供空隙。箱式布局管理器提供了三种填充物：支柱、固定区、弹簧。

【实例22.15】在这里先举例，然后再针对实例进行分析。

01 import javax.swing.*；

02 import java.awt.*；

03 ///创建一个顶层框架类

04 ///WIDTH参数是指框架的宽度

05 ///HEIGHT参数是指框架的长度

06 ///setTitle用来设置框架左上角的标题

07 ///setSize用来设置框架的大小

08 ///产生不同的箱子布局管理器对象，每个对象放置不同的控件

09 public class Swingtest14

10 {

11 12

public static void main（String[]args）

13 {

14 BoxLayoutFrame frame1=new BoxLayoutFrame（）；

15 frame1.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

16 frame1.show（）；

17 }

18 }

19 class BoxLayoutFrame extends JFrame

20 {

21 private static final int WIDTH=300；

22 private static final int HEIGHT=200；

23 public BoxLayoutFrame（）

24 {

25 setTitle（"箱式布局管理器"）；

26 setSize（WIDTH, HEIGHT）；

27 Container con=getContentPane（）；

28 JLabel label1=new JLabel（"姓名："）；

29 JTextField textField1=new JTextField（10）；

30 textField1.setMaximumSize（textField1.getPreferredSize（））；

31 Box hbox1=Box.createHorizontalBox（）；

32 hbox1.add（label1）；

33 hbox1.add（Box.createHorizontalStrut（20））；

34 hbox1.add（textField1）；

35 JLabel label2=new JLabel（"密码："）；

36 JTextField textField2=new JTextField（10）；

37 textField2.setMaximumSize（textField2.getPreferredSize（））；

38 Box hbox2=Box.createHorizontalBox（）；

39 hbox2.add（label2）；

40 hbox2.add（Box.createHorizontalStrut（20））；

41 hbox2.add（textField2）；

42 JButton button1=new JButton（"确定"）；

43 JButton button2=new JButton（"取消"）；

44 Box hbox3=Box.createHorizontalBox（）；

45 hbox3.add（button1）；

46 hbox3.add（button2）；

47 Box vbox=Box.createVerticalBox（）；

48 vbox.add（hbox1）；

49 vbox.add（hbox2）；

50 vbox.add（Box.createVerticalGlue（））；

51 vbox.add（hbox3）；

52 con.add（vbox, BorderLayout.CENTER）；

53 }

54 }

【代码说明】这个程序的含义是先创建3个水平箱，再创建一个垂直箱。将3个水平箱添加到垂直箱中。

【运行效果】运行结果如图22.16所示。

在下一小节中，将介绍一种比较人性化的布局管理器：网格组布局管理器，这种布局管理器最大的好处在于，可以由用户自己来分配控件的空间位置，它结合了很多种布局管理器的优点。

图　22.16　使用箱式布局管理器

22.10.2　网格组布局的设计

布局管理器是一种很先进的布局管理器，通过网格的划分，可看到每个组件都占据一个网格，也可以一个组件占据几个网格。如果要采用网格组布局管理器，一般来说可以采用下列步骤：

（1）创建一个GridBagLayout对象。

（2）将容器设成此对象的布局管理器。

（3）创建约束（GridBagConstraints）对象。

（4）创建各个相应的组件。

（5）添加各个组件与约束到网格组布局管理器中。

网络组由多个网格组成，而且各个行或者列的长度和宽度不同。但默认的情况下，单元格从左上角开始有序列的编号，从第0行第0列开始计数。

当向网格组布局管理器中添加组件时，分别定义每个单元格的序列号，只要设定相应的值，那么组件就会添加到网格组布局管理器中。gridX、gridY分别定义了添加组件时左上角的行与列位置，而gridwidth、gridheight分别定义了组件所占用的列数和行数。

网格组布局管理器中每个区域都要设置增量字段（weightx与weighty，分别代表x方向和y方向的增量）。如果想让某个区域保持初始化的大小，也就是说窗口缩放不会引起组件缩放，那就应该设置该区域的增量为0，相反如果让组件能完全保证填充单元格，那增量字段就应该设置为100。

fill和anchor参数都是非常重要的约束，其中fill是当组件不能填满单元格时，该参数就可以发挥作用。该约束的值主要有以下几种。

❑GridBagConstraints.NONE：在每一个方向都不填充，即保持原状。

❑GridBagConstraints.HORIZONTAL：只在水平方向上填充。

❑GridBagConstraints.VERTICAL：只在垂直方向上填充。

❑GridBagConstraints.BOTH：在两个方向上都填充。

而anchor参数则是当一个组件大于分配给它的单元格时发挥作用。该约束就是约定如何处理该组件，它的值如下所示。

❑GridBagConstraints.CENTER：居中缩小。

❑GridBagConstraints.NORTH：顶部缩小。

❑GridBagConstraints.NORTHEAST：左上角缩小。

❑GridBagConstraints.EAST：右侧缩小。

22.11　通过实例熟悉Swing编程

【实例22.16】本小节给出一个综合实例，希望读者能够真正掌握如何使用Swing编程。

01 import java.awt.*；

02 import javax.swing.*；

03 ///主运行类

04 ///frame1指主框架

05 ///setDefaultCloseOperation指让框架右上角的按钮都有效

06 ///show让框架能显示出来

07 public class Swingtest15

08 {

09 public static void main（String[]args）

10 {

11 gridbaglayoutframe frame1=new gridbaglayoutframe（）；

12 frame1.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

13 frame1.show（）；

14 }

15 }

16 ///创建一个顶层框架类

17 ///WIDTH参数是指框架的宽度

18 ///HEIGHT参数是指框架的长度

19 class gridbaglayoutframe extends JFrame

20 {

21 private static final int WIDTH=300；

22 private static final int HEIGHT=200；

23 ///顶层框架的构造器

24 ///setTitle用来设置框架左上角的标题

25 ///setSize用来设置框架的大小

26 ///设置一个容器类的对象con

27 ///通过容器类对象进行添加控件，这里的控件是以类的对象形式出现的

28 public gridbaglayoutframe（）

29 {

30 setTitle（"学生管理系统"）；

31 setSize（WIDTH, HEIGHT）；

32 Container con=getContentPane（）；

33 con.add（new studentJPanel（），BorderLayout.CENTER）；

34 con.add（new buttonpanel（），BorderLayout.SOUTH）；

35 }

36 }

37 ///创建一个学生标签和文本域及文本区所在的容器类

38 class studentJPanel extends JPanel

39 {

40 ///此容器类的构造器

41 ///namelabel标签是指显示为"姓名"的标签

42 ///codelabel标签是指显示为"　学号"的标签

43 ///sexlabel标签是指显示为"　性别"的标签

44 ///addresslabel标签是指显示为"籍贯"的标签

45 ///commentlabel标签是指显示为"简单介绍"的标签

46 ///nametextfield文本域是指显示姓名的文本域

47 //codetextfield文本域是指显示　学号的文本域

48 ///sextextfield文本域是指显示　性别的文本域

49 //addressCombo文本域是指显示地点的组合列表框

50 //commenttextfield文本域是指显示简单介绍的文本区

51 ///Constraints表示创建一个网格组布局管理器对象

52 ///add方法是添加组件的函数，在后面有这个函数的具体内容

53 ///constraints.fill和constraints.anchor指定了控件的摆放方式

54 ///constraints.weightx和constraints.weighty分别是指控件在容器中总共需要占据多少行与列

55 public studentJPanel（）

56 {

57 GridBagLayout layout=new GridBagLayout（）；

58 setLayout（layout）；

59 JLabel namelabel=new JLabel（"姓名："）；

60 JTextField nameTextField=new JTextField（10）；

61 JLabel codelabel=new JLabel（"　学号："）；

62 JTextField codeTextField=new JTextField（10）；

63 JLabel sexlabel=new JLabel（"　性别："）；

64 JTextField sexTextField=new JTextField（10）；

65 JLabel addresslabel=new JLabel（"籍贯："）；

66 JComboBox addressCombo=new JComboBox（new String[]{"江西"，"四川"，"山西"，

67 "湖北"，"湖南"，"海南"}）；

68 JLabel commentLabel=new JLabel（"简单介绍"）；

69 JTextArea sample=new JTextArea（）；

70 sample.setLineWrap（true）；

71 GridBagConstraints constraints=new GridBagConstraints（）；

72 constraints.fill=GridBagConstraints.NONE；

73 constraints.anchor=GridBagConstraints.EAST；

74 constraints.weightx=5；

75 constraints.weighty=5；

76 add（namelabel, constraints，0，0，1，1）；

77 add（codelabel, constraints，0，1，1，1）；

78 add（sexlabel, constraints，0，2，1，1）；

79 add（addresslabel, constraints，0，3，1，1）；

80 constraints.fill=GridBagConstraints.HORIZONTAL；

81 constraints.weightx=100；

82 add（nameTextField, constraints，1，0，1，1）；

83 add（codeTextField, constraints，1，1，1，1）；

84 add（sexTextField, constraints，1，2，1，1）；

85 add（addressCombo, constraints，1，3，1，1）；

86 constraints.fill=GridBagConstraints.NONE；

87 constraints.anchor=GridBagConstraints.CENTER；

88 add（sample, constraints，2，0，1，1）；

89 constraints.fill=GridBagConstraints.BOTH；

90 add（sample, constraints，2，1，1，1）；

91 }

92 ///用来添加组件到容器中的函数

93 ///constraints.gridx指该控件位于哪一列

94 ///constraints.gridy指该控件位于哪一行

95 ///constraints.gridwidth指该控件占据多少列

96 ///constraints.gridheight指该控件占据多少行

97 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

98 {

99 constraints.gridx=x；

100 constraints.gridy=y；

101 constraints.gridwidth=w；

102 constraints.gridheight=h；

103 add（c, constraints）；

104 }

105 }

106 ///一个放置按钮控件的容器类

107 ///okbutton是指确定按钮

108 ///cancelbutton是指取消按钮

109 ///hBox指箱式控制器中的空白条之类的东西

110 class buttonpanel extends JPanel

111 {

112 public buttonpanel（）

113 {

114 setLayout（new BoxLayout（this, BoxLayout.X_AXIS））；

115 JButton okbutton=new JButton（"确定"）；

116 JButton cancelbutton=new JButton（"取消"）；

117 Box hBox=Box.createHorizontalBox（）；

118 hBox.add（Box.createHorizontalStrut（40））；

119 hBox.add（okbutton）；

120 hBox.add（Box.createHorizontalGlue（））；

121 hBox.add（cancelbutton）；

122 hBox.add（Box.createHorizontalStrut（40））；

123 add（hBox）；

124 }

125 }

【代码说明】第1 9～3 6行创建一个顶层框架类gridbaglayoutframe，并继承自JFrame。第38～105行创建一个学生标签和文本域及文本区所在的容器类。第110～125行创建一个放置按钮的容器类。

图　22.17　使用网格组布局管理器的综合实例

【运行效果】运行结果如图22.17所示。

22.12　常见疑难解答

22.12.1　如何处理菜单的启用和禁用功能

答：当打开一个只读文件时，不允许保存和另存为，此时可以使用菜单项的禁用和启用功能。在菜单监听器接口MenuListener中声明了三个方法，如下所示。

public void menuSelected（MenuEvent event）

public void menuDeSelected（MenuEvent event）

public void menucanceled（MenuEvent event）

menuSelected方法在菜单项被显示前会自动调用，因此在这个时候可以进行菜单项的相关设置。如果选择了只读打开，那么相应的保存项就应该设置为不可用，可以按照下面的方法进行编写代码。

Public void menuSelected（MenuEvent event）

{

savaItem.setEnabled（！readOnlyItem.isSelected（））；

}

！readOnlyItem. isSelected返回一个布尔型值。这段代码的含义就是让saveItem与readOnlyItem菜单项选择状态相反，所以采用了“非”操作。

22.12.2　如何编写快捷键的程序

答：其实很简单，可以按照下面的代码方式定义一个快捷键。

JMenu save=new JMenu（"save"）；

Save.setMnemonic（'s'）；

这里将save菜单项的快捷键定义为“s”。

22.13　小结

Awt图形编程和Swing图形编程都是Java GUI图形开发的基础，但AWT功能有限，所以Java提供了Swing GUI用户类库来细化和完善图形开发的技术。本章实现了一些常见的图形界面，包括按钮、文本框、标签等，尤其是最后还介绍了布局管理器，让读者了解Java程序中界面是如何进行布局的。希望通过本章的学习，读者能对图形编程有进一步的了解。

22.14　习题

1.Java是使用______来进行布局管理的。

2.BorderLayout把界面分成哪几个部分？

3.Swing是如何处理事件的？

4.描述一下Swing中添加菜单的过程。

第23章　Eclipse的使用

本章将介绍一个功能更加强大的开发工具：Eclipse。Eclipse是替代IBM Visual Age for Java（以下简称IVJ）的下一代IDE开发环境，但它未来的目标不仅仅是成为专门开发Java程序的IDE环境。根据Eclipse的体系结构，通过开发插件，它能扩展到支持任何语言的开发。目前，Eclipse已经提供C语言和PHP语言开发的功能插件。

更难能可贵的是，Eclipse是一个开放源代码的项目。任何人都可以下载Eclipse的源代码，并且在此基础上开发自己的功能插件。例如，在现有的Java开发环境中加入Tomcat服务器插件。Eclipse可以无限扩展，而且有着统一的外观、操作和系统资源管理，这也正是其潜力所在。

本章重点：

❑认识Eclipse的界面。

❑学习用Eclipse开发工程。

❑掌握在Eclipse中开发完整案例的过程。

23.1　Eclipse简介

2001年11月，IBM宣布捐出4000万美元，用来开发软件给开放源码的Eclipse项目。如此受青睐的Eclipse是什么样子？如何使用？本章会使读者对Eclipse有一个初步的认识。

虽然目前Eclipse项目还在不断进行升级，但从已有的版本中，已经能领略到Eclipse设计的主导思想和主要功能特点。

如果参加到Eclipse项目的开发中，或阅读其开放源代码，对程序员而言，是提高编程水平的好机会。Eclipse计划提供多个平台的版本，像Windows、Linux、Solaris、HP-UX和AIX，以下只介绍Windows版本。

Eclipse项目分成三个子项目，如下所示。

❑平台—Platform

❑开发工具箱—Java Development Toolkit（JDT）

❑外挂开发环境—Plug-in Development Environment（PDE）

这些子项目又细分成更多子项目。例如Platform子项目包含数个组件，如Compare、Help与Search。JDT子项目包括三个组件：User Interface（UI）、核心（Core）及排错（Debug）。下面介绍一下Eclipse版本方面的知识。

可以从eclipse.org网站（http：//www.eclipse.org/downloads）下载，基本上有4种版本供下载：

❑释出版本（Release Builds）。由Eclipse开发团队所宣称的主要稳定版本。

❑稳定版本（Stable Builds）。比Release Builds新一级的版本，经由Eclipse开发团队测试，并认定它相当稳定。新功能通常会在此过渡版本出现。

❑整合版本（Integration Builds）。此版本的各个独立的组件已经过Eclipse开发团队认定，它具有一定的稳定度。但不保证整合在一起没问题。若整合在一起够稳定，它就有可能晋级成Stable Builds。

❑当前最新版（Nightly Builds）。此版本显然是从最新的原始码中产生出来的。此版本在运行时，很可能会出现一些不可预知的错误。

23.2　Eclipse界面简介

当打开Eclipse的界面时，会出现如图23.1所示的界面。如果使用过JBuilder这个开发工具，会觉得以上这个界面与JBuilder有点相似。其实无论哪种工具，它的使用方法几乎差不多。下面将针对这个界面，来了解如何使用Eclipse进行应用程序开发。

图　23.1　Eclipse软件界面

Eclipse平台由数种组件组成：平台核心（platform kernel）、工作台（workbench）、工作区（workspace）、团队组件（tream component）以及说明组件（help）。

若要手动操作文件、复制或查看文件的大小，就必须知道文件放在哪里。但原文件系统会随着操作系统的改变而改变，这在不同操作系统下运行同一个程序可能会发生问题。为了解决此问题，Eclipse在文件系统之上提供了一个抽象层级。换句话说，它不使用内含文件的阶层式目录/子目录结构，反之，Eclipse在最高层级使用项目，并在项目之下使用文件夹。

说明

根据预设，项目对应workspace目录下的子目录，而文件夹对应项目目录下的子目录。在Eclipse项目内的所有内容均是以独立的方式存在。

1.平台核心

核心的任务是让每样东西动起来，并加载所需的外挂程序。当启动Eclipse时，先执行的就是这个组件，再由这个组件加载其他外挂程序。

2.工作区（workspace）

工作区负责管理使用者的资源，这些资源会被组织成一个或多个项目，摆在最上层。每个项目对应到Eclipse工作区目录下的一个子目录。每个项目可包含多个文件和文件夹。通常每个文件夹对应到一个在项目目录下的子目录，但文件夹也可连到文件系统中的任意目录。

每个工作区维护一个低阶的历史记录，记录每个资源的改变。这样便可以立刻复原改变，回到前一个储存的状态，可能是前一天或几天前，取决于使用者对历史记录的设定。此历史记录可将资源丧失的风险减到最少。

工作区也负责通知相关工具有关工作区资源的改变。工具可为项目标记一个项目性质（project nature），譬如标记为一个“Java项目”。它还可在必要时提供配置项目资源的程序代码。

3.工作台

工作台是Eclipse中仅次于平台核心的最基本的组件，启动Eclipse后出现的主要窗口就是这个。workbench的工作很简单，它不懂得如何编辑、执行、排错，它只懂得如何找到项目与资源（如文件与文件夹）。若有它不能做的工作，就会丢给其他组件，例如JDT。

工作台看起来像是操作系统内建的应用程序，可以说是Eclipse的特点，也是争议点。工作台本身可以说是Eclipse的图形操作接口，它使用Eclipse自己的标准图形工具箱和JFace的架构。SWT会使用操作系统的图形支持技术，使得程序的外观感觉（look-and-feel）随操作系统而定。这一点和过去多数Java程序的做法很不同，即使是用Swing，也没有这样过。

工作台会有许多不同种类的内部窗口，称之为视图（view），以及一个特别的窗口：编辑器。它之所以称为视图，是因为这些窗口以不同的视野来看整个项目，Outline的视图可以看项目中Java类别的概略状况，而Navigator的视图可以浏览整个项目。视图支持编辑器，且提供工作台中信息的替代、呈现或导览方式。比如：“书签”视图会显示工作台中的所有书签，且会附带书签所关联的文件名称。“Navigator”视图会显示项目和其他资源。

视图有两个菜单：

❑第一个是用鼠标右键按一下视图卷标来存取的菜单，它可以利用类似工作台窗口相关菜单的相同方式来操作视图。

❑第二个菜单称为视图下拉菜单，存取方式是单击向下箭头。视图下拉菜单所包含的作业通常会套用到视图的全部内容，而不是套用到视图中所显示的特定项目。排序和过滤作业通常可在检视下拉菜单中找到。

23.3　如何使用Eclipse进行开发

使用Eclipse进行程序开发，有一个优点就是可以自动补全输入。如果读者使用过Visual Studio等开发工具，应该能很好理解这个意思。如果没有这方面的经验，也没有关系。下面将通过一个实际的编程实例，介绍如何使用它进行软件开发。

23.3.1　如何新建一个Java工程

创建一个Java工程的步骤如下。

（1）打开Eclipse应用程序，单击“New”按钮，弹出如图23.2所示的选择项目对话框。工程在整个程序中作为一个项目组，其相当于一个成形的软件。在这个工程中包含有类、包、接口等元素。把这些元素组合在一起打包成一个应用软件。

说明

在编写程序之前，必须要创建一个工程，否则一切都不可能开始。工程的创建就好比是一个应用程序框架的搭建，只有先创建了工程，才能往这个工程的框架里放入各种各样的元素。

（2）在列表中，选择“Java Project”列表项后，输入这个新建工程的名称，单击“Next”按钮，出现新建工程的对话框，如图23.3所示。

图　23.2　选择项目

图　23.3　产生一个新的工程

注意

如果要产生新的工程，选择“Create new project in workspace”选项，如果在原来工程的基础上再产生一个工程包，就选择“Create project from existing source”。

（3）单击“Finish”按钮完成工程的创建。

23.3.2　如何新建一个Java的类创建类的步骤如下所示。

（1）单击“New”按钮，在打开的选择项目对话框中，选中“Class”列表项，弹出新建类的对话框，如图23.4所示。

（2）单击“Browser”按钮，会出现一个已经拥有工程的浏览图，如图23.5所示。

（3）选择“schoolmanage”项后，再填写好类名。在图中还有类的访问控制符的选择。如果是主运行程序，就选择“public”。如果是一般的类，就选择“default”，如图23.6所示。

（4）单击“Finish”按钮，类创建完毕。

图　23.4　新建一个类

图　23.5　在已有工程创建一个类

图　23.6　输入新类的名称

23.3.3　编写代码

【实例23.1】下面将举一个使用Eclipse工具来编写的代码段。在这段程序中，将创建一个登录的界面系统。

01 import javax.swing.*；

02 import java.awt.*；

03 04 public class login extends JPanel

05 {

06 static final int WIDTH=300；

07 static final int HEIGHT=150；

08 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

09 {

10 constraints.gridx=x；

11 constraints.gridy=y；

12 constraints.gridwidth=w；

13 constraints.gridheight=h；

14 add（c, constraints）；

15 }

16 public static void main（String[]args）

17 {

18 JFrame loginframe=new JFrame（"信息管理系统"）；

19 loginframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

20 login l=new login（）；

21 GridBagLayout lay=new GridBagLayout（）；

22 l.setLayout（lay）；

23 loginframe.add（l, BorderLayout.WEST）；

24 loginframe.setSize（WIDTH, HEIGHT）；

25 Toolkit kit=Toolkit.getDefaultToolkit（）；

26 Dimension screenSize=kit.getScreenSize（）；

27 int width=screenSize.width；

28 int height=screenSize.height；

29 int x=（width-WIDTH）/2；

30 int y=（height-HEIGHT）/2；

31 loginframe.setLocation（x, y）；

32 JButton ok=new JButton（"登录"）；

33 JButton cancel=new JButton（"放弃"）；

34 JLabel title=new JLabel（"信息系统登录窗口"）；

35 JLabel name=new JLabel（"用户名"）；

36 JLabel password=new JLabel（"密码"）；

37 JTextField nameinput=new JTextField（15）；

38 JPasswordField passwordinput=new JPasswordField（15）；

39 GridBagConstraints constraints=new GridBagConstraints（）；

40 constraints.fill=GridBagConstraints.NONE；

41 constraints.anchor=GridBagConstraints.EAST；

42 constraints.weightx=3；

43 constraints.weighty=4；

44 l.add（title, constraints，0，0，4，1）；

45 l.add（name, constraints，0，1，1，1）；

46 l.add（password, constraints，0，2，1，1）；

47 l.add（nameinput, constraints，2，1，1，1）；

48 l.add（passwordinput, constraints，2，2，1，1）；

49 l.add（ok, constraints，0，3，1，1）；

50 l.add（cancel, constraints，2，3，1，1）；

51 loginframe.setResizable（false）；

52 loginframe.show（）；

53 }

54 }

【代码说明】从第32～38行可以看出这些代码是界面的组成元素，包括按钮和文本框。第37行是普通的文本框，第38行是一个密码文本框。第44～50行是将这些按钮、标签等元素添加到窗体中。第52行显示窗体。

【运行效果】将程序代码写入工作区前面创建的相应的类中。如果有错误，在图中会自动显示出来。若图中有叉叉、问号等之类的红色符号，那就是代表此语句有错误，如图23.7所示。单击工具栏中的“运行”按钮，结果如图23.8所示。

图　23.7　代码的编写

图　23.8　运行窗口

23.4　常见疑难解答

23.4.1　Eclipse和UltraEdit两个开发工具的优缺点

答：UltraEdit是共享软件，官方网址为www.ultraedit.com。它是一个功能强大的文本、HTML、程序源代码编辑器。作为源代码编辑器，它的默认配置可以对C/C++、VB、HTML、Java和Perl进行语法着色。什么是语法着色呢？所谓的语法着色，就是对于一个开发语言中的关键字给予颜色上的区分。用它设计Java程序时，可以对Java的关键词进行识别并着色，方便了Java程序设计。它具有完备的复制、粘贴、剪切、查找、替换、格式控制等编辑功能。可以在Advanced菜单的Tool Configuration菜单项配置好Java的编译器Javac和解释器Java，直接编译运行Java程序。

Eclipse是一个开放可扩展的集成开发环境（IDE）。它不仅可以用于Java的开发，并通过开发插件，还可以构建其他的开发工具。E c l i p s e是开放源代码的项目，并可以免费下载，官方网址为www.eclipse.org，并提供ReleaseBuilds、Stable Builds、Integration Builds和Nightly Builds下载。建议使用ReleaseBuilds或Stable Builds版本。

ReleaseBuilds版本是Eclipse开发团队发布的主要发行版本，是经过测试的稳定的版本，适合要求稳定而不需要最新改进功能的使用者选择。目前最新的ReleaseBuilds版本是Eclipse 3.0。

Stable Builds版本是对大多数使用者足够稳定的版本，由开发团队将认为比较稳定的Integration Builds版本提升到Stable Builds而来，适合想使用Eclipse新功能的使用者选择。对于ReleaseBuilds版本2.1.x，在Eclipse的官方网站上有一个语言包可以下载，这样Eclipse及其帮助都是简体中文的。在Java开发上，Eclipse与UltraEdit、EditPlus两种编辑器和JCreator IDE比较，Eclipse更专业、功能更强大。

23.4.2　什么是IDE环境

答：IDE环境就是集成开发环境，在其中可以非常方便地编写程序。在命令行下执行客户程序，输入客户程序名字按下“Enter”键就可以。

集成开发环境，英文是Integrated Development Environment。比如，过去编写程序，用文本编辑软件编写源程序，用编译程序对其进行编译而生成OBJ文件（目标文件），再用连接程序将OBJ文件与库文件（LIB文件）连接，而成为可执行的EXE文件；还要对程序进行调试。整个过程很复杂。而IDE通常集编辑、编译、连接、调试为一体，使得程序的开发变得很方便。用的Turbo C、Turbo Pascal、Borland C++、Visual C++、Delphi等，都有很好的IDE在里面，通常说的用它们编程序，实际都是在它们的IDE中进行工作。

23.4.3　Eclipse与JBuilder两个开发工具哪个好

答：Eclipse好，很多人都这样认为。JBuilder能做的，Eclipse基本上都能做到。而且做的也不比JBuilder差。

Eclipse也有可视化界面，不过需要插件而已。Swt designer用于可视化开发，至于Awt和Swing的开发，使用vep这些插件，需要下载并配置。

现在很多人都开始使用Eclipse，它的速度很快，一般的机器都可以用Eclipse。而JBuilder的开发对配置要求太高，如果电脑配置太低，程序又比较庞大，那运行的速度可以让人发疯。

23.4.4　有关Eclipse的编译报错的原因

答：（1）先将JDK对应的Eclipse版本安装上，然后设置path环境变量。

（2）将Eclipse目录下的configuration文件夹里面的文件，除config.ini文件外，其他全部删除。

（3）最后启动Eclipse即可。

（4）在Eclipse的Preferences里可以找到设置JDK路径的地方，重新设置一下。

23.5　小结

每种开发语言都有至少一种开发工具，如开发C语言的TC工具，开发C++语言的Visual Studio工具。前面介绍的Java开发，都是建议读者直接写在文本文档中，因为通过手写这些类和方法，可以加深读者对它们的了解，比如说大小写错误可能就导致程序无法运行。但如果你已经了解了这些基础知识，想编写大型程序，那可以学习本章的开发工具Eclipse，它可以提高开发速度。

23.6　习题

1.Eclipse中的工作环境是______。

2.工作空间与项目是什么关系？

3.Eclipse提供了什么样的特有扩展机制？

第六篇　Java程序设计网络篇

第24章　Applet设计

Applet编程是Java编程语言至关重要的独特功能。它不同于一般的Java程序，其能够嵌入到HTML网页中，并由支持Java的Web浏览器解释执行。只有使用IE浏览器3.0以上的版本，才能支持Java小应用程序。有了这种小应用程序，所设计的网页就能有一定的交互功能。本章将学习有关Applet的知识，以及如何使用Applet编写应用程序段。

本章重点：

❑Applet的生命周期。

❑Applet的声音和图像处理功能。

❑在浏览器中Applet的使用。

❑Applet的安全问题。

24.1　Applet的基本概念及生命周期

Applet是在Web浏览器中运行的Java小应用程序，它能够嵌入到HTML页面中，并可以通过Web浏览器下载和执行。一个Java程序要想运行，必须编写主运行函数，把所有要运行的内容放到主运行函数中，就可以运行了。但是在Applet小程序中，并不需要主运行函数，它由Web浏览器中的Java虚拟机调用执行。

Applet类位于java.applet包中，其继承自Panel类，所以也是一个容器，可以包含AWT组件。为了能够对Applet有一个很好的理解，先看一个很简单的实例，学习如何制作Applet应用小程序。

import java.applet.*；

public class test1 extends Applet

{

}

然后在同一个目录中，建立一个以html为扩展名的文件。

＜html＞

＜body＞

＜Applet code="test1.class"width="200"height="200"＞

＜/Applet＞

＜/body＞

＜/html＞

最后在DOS窗口中，使用如下命令。

Appletviewer以html为扩展名的文件

在Applet程序中，不需要主运行函数，取而代之的则是使用init、start、stop和destroy方法。下面介绍这几个函数。

❑public void init（）：完成Applet的初始化操作。

❑public void start（）：启动浏览器运行Applet程序。

❑public void stop（）：暂停Applet的执行。

❑public void destroy（）：撤销Applet的执行。

这4个函数也就形成了Applet的生命周期。它们的执行步骤如下所示。

加载Applet类→创建Applet对象（调用构造器）→init（）→start（）

当从包含有Applet的Web页面转到其他页面时，浏览器会自动调用Applet类的stop（）方法，因此要让Applet暂停时运行的代码，放入stop（）方法函数中。若再次返回到包含Applet的Web页面，Applet对象会重新被创建，然后再次依照上面的步骤执行。

【实例24.1】下面将演示一个有关Applet生命周期的实例。

01 import java.applet.*；

02 ///在init方法中输出一段话，就是在初始化页面时会输出它

03 ///在start方法中输出一段话，就是在运行时会输出它

04 ///在stop方法中输出一段话，就是在暂停时会输出它

05 public class test1 extends Applet

06 {

07 public void init（）

08 {

09 System.out.println（"欢迎大家进入Applet程序学习页面！"）；

10 }

11 public void start（）

12 {

13 System.out.println（"现在程序正在运行。"）；

14 }

15 public void stop（）

16 {

17 System.out.println（"程序停止运行了。"）；

18 }

19 }

在同一个目录下创建一个HTML文件如下。

20＜html＞

21＜body＞

22＜Applet code="test1.class"width="200"height="200"＞

23＜/Applet＞

24＜/body＞

25＜/html＞

【代码说明】第7～18行实现了Applet的3个方法，第22行使用＜Applet＞标签来调用Java类。这里要注意调用的是编译后的Java类。

【运行效果】

欢迎大家进入Applet程序学习页面！

现在程序正在运行。

（当关闭Applet程序时）

程序停止运行了。

从上面的实例可以看出一个程序的运行步骤。这是一个很简单的程序段，演示这个例子的目的，在于让读者对Applet生命周期有一个认识，因为这个周期的概念非常重要。

一个网页中不但有声音、图像、动画等，还有静态文字。本章后面将会对声音的处理、图像的处理、动画的处理做一个介绍，并且通过一些实例让读者学会如何编写Applet程序。

24.2　Applet的多媒体处理

处理声音和图像是Applet兴起的目的。本节将会通过实例，学习如何针对这些多媒体的内容进行处理。其实多媒体的处理不是一件很难的事情，关键在于函数的调用。

24.2.1　声音处理

Java可以播放的声音文件格式有AIFF、AU、WAV、MIDI、RMF等。Applet包中的AudioClip接口包含了与声音相关的API，它可以实现声音文件的反复播放，在需要的时候，可以停止声音文件的播放。下面是AudioClip接口中的一些常用方法。

❑void play（）：播放声音文件一次。

❑void stop（）：停止播放声音文件。

❑void loop（）：循环播放声音文件。

若想播放声音文件，只需从Applet对象中获取AudioClip对象即可，在Applet类中有一些与AudioClip接口相配合的方法，具体如下所示。

❑public void play（URL url）：播放指定url处的声音文件。

❑public void play（URL url, String name）：播放指定位置url处，名称为name的声音文件。

❑public final static AudioClip newAudioClip（URL url）：获取可用于播放指定位置url处声音文件的AudioClip对象。

❑public AudioClip getAudioClip（URL url），public AudioClip getAudioClip（URL url, string name）：都是获取播放指定位置声音文件的AudioClip对象。

【实例24.2】下面就针对声音的播放，举一个有关的实例。

01 import java.applet.*；

02 import java.awt.*；

03 ///创建一个AudioClip对象voice

04 ///在初始化方法中，播放音乐

05 ///在运行方法中，不断播放

06 ///在停止方法中，停止播放音乐

07 public class test2 extends Applet

08 {

09 AudioClip voice；

10 public void init（）

11 {

12 voice=getAudioClip（getCodeBase（），"ding.wav"）；

13 }

14 public void start（）

15 {

16 voice.loop（）；

17 }

18 public void stop（）

19 {

20 voice.stop（）；

21 }

22 }

在同一个目录下创建一个HTML文件如下：

23＜html＞

24＜body＞

25＜Applet code="test2.class"width="200"height="200"＞

26＜/Applet＞

27＜/body＞

28＜/html＞

【代码说明】第12行指定要播放的文件，第25行将Applet嵌入到HTML文件中。

【运行效果】当运行时，程序会不断地播放ding.wav这个声音文件，直到转到其他页面，才会停止播放音乐。

24.2.2　图像处理

本节学习如何处理图像。其实无论处理声音还是处理图像，最关键的就是把握好对函数的运用。Applet类中与图像处理有关的方法如下。

❑public Image getImage（URL url）：获取url指定的图像对象。

❑public Image getImage（URL url, string name）：使用指定的url及name获取图像。

与Applet类有关的相关位置的方法：

❑public URL getCodeBase（）：获取Applet Code位置URL。

❑public URL getDocumentBase（）：获取Web页位置URL。

【实例24.3】学习了图像处理的相关函数，下面将演示一个有关图像处理的实例。

01 import java.applet.*；

02 import java.awt.*；

03 ///创建三个image对象

04 ///在初始化方法中通过getimage方法获取三幅图像

05 ///创建一个Graphics对象g

06 ///通过g的drawImage方法来将图像绘制在页面的不同位置

07 public class test3 extends Applet

08 {

09 Image img1，img2，img3；

10 public void init（）

11 {

12 img1=getImage（getDocumentBase（），"bullet.gif"）；

13 img2=getImage（getDocumentBase（），"exclam.gif"）；

14 img3=getImage（getDocumentBase（），"tips.gif"）；

15 }

16 public void paint（Graphics g）

17 {

18 g.drawImage（img1，0，50，this）；

19 g.drawImage（img2，100，50，this）；

20 g.drawImage（img3，200，50，this）；

21 }

22 }

在同一个目录下创建一个HTML文件如下：

23＜html＞

24＜body＞

25＜Applet code="test3.class"width="200"height="200"＞

26＜/Applet＞

27＜/body＞

28＜/html＞

【代码说明】这个程序段将三幅图像显示在网页上，遵照面向对象编程的思想，可以将声音处理和图像处理等作为一个一个的模块，让主程序逐个调用，那么程序代码的复杂性就不存在了。

【运行效果】这里需要从系统中找几个图像来显示，鉴于版权问题，本例不给出运行效果，读者可自行测试。

24.3　浏览器与Applet的设计

本节重点讲述如何设计Applet，以及如何控制它在浏览器中的显示、运行等问题。本节会用一些简单的实例，演示如何来制作Applet小应用程序，从而为以后的开发工作奠定坚实的基础。

24.3.1　如何将Applet嵌入浏览器

其实将Applet嵌入到浏览器非常简单。在前面也曾经举过实例，只需在HTML文件中，添加如下代码就可以。

＜html＞

＜body＞

＜Applet code="test1.class"width="200"height="200"＞

＜/Applet＞

＜/body＞

＜/html＞

但是只有这些代码，还没有彻底完成任务。这样只是将Applet程序嵌入到浏览器中，但显示出来的效果不一定是想要的，或者说不一定是所期望的结果。这就涉及一个Applet参数的问题，下面详细地讨论这个问题。

Applet可以从Web页面的＜para＞标记中，获取字符串形式的信息。这对于创建Web页面中使用的滚动字符串非常有用。＜para＞标记位于Applet标记中，分别指定了name与value的值，代码如下所示。

＜Applet＞

＜param name="变量名称"value="值"＞

＜/Applet＞

如果在Apple中想获取参数的数值，就必须调用getParameter方法，通过它来获取变量的值。

24.3.2　如何使用Applet类编程

【实例24.4】针对前面的讲述，本节演示一个复杂的实例，分析如何使用Applet类编程。实例的代码如下所示。

01 import java.applet.*；

02 import java.awt.*；

03 ///创建一个线程类，并且这个线程类继承了Applet类

04 ///在初始化方法中，通过getimage方法设置背景图像，通过getParameter方法获取字符串

05 ///在运行方法中，创建这个线程类的对象t，让其运行

06 ///在线程的run方法中，将text这个字符串循环地写到页面的不同位置

07 public class test4 extends Applet implements Runnable

08 {

09 Image backgroud；

10 String text；

11 boolean loop=true；

12 int delay；

13 public void init（）

14 {

15 backgroud=getImage（getCodeBase（），getParameter（"backgroud"））；

16 text=getParameter（"text"）；

17 delay=Integer.parseInt（getParameter（"delay"））；

18 }

19 public void start（）

20 {

21 Thread t；

22 t=new Thread（this）；

23 t.start（）；

24 }

25 public void stop（）

26 {

27 loop=false；

28 }

29 public void run（）

30 {

31 int x=0；

32 Graphics g=getGraphics（）；

33 Image buffer=createImage（getWidth（），getHeight（））；

34 Graphics gp=buffer.getGraphics（）；

35 while（loop）

36 {

37 gp.drawImage（backgroud，0，0，getWidth（），getHeight（），this）；

38 gp.drawString（text, x，50）；

39 g.drawImage（buffer，0，0，this）；

40 x+=2；

41 if（x＞=getWidth（））

42 {

43 x=0；

44 }

45 try

46 {Thread.sleep（delay）；}

47 catch（Exception e）{}

48 }

49 }

50 }

在同一个目录下创建一个HTML文件，代码如下所示。

51＜html＞

52＜body＞

53＜Applet code="test4.class"width="200"height="200"＞

54＜param name="background"value="bullet.gif"＞

55＜param name="text"value="我是一个优秀的程序员"＞

56＜param name="delay"value="100"＞

57＜/Applet＞

58＜/body＞

59＜/html＞

【代码说明】第53行指明引用的Java类名称。第54行设置背景图像。第56行设置延迟时间，value的单位为毫秒。

【运行效果】这个程序的结果是一个跑马灯。让一段文字不断地从屏幕的一侧移动到另一侧，这种动作循环执行。

24.3.3　如何用Applet控制浏览器

在API中有一个类：AppletContext类，利用此类，不仅可以控制浏览器，也可以控制同一网页中其他的Applet。下面列举AppletContext类的常用方法：

❑void showDocument（URL url）：显示url指定的网页。

❑public void showDocument（URL url, String target）：将url指定的Web页面显示于目标窗口中。

❑Applet getApplet（String name）：获取同一网页中名为name的Applet。

❑Enumeration getApplets（）：获取同一网页中的所有的Applet。

❑void showStatus（String status）：向浏览器的状态栏显示status字符串。

注意　当调用Applet的getAppletContext（）方法时，可以获取AppletContext对象。

24.4　Applet的安全

当访问包含Applet的网页的时候，不管愿意还是不愿意，网页中的Applet都会被下载并且运行。如果程序员设计一个恶意的Applet，那么用户访问这个网页的时候，就会对用户进行攻击。

为了能解决上面这个问题，Applet已经提供了安全的保障，以防止其内含有威胁用户安全的代码。Applet包含下列安全事项：

❑Applet无法运行用户计算机中的程序。

❑Applet无法访问用户计算机中的文件。

❑Applet无法获取用户计算机中的大部分信息。

❑Applet无法调用用户计算机中的native method。

❑Applet只能与提供本Applet的服务器进行通信。

【实例24.5】下面通过一个实例，演示当Applet试图访问用户计算机上的文件时，发生的情况。

01 import java.awt.*；

02 import java.applet.*；

03 import java.io.*；

04 ///创建一个文本区控件ta

05 ///在初始化方法中添加这个控件，创建一个文件类对象，并且通过判断f是否存在，

06 ///以输出不同的结果

07 public class test5 extends Applet

08 {

09 TextArea ta=new TextArea（""，10，30，1）；

10 public void init（）

11 {

12 add（ta）；

13 File f=new File（"d：\abc.txt"）；

14 try

15 {

16 if（f.exists（））

17 {

18 ta.setText（"文件存在"）；

19 }

20 else

21 {

22 ta.setText（"文件不存在"）；

23 }

24 }

25 catch（Exception e）

26 {

27 ta.setText（e.toString（））；

28 }

29 }

30 }

在同一个目录下创建一个HTML文件，代码如下所示。

31 ＜html＞

32＜body＞

33＜Applet code="test5.class"width="200"height="200"＞

34＜/Applet＞

35＜/body＞

36＜/html＞

【代码说明】第13行创建一个文件对象，对象文件是d：\abc.txt。第16行通过方法exists（）判断文件是否存在。

【运行效果】如果d：\abc.txt这个文件存在，则提示文件存在，否则提示文件不存在。

24.5　一个Applet的综合实例分析

【实例24.6】下面将结合前面讲述的所有知识，演示一个综合的实例。这是一个物体垂直上抛运动的Applet实例，详细代码如下所示。

01 import java.applet.*；

02 import java.awt.*；

03 public class test6 extends Applet

04 {

05 public void paint（Graphics g）

06 {

07 g.setColor（Color.red）；

08 g.drawString（"爱情三十六计，看我七十二变"，15，30）；

09 g.setColor（Color.blue）；

10 g.drawString（"走为上计"，50，80）；

11 }

12 }

【代码说明】第7行设置字体颜色为红色，第9行设置字体颜色为蓝色。第8行输出一段文字，第10行输出第二段文字。

【运行效果】运行效果如图24.1所示。

图　24.1　运行效果

24.6　常见疑难解答

24.6.1　浏览器如何运行Applet

答：首先，确定Applet已经被编译为*.class文件，然后编写一个*.htm文件（扩展名也可以是.html），代码如下所示。

＜html＞

＜body＞

＜Applet code="..\..\myclass\MyJavaApplet.class"height=200 width=300＞

＜/Applet＞

＜/body＞

＜/html＞

如果HTML文件和class文件在同一个目录下，则不用写路径名，直接写“MyJavaApplet.class”即可。

24.6.2　有关Applet无法在IE中显示的问题

答：有关Applet在AppletVIEWS中可以显示，但在IE中却无法显示问题的解答：

现象：在IE中打开带有Applet的HTML文件时，状态条不是显示不能初始化，就是显示找不到类，以下是笔者解决问题的步骤，希望对大家有所帮助：

（1）在IE中确保Java控制台已经启动（选择“工具”|“Internet选项”|“高级”|“启动Java控制台”）。

（2）建议在帮助里找到相应的内容。

（3）在帮助中指示到http：//java.sun.com/prodUCts/plugin去下载。

（4）选择Consumer（windows）--“get Java Plug-in early Access now”。

（5）选择直接安装或者下载后安装（以下载后安装为例）。

（6）选择下载（大约5.2MB）。

（7）找到下载后的文件（j2re-1_5_0_02-win.exe），执行安装。

（8）全部默认安装选项，安装成功后它会给一个提示，并自动完成安装过程。

（9）安装成功后，打开位于时间指示区的Java console，可以看到有关Java（TM）Plug-in的信息。

（10）再用IE打开那个原来无法显示的带Applet的HTML，看看发生了什么。

24.7　小结

读者以前可能也听过“Java小程序”，这里所说的小程序就是Applet。本章首先介绍了Applet的概念和生命周期，然后介绍了如何利用它实现多媒体处理功能。我们可以开发Applet程序，并将其嵌入到浏览器中，以完成我们需要的功能。因为要在浏览器中运行程序，所以一定要注意Applet的安全问题。

24.8　习题

1.在DOS中执行带Applet的HTML文件用______命令。

2.Java可以播放的声音文件格式有哪些？

3.Applet提供了哪些安全事项来解决安全问题？

第25章　网络编程基础

本章主要讲述网络编程的基础知识，其中包括TCP和UDP的使用，并通过实例学习如何使用它们进行编程。随着互联网等各种网络的兴起，网络程序开发就成为热门应用，Java把将进行网络程序设计所需要的所有东西都对象化，使得网络编程更加轻松。

本章重点：

❑什么是TCP/IP。

❑如何设计TCP的程序。

❑如何设计UDP的程序。

❑设计网络程序的实例。

25.1　网络基础知识

在讲述如何进行网络程序开发之前，先讲述一些有关网络的基础知识。

为了使两台计算机之间能够通信，必须为这两台计算机建立一个网络，将这两台计算机进行连接，把其中一台用作服务器，另一台用作客户机。那么，什么是服务器？什么又是客户机？

服务器就是能够提供信息的计算机或程序。客户机是指请求信息的计算机或程序。有的时候很难区分服务器和客户机，因为很多信息都是互相请求、互相提供信息的。

为了能够保证两台以上计算机之间可以顺利地通信，必须有某种相互都必须要遵守的条约，在计算机学中将之称为协议，例如互联网使用IP协议。这种协议使用4个字节来唯一地表示网络中的一台机器，例如在公司内部网络中，有的机器IP地址是192.168.0.1，这就是前面说的IP协议地址。在一个网段中，它必须是唯一的，使用IP协议的有TCP协议和UDP协议。下面会详细介绍这两个协议，如图25.1所示。

25.2　TCP与UDP

图　25.1　网络通信示意图

TCP（Transmission Control Protocol）就是传输控制协议，其和IP协议一起使用。一般会将两者合在一起，称TCP/IP。TCP协议负责数据或文件的分组与重组，而IP协议负责发送和接收数据包。数据或文件在网络上传输的时候，会被分成许多块，称之为包。

TCP是可靠的、面向连接的协议，也就是说，只有连接可靠了才能相互进行通信。为什么它会连接可靠呢？因为它的连接是经过3次握手的过程。3次握手就是告知消息发一次，对方确认了并回一次包，再开始发一次包，所以称之为3次握手。它一般适合于可靠性比较高的场合。

UDP（User Datagram Protocol）就是数据包协议，它也跟IP协议一起使用。与TCP协议相比较有很多类似的地方。但是，它不对连接状态与数据丢失做检查，只是保证数据发出去了，例如，平时上网所使用的电子邮件或者聊天时的QQ、MSN等。它们都可以非在线发消息，而这一点与TCP不同。TCP需要对对方是否收到以及数据的完整性做检查。

Java对TCP和UDP协议提供了强有力的支持，分别引入了Sokcet类和DatagramSocket类，来解决这两个不同协议的网络编程。

TCP就好比上网时使用的视频聊天工具，而UDP就好比上网时使用的电子邮件。视频聊天是必须双方都在线，并且双方都监控着自己的数据是否已经发送给对方。而电子邮件则只要把邮件发出去就可以了，至于对方是否收到，就不关心了。

25.3　端口与套接字

什么是端口？什么是套接字？下面将围绕这两个概念进行讲述。网络程序设计中的端口（Port）并非真实物理存在的，而是一个假想的连接器。计算机提供了很多种服务，例如HTTP、FTP、DNS等。那么客户机必须明确地知道自己要连接的是服务器上的哪一个服务，是HTTP、FTP或者DNS。

为此就引入了一个端口的概念。端口被规定为一个在0～65535之间的整数。HTTP服务一般使用80端口，FTP使用的是21端口，那么客户必须通过80端口才能连接到服务器的HTTP服务，而通过21端口，才能连接到服务器的FTP服务器上。

其实1～1023之间的端口已经被系统占用了，因此在定义自己的端口时，不能使用这一段的端口号，而应该使用1024～65535之间的任意端口号，以免发生端口冲突。

那什么是套接字呢？网络程序中的套接字用来将应用程序与端口连接起来，套接字是一个软件实现，也是一个假想的装置。

在Java中，将套接字抽象化为类，所以程序只需创建Socket类的对象，就可以使用套接字。那么Java是如何实现数据传递的呢？答案是使用Socket的流对象进行数据传输，Socket类中有输入和输出流。

使用Socket进行的通信都称为Socket通信。将编写的Socket类，用在Socket通信程序中，这就称为Socket网络程序设计。下面将介绍Socket网络程序设计中，如何进行TCP程序设计。

25.4　TCP程序设计基础

Java中的TCP网络程序设计是指利用Socket类编写通信程序。TCP程序设计的一个过程是：服务器的套接字等待客户机连接请求，并创建新的套接字，使其与客户机的套接字连接，而本身继续等待其他客户机的连接请求。

25.4.1　如何设计TCP的程序

下面看看几个与TCP程序设计有关的类。

1.与IP相关的InetAddress类应用

InetAddress是与IP地址相关的类。利用此类可以获取IP地址、主机地址等信息。InetAddress类的常用方法如下所示。

❑public static InetAddress getByName（String host）：获取与host相对应的InetAddress对象。

❑public String getHostAddress（）：返回主机地址的字符串表示。

❑public String getHostName（）：返回主机名的字符串表示。

❑public synchronized static InetAddress getLocalHost（）：返回本地主机的InetAddress对象。

【实例25.1】下面分析一个实例，通过这个实例能够清楚以上几个方法的用法。

01 import java.net.*；

02 ///创建一个InetAddress类对象ip

03 ///通过getHostName方法获得主机名

04 ///通过getHostAddress方法获得主机的IP地址

05 ///通过getLocalHost（）.getHostAddress方法获取本机的IP地址

06 public class tcptest

07 {

08 public static void main（String[]args）

09 {

10 InetAddress ip=null；

11 try

12 {

13 ip=InetAddress.getByName（"Jason"）；

14 System.out.println（"主机名："+ip.getHostName（））；

15 System.out.println（"主机IP地址："+ip.getHostAddress（））；

16 System.out.println（"本机IP地址："+InetAddress.getLocalHost（）.getHostAddress（））；

17 }

18 catch（Exception e）

19 {

20 System.out.println（e）；

21 }

22 }

23 }

【代码说明】第13行指定机器名，这里笔者的机器名是Jason，第14～16行分别获取主机名、IP地址等。说明　读者在演示此实例时，需要在代码中将机器名更改成自己的。

【运行效果】

主机名：Jason

主机IP地址：127.0.0.1

本机IP地址：127.0.0.1

总结这个类：要使用InetAddress类的方法，就必须要获得相应的InetAddress对象，因为只有这个对象才能使用这些方法。在上面的程序中，通过主机名称来获得InetAddress对象。

2.服务器套接字应用

ServerSocket类表示服务器套接字。服务器的套接字是通过指定的端口等待连接的套接字。服务器的套接字一次只可以与一个客户机的套接字相连接，如果有多台客户机同时要求同服务器连接，那么服务器套接字会将请求的客户机的套接字存入队列中，然后，从中取一个连接一个。

队列的大小，就是服务器可同时接收的连接请求数，若大于队列长度，则多出来的那些请求套接字会被拒绝。队列的默认大小为50，即一个服务器套接字可以同时接收50个请求。

ServerSocket类的常用构造函数如下：

❑public ServerSocket（int port）

使用指定的端口创建服务器套接字。

❑public ServerSocket（int port, int backlog）

使用指定的端口创建服务器套接字。backlog用来指定队列大小。

❑public ServerSocket（int port, int backlog, InetAddress bindAddr）

使用指定的IP地址、端口号及队列大小，创建服务器套接字。

ServerSocket类的常用方法如下所示。

❑public Socket accept（）：等待客户机请求，若连接，则创建一套接字，并且将其返回。

❑public void close（）：关闭服务器的套接字。

❑public boolean isClosed（）：若服务器套接字成功关闭，则返回true。否则返回false。

❑public InetAddress getInetAddress（）：返回与服务器套接字结合的IP地址。

❑public int getLocalPort（）：获取服务器套接字等待的端口号。

❑public void bind（SocketAddress endpoint）：绑定于endpoint相对应的套接字地址（IP地址+端口号）。

❑public boolean isBound（）：若服务器套接字已经与某个套接字地址绑定起来，则返回true；否则返回false。

3.套接字实现

Socket类表示套接字。使用Socket时，需要指定待连接服务器的IP地址及端口号。客户机创建了Socket对象后，将立即向指定的IP地址及端口发起请求且尝试连接。于是，服务器套接字就会创建新的套接字对象，使其与客户端套接字连接起来。一旦服务器套接字与客户端套接字成功连接后，就可以获取套接字的输入/输出流，彼此进行数据交换。

Socket类的构造器如下所示。

❑public Socket（String host, int port）

创建连接指定的服务器（主机与端口）的套接字。

❑public Socket（InetAddress address, int port）

创建连接指定服务器的套接字。address表示IP地址对象，port是端口号。

Socket类的常用方法如下所示。

❑public InetAddress getInetAddress（）：获取被连接的服务器的地址。

❑public int getPort（）：获取端口号。

❑public InetAddress getLocalAddress（）：获取本地地址。

❑public int getLocalPort（）：获取本地端口号。

❑public Inputstream getInputStream（）：获取套接字的输入流。

❑public OutputStream getOutputStream（）：获取套接字的输出流。

❑public void bind（SocketAddress bindpoint）：绑定指定的IP地址和端口号。

❑public boolean isBound（）：获取绑定状态。

❑public synchorized void close（）：关闭套接字。

❑public boolean isClosed（）：获取套接字是否关闭。

❑public boolean isConnected（）：套接字被连接，则返回true。否则返回false。

❑public void shutdownInput（）：关闭输入流。

❑public boolean isInputShutdown（）：返回输入流是否被关闭。

25.4.2　一个简单的例子

【实例25.2】下面演示一个Socket类和ServerSocket类的应用实例。

01 import java.net.*；

02 ///创建一个服务器套接字对象server

03 ///指定客户端套接字要连接到服务器的IP地址和端口号

04 ///通过循环语句让客户端连接到服务器套接字上

05 public class tcptest1

06 {

07 public static void main（String[]args）

08 {

09 try

10 {

11 ServerSocket server=new ServerSocket（3002）；

12 System.out.println（"服务器的套接字已经创建成功！"）；

13 System.out.println（"正在等待客户机连接……！"）；

14 for（int i=0；i＜10；i++）

15 {

16 Socket s=new Socket（"127.0.0.1"，3002）；

17 System.out.println（"已经与第"+i+"客户机连接！"）；

18 }

19 }

20 catch（Exception e）{}

21 }

22 }

【代码说明】第11行创建服务器套接字对象，这里需要指定端口，第14～18行创建Socket对象，指定的端口要与服务器端指定的端口相同。

【运行效果】

服务器的套接字已经创建成功！

正在等待客户机连接……！

已经与第0客户机连接！

已经与第1客户机连接！

已经与第2客户机连接！

已经与第3客户机连接！

已经与第4客户机连接！

已经与第5客户机连接！

已经与第6客户机连接！

已经与第7客户机连接！

已经与第8客户机连接！

已经与第9客户机连接！

上面是一个模拟网络程序，有10个客户机与服务器连接。下面总结编写TCP网络程序的步骤：

（1）服务器程序编写。

1）调用ServerSocket（int port）创建一个服务器端套接字，并绑定到指定端口上。

2）调用accept（），监听连接请求，如客户端请求连接，则接受连接，返回通信套接字。

3）调用Socket类的getOutputStream（）和getInputStream（）获取输出流和输入流，开始网络数据的发送和接收。

4）最后关闭通信流套接字。

（2）客户端程序编写。

1）调用Socket（）创建一个流套接字，并连接到服务器端。

2）调用Socket类的getOutputStream（）和getInputStream（）获取输出流和输入流，开始网络数据的发送和接收。

3）最后关闭通信流套接字。

25.5　UDP程序设计基础

本节介绍如何针对UDP进行网络程序设计。通过设计UDP程序，可以将UDP与TCP程序设计进行比较。分析两种截然不同的网络通信方式编写代码的方式有何不同。其实网络编程的关键，还是要理解UDP或TCP程序执行的步骤，这是网络编程的基本点。

25.5.1　如何设计UDP的程序

在这里总结编写UDP程序的步骤。

（1）接收端程序代码编写：

1）调用DatagramSocket（int port）创建一个数据报套接字，并且绑定到指定端口上。

2）调用DatagramPacket（byte[]buf, int length），建立一个字节数组以接收UDP包。

3）调用DatagramSocket类的receive（），接收UDP包。

（2）关闭数据报套接字。

（3）发送端程序编写：

1）调用DatagramSocket（）创建一个数据包套接字。

2）调用DatagramPacket（byte[]buf, int offset, int length, InetAddress address, int port），建立要发送的UDP包。

3）调用DatagramSocket类的send（），发送UDP包。

4）关闭数据包套接字。

25.5.2　一个简单的例子

【实例25.3】本节演示一个UDP的设计实例，代码如下。

01 import java.net.*；

02 import java.io.*；

03 public class udptest

04 {

05 ///创建一个数据包套接字对象ds，并且指定连接的端口号

06 ///创建一个数据包对象dp

07 ///指定一个字节数组，用来存储接收的数据

08 public static void rev（）

09 {

10 try

11 {

12 DatagramSocket ds=new DatagramSocket（6000）；

13 byte[]buf=new byte[100]；

14 DatagramPacket dp=new DatagramPacket（buf，100）；

15 ds.receive（dp）；

16 System.out.println（new String（buf，0，dp.getLength（）））；

17 ds.close（）；

18 }

19 catch（Exception e）{}

20 }

21 ///创建一个数据包套接字对象ds

22 ///创建一个数据包对象dp，并且指定其需要连接的端口号、需连接的主机名称等

23 ///初始化一个字符串，并且将这个字符串通过套接字连接后按照端口号发送出去

24 public static void send（）

25 {

26 try

27 {

28 DatagramSocket ds=new DatagramSocket（）；

29 String str="hello, i am zhanghong.i am a student and i am a best programer！"；

30 DatagramPacket dp=new DatagramPacket（str.getBytes（），str.length（），

31 InetAddress.getByName（"localhost"），6000）；

32 ds.send（dp）；

33 ds.close（）；

34 }

35 catch（Exception e）{}

36 }

37 public static void main（String[]args）

38 {

39 if（args.length＞0）

40 rev（）；

41 else

42 send（）；

43 }

44 }

【代码说明】以上的程序段设计了一个发送端（第24～36行）和一个接收端（第8～20行）。发送端发送了一个数组的数据，而接收端就接收发过来的数组中的字符，并且将其以字符串形式输出。

【运行效果】第39行判断程序是否有参数，如果没有则发送数据，没有任何结果的显示。

25.6　如何设计网络程序

上一节中讲述了如何设计TCP程序和UDP程序，本节主要讲述如何将网络编程应用到实际工作的开发中去。

25.6.1　单向通信综合实例

【实例25.4】下面举一个单向通信的实例。这个实例用来实现客户机向服务器发送字符串功能。由于只要求客户机向服务器发送消息，不用服务器向客户机发送消息，所以称为单向通信。客户机套接字和服务器套接字连接成功后，客户机会通过输出流发送数据，而服务器会使用输入流接收数据，下面是具体的实例代码。

服务器程序代码如下所示。

01 import java.net.*；

02 import java.io.*；

03 public class serverrev

04 {

05 private BufferedReader reader1；

06 private ServerSocket server；

07 private Socket socket1；

08 public serverrev（）

09 {}

10 ///创建服务器套接字server，并且其端口为6000

11 void startserver（）

12 {

13 try

14 {

15 server=new ServerSocket（6000）；

16 System.out.println（"服务器套接字创建完成了！"）；

17 ///若客户机提出请求，则与其套接字连接

18 while（true）

19 {

20 System.out.println（"等待客户机的连接。"）；

21 socket1=server.accept（）；

22 System.out.println（"完成与客户机的连接。"）；

23 reader1=new BufferedReader（new InputStreamReader（socket1.getInputStream（）））；

24 getMessage（）；

25 }

26 27

}

28 catch（Exception e）{}

29 }

30 ///读取来自套接字的输入输出流，从而将其输出到屏幕上

31 void getMessage（）

32 {

33 try

34 {

35 while（true）

36 {

37 System.out.println（"客户机："+reader1.readLine（））；

38 }

39 }

40 catch（Exception e）{}

41 finally

42 {

43 System.out.println（"客户机中断连接"）；

44 }

45 try

46 {

47 if（reader1！=null）

48 reader1.close（）；

49 if（socket1！=null）

50 socket1.close（）；

51 }

52 catch（Exception e）{}

53 }

54 public static void main（String[]args）

55 {

56 serverrev server=new serverrev（）；

57 server.startserver（）；

58 }

59 }

客户机的程序代码如下所示。

60 import java.io.*；

61 import java.net.*；

62 import java.awt.*；

63 import java.awt.event.*；

64 public class clientrev extends Frame

65 {

66 private PrintWriter writer1；

67 Socket socket1；

68 private TextArea ta=new TextArea（）；

69 private TextField tf=new TextField（）；

70 public clientrev（String title）

71 {

72 super（title）；

73 ta.setEditable（false）；

74 add（ta，"North"）；

75 add（tf，"South"）；

76 tf.addActionListener（new ActionListener（）

77 {

78 public void actionPerformed（ActionEvent ae）

79 {

80 writer1.println（tf.getText（））；

81 ta.append（tf.getText（）+"\n"）；

82 tf.setText（""）；

83 }

84 }）；

85 pack（）；

86 }

87 ///实现与服务器的连接

88 private void connect（）

89 {

90 try

91 {

92 ta.append（"尝试与服务器连接\n"）；

93 94

socket1=new Socket（"127.0.0.1"，6000）；

95 writer1=new PrintWriter（socket1.getOutputStream（），true）；

96 ta.append（"完成连接，清除待传字符串\n"）；

97 98

}

99 catch（Exception e）

100 {

101 System.out.println（"连接失败"）；

102 }

103 }

104 105

public static void main（String[]args）

106 {

107 clientrev client1=new clientrev（"向服务器发送数据。"）；

108 client1.setVisible（true）；

109 client1.connect（）；

110 }

111 }

【代码说明】分析上面的程序，在服务器程序中，主要是建立服务器套接字，然后等待客户连接。如果有客户机连接，就从客户机中传输过来的数据流中读取数据。

而在客户机程序中，主要是建立一个客户机套接字，然后连接到服务器套接字，最后通过输出流程序将数据发送给服务器。

【运行效果】运行结果如图25.2所示，这是服务器运行的界面，服务器执行在等待，等待客户机的连接。当有客户机连上的时候，会出现如图25.3所示的效果。客户端运行效果如图25.4所示，可在客户界面上输入数据，如图25.5所示。

图　25.2　单向通信服务器运行（一）

图　25.3　单向通信服务器运行（二）

图　25.4　单向通信客户端运行（一）

图　25.5　单向通信客户端运行（二）

说明

因为一个代表服务器端，一个代表客户端，所以需要打开两个DOS窗口，一个运行服务端程序，一个运行客户端程序。

25.6.2　双向通信综合实例

【实例25.5】上一节介绍了客户机向服务器发送数据的单向通信，本节将介绍服务器和客户机相互发送数据的双向通信，其程序代码在上一小节的代码基础上有所改变，读者可以比较两者的区别。

服务器程序代码如下：

01 import java.net.*；

02 import java.io.*；

03 public class serverrev1

04 {

05 private DataInputStream reader1；///输入流

06 private DataOutputStream writer1；///输出流

07 private ServerSocket server；///服务器套接字

08 private Socket socket1；///套接字

09 public serverrev1（） ///启动的服务器

10 {}

11 void startserver（）

12 {

13 try

14 {

15 server=new ServerSocket（6000）；

16 System.out.println（"服务器套接字创建完成了！"）；

17 while（true）

18 {

19 System.out.println（"等待客户机的连接。"）；

20 socket1=server.accept（）；

21 System.out.println（"完成与客户机的连接。"）；

22 reader1=new DataInputStream（socket1.getInputStream（））；

23 writer1=new DataOutputStream（socket1.getOutputStream（））；

24 getrev（）；///与客户机进行通信

25 }

26 27

}

28 catch（Exception e）{}

29 }

30 void getrev（）

31 {

32 try

33 {

34 while（true）

35 {

36 String filename=reader1.readUTF（）；

37 writer1.writeUTF（getfileinfo（filename））；

38 writer1.flush（）；

39 System.out.println（filename+"的信息传送完毕。"）；

40 }

41 }

42 catch（Exception e）{}

43 finally

44 {

45 System.out.println（"客户机中断连接"）；

46 }

47 try

48 {

49 if（reader1！=null）

50 reader1.close（）；

51 if（writer1！=null）

52 writer1.close（）；

53 if（socket1！=null）

54 socket1.close（）；

55 }

56 catch（Exception e）{}

57 }

58 String getfileinfo（String filename）

59 {

60 String fileinfo=""；

61 try

62 {

63 FileReader fr=new FileReader（filename）；

64 BufferedReader br=new BufferedReader（fr）；

65 String temp；

66 while（（temp=br.readLine（））！=null）

67 fileinfo+=temp+"\n"；

68 br.close（）；

69 }

70 catch（Exception e）{}

71 return fileinfo；

72 }

73 public static void main（String[]args）

74 {

75 serverrev1 server=new serverrev1（）；

76 server.startserver（）；

77 }

78 }

客户机程序代码如下所示。

79 import java.io.*；

80 import java.net.*；

81 import java.awt.*；

82 import java.awt.event.*；

83 ///构建一个图形界面

84 ///界面中ta指文本区，用来显示聊天结果

85 ///tf是指文本框

86 public class clientrev1 extends Frame

87 {

88 Socket socket1；

89 private DataInputStream reader1；

90 private DataOutputStream writer1；

91 private TextArea ta=new TextArea（）；

92 private TextField tf=new TextField（）；

93 public clientrev1（String title）

94 {

95 super（title）；

96 ta.setEditable（false）；

97 add（ta，"North"）；

98 add（tf，"South"）；

99 ///通过按钮的动作，开始将输出流输送到屏幕中

100 tf.addActionListener（new ActionListener（）

101 {

102 public void actionPerformed（ActionEvent ae）

103 {

104 try

105 {

106 writer1.writeUTF（tf.getText（））；

107 writer1.flush（）；

108 String fileinfo=reader1.readUTF（）；

109 ta.setText（"＜"+tf.getText（）+"的内容＞\n\n"）；

110 ta.append（fileinfo）；

111 }

112 catch（Exception e）{}

113 }

114 }）；

115 pack（）；

116 }

117 private void connect（）

118 {

119 try

120 {

121 ta.append（"尝试与服务器连接\n"）；

122 socket1=new Socket（"127.0.0.1"，6000）；

123 ta.append（"连接完毕。请输入文件名\n"）；

124 writer1=new DataOutputStream（socket1.getOutputStream（））；

125 reader1=new DataInputStream（socket1.getInputStream（））；

126 }

127 catch（Exception e）

128 {

129 System.out.println（"连接失败"）；

130 }

131 }

132 public static void main（String[]args）

133 {

134 clientrev1 client1=new clientrev1（"查看服务器系统文件。"）；

135 client1.setVisible（true）；

136 client1.connect（）；

137 }

138 }

【代码说明】分析上面的程序段，在服务器程序代码中，主要是在上一小节服务器程序段的基础上，多了一个服务器收到后回复给客户机的代码。而客户机从服务器的回复数据流中提取出来，显示在客户机的屏幕上。

【运行效果】运行结果如图25.6～图25.9所示。图25.6代表是服务器运行结果。图25.7代表是客户端程序运行界面。图25.8代表是客户端输入数据后的运行界面。图25.9则是服务器接收数据的运行界面。

图　25.6　双向通信服务器运行（1）

图　25.7　双向通信客户端运行（1）

图　25.8　双向通信客户端运行（2）

图　25.9　双向通信服务器运行（2）

25.7　常见疑难解答

25.7.1　TCP和UDP的区别

答：UDP不提供可靠的数据传输，事实上，该协议不能保证数据准确无误地到达目的地。UDP在许多方面非常有效。当某个程序的目标是尽快地传输尽可能多的信息时（其中任意给定数据的重要性相对较低），可使用UDP。ICQ短消息使用UDP协议发送消息。

许多程序将使用单独的TCP连接和单独的UDP连接，重要的状态信息随可靠的TCP连接发送，而主数据流通过UDP发送。

TCP的目的是提供可靠的数据传输，并在相互进行通信的设备或服务之间，保持一个虚拟连接。TCP在数据包接收无序、丢失或在交付期间被破坏时，负责数据恢复。它通过为其发送的每个数据包提供一个序号来完成此恢复。记住，较低的网络层会将每个数据包视为一个独立的单元，因此，数据包可以沿完全不同的路径发送，即使它们都是同一消息的组成部分。这种路由与网络层处理分段和重新组装数据包的方式非常相似，只是级别更高而已。

为确保正确地接收数据，TCP要求在目标计算机成功收到数据时，发回一个确认（即ACK）。如果在某个时限内未收到相应的ACK，将重新传送数据包。如果网络拥塞，这种重新传送将导致发送的数据包重复，但是，接收计算机可使用数据包的序号来确定它是否为重复数据包，并在必要时丢弃它。

25.7.2　什么是TCP/IP协议，分为几层，有什么功能

答：TCP/IP协议族包含了TCP/IP层次模型，协议共分为4层：应用层、传输层、网络层、数据链路层。

TCP/IP（Transmission Control Protocol/Internet Protocol，传输控制协议/网间网协议）是目前世界上应用最为广泛的协议。它的流行与Internet的迅猛发展密切相关，TCP/IP最初是为互联网的原型ARPANET所设计的，目的是提供一整套方便实用、能应用于多种网络上的协议。事实证明TCP/IP做到了这一点，它使网络互联变得容易起来，并且使越来越多的网络加入其中，成为Internet的事实标准。

应用层是用户所面向的应用程序的统称。TCP/IP协议族在这一层面有很多协议来支持不同的应用，大家所熟悉的基于Internet应用的实现，就离不开这些协议。如进行万维网（WWW）访问用到了HTTP协议、文件传输用FTP协议、电子邮件发送用SMTP、域名的解析用DNS协议、远程登录用Telnet协议等，都是属于TCP/IP应用层的。就用户而言，看到的是由一个个软件所构筑的大多数为图形化的操作界面，而实际后台运行的便是上述协议。

传输层的功能主要是提供应用程序间的通信，TCP/IP协议族在这一层的协议有TCP和UDP。

网络层是TCP/IP协议族中非常关键的一层，主要定义了IP地址格式，从而能够使得不同应用类型的数据在Internet上传输，IP协议就是一个网络层协议。

数据链路层是TCP/IP软件的最低层，负责接收IP数据包并通过网络发送它，或者从网络上接收物理帧，抽出IP数据报，交给IP层。

TCP（Transmission Control Protocol）和UDP（User Datagram Protocol）协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输，它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用，通过面向连接、端到端和可靠的数据包发送。它是事先为所发送的数据开辟出连接好的通道，然后再进行数据发送。而UDP则不为IP提供可靠性、流控或差错恢复功能。

一般来说，TCP对应的是可靠性要求高的应用，而UDP对应的则是可靠性要求低、传输经济的应用。TCP支持的应用协议主要有Telnet、FTP、SMTP等。UDP支持的应用层协议主要有NFS（网络文件系统）、SNMP（简单网络管理协议）、DNS（主域名称系统）、TFTP（通用文件传输协议）等。

25.7.3　什么是IP协议，IP地址如何表示，分为几类，各有什么特点

为了便于寻址和层次化地构造网络，IP地址被分为A、B、C、D、E五类，商业应用中只用到A、B、C三类。

IP协议（Internet Protocol）又称互联网协议，是支持网间互连的数据报协议，它与TCP协议（传输控制协议）一起构成了TCP/IP协议族的核心。它提供网间连接的完善功能，包括IP数据报规定互连网络范围内的IP地址格式。Internet上，为了实现连接到互联网上的结点之间的通信，必须为每个结点（入网的计算机）分配一个地址，并且应当保证这个地址是全网唯一的，这便是IP地址。

目前的IP地址（IPv4：IP第4版本）由32个二进制位表示，每8位二进制数为一个整数，中间由小数点间隔，如159.225.41.98。整个IP地址空间有4组8位二进制数，由表示主机所在的网络的地址（类似部队的编号），以及主机在该网络中的标识（如同士兵在该部队的编号）共同组成。为了便于寻址和层次化的构造网络，IP地址被分为A、B、C、D、E五类，商业应用中只用到A、B、C三类。

❑A类地址：A类地址的网络标识由第一组8位二进制数表示，网络中的主机标识占3组8位二进制数，A类地址的特点是网络标识的第一位二进制数取值必须为“0”。不难算出，A类地址允许有126个网段，每个网络大约允许有1670万台主机，通常分配给拥有大量主机的网络（如主干网）。

❑B类地址：B类地址的网络标识由前两组8位二进制数表示，网络中的主机标识占两组8位二进制数，B类地址的特点是网络标识的前两位二进制数取值必须为“10”。B类地址允许有16384个网段，每个网络允许有65533台主机，适用于结点比较多的网络（如区域网）。

❑C类地址：C类地址的网络标识由前3组8位二进制数表示，网络中主机标识占1组8位二进制数，C类地址的特点是，网络标识的前3位二进制数取值必须为“110”。具有C类地址的网络允许有254台主机，适用于结点比较少的网络（如校园网）。

为了便于记忆，通常习惯采用4个十进制数来表示一个IP地址，十进制数之间采用句点“.”予以分隔。这种IP地址的表示方法也被称为点分十进制法。如以这种方式表示，A类网络的IP地址范围为1.0.0.1～127.255.255.254，B类网络的IP地址范围为：128.1.0.1～191.255.255.254，C类网络的IP地址范围为：192.0.1.1～223.255.255.254。

由于网络地址紧张、主机地址相对过剩，采取子网掩码的方式来指定网段号。TCP/IP协议与低层的数据链路层和物理层无关，这也是TCP/IP的重要特点，正因为如此，它能广泛地支持由低两层协议构成的物理网络结构。目前已使用TCP/IP连接成洲际网、全国网与跨地区网。

25.8　小结

如果读者经常上网，可能会使用QQ或MSN软件，这是一些网络即时通信软件，属于网络编程的范畴。本章讲解了网络编程的一些基础知识，包括TCP、UDP、套接字和端口等。本章最后设计了两个大型案例：单向通信和双向通信，通过这两个案例，读者可以了解前面介绍的这些基础知识。

25.9　习题

1.什么是端口？

2.什么是套接字？

3.说出TCP、IP、UDP的区别。

4.简述单向通信和双向通信的区别。

第七篇　Java程序设计综合实例

第26章　学校管理系统

至此，基础知识已经介绍完毕。本章将综合前面讲过的知识，介绍比较实用的综合实例。本章的宗旨是希望读者能通过本章的学习，更加牢固地掌握Java程序开发，而且还可通过本章的综合实例，来检验自己学习的情况。这个学校管理系统具有一定的通用性，希望读者能很好地理解这个综合实例，因为其能带来很多编程的启示。下面将通过详细的代码和分析，来讲述编写应用程序的一个思路。

本章重点：

❑如何进行数据库系统的设计。

❑如何实现主界面的设计。

❑如何实现登录功能。

❑如何实现学校管理系统的各个功能。

26.1　模块划分

针对本书所讲述的内容将整个系统分为以下几个模块。

❑登录界面。

❑主界面。

❑学生信息系统。

❑教师管理系统。

❑领导信息系统。

❑数据库系统。

在接下来的章节中，将详细地讲述每个模块，希望读者能够跟着作者的思路，找到自己编程的一个独特思路。

26.2　需求分析

首先，分析整个系统的构成。整个系统分为登录系统、主界面系统、学生信息系统、教师信息系统、领导信息系统和数据库系统，其实整个应用软件就是将数据库系统中的数据，通过软件界面的形式，展现在用户面前。整个系统最关键的就是数据库系统。

将整个软件系统同数据库系统连接，这样就可以通过软件界面来观察数据，并且可以通过增添和删除来管理数据库中的数据。数据库系统同软件如何连接呢？在很多软件中看到，当打开一个信息界面的时候，就会直接出现数据，那就是说在这个界面被装载的时候，同时也会将数据库中的数据装载，即把数据库同这个界面连接起来。如何连接呢？后面会详细讲述。

针对学生信息系统、教师信息系统、领导信息系统建立了主界面，这个界面主要是起到一个主程序入口的作用。

而登录界面，是为了能够防止没有进行系统注册的用户，登录到系统，整个软件的结构就是这样的。通过分析后，相信读者的思路已经很清晰了。

在学生信息系统中，主要是通过软件界面来操作数据，例如增加数据和删除数据。另外还可以通过软件界面进行查询，所以要打开学生信息系统的界面时，数据库就必须连接好了。

在教师信息系统中，主要是通过软件界面来操作数据，例如增加数据和删除数据，另外还可以通过软件界面进行查询，所以要打开教师信息系统的界面时，数据库就必须连接好了。

26.3　登录界面的设计

本节将详细地讲述如何设计整个软件的登录界面，从中可以复习前面学习过的布局管理器、事件监视器等方面的知识。

26.3.1　登录界面的分析

登录界面主要用来防止非法用户进入软件系统，所以在登录系统中，首先要设置系统的合法登录用户和登录密码。

在登录界面中，可以通过网格组布局管理器，将各个控件合理地分布在界面中。在设计登录界面时，首先将其设置为一个类，此类可以设置为容器类，将所有控件放置其中，然后再将这个容器放置到整体框架中去。其实在设计所有的界面类时，都是将整个类设置为容器类，这样就会给程序员带来在编写代码上的方便和简洁。

26.3.2　登录界面的代码实现

【实例26.1】本小节将分析登录界面的代码如何实现，其代码如下所示。

01 import javax.swing.*；

02 import java.awt.*；

03 import java.awt.event.*；

04 ///这是一个登录类。设计成一个继承容器的类

05 ///WIDTH是指整个顶层框架的宽度

06 ///HEIGHT是指整个顶层框架的长度

07 class login extends JPanel

08 {

09 static final int WIDTH=300；

10 static final int HEIGHT=150；

11 JFrame loginframe；

12 ///按照网格组布局方式排列组件的方法

13 ///x指控件位于第几列

14 ///y指控件位于第几行

15 ///w指控件需要占几列

16 ///h指控件需要占几行

17 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

18 {

19 constraints.gridx=x；

20 constraints.gridy=y；

21 constraints.gridwidth=w；

22 constraints.gridheight=h；

23 add（c, constraints）；

24 } //此方法用来添加控件到容器中

25 ///这是一个构造器方法

26 ///loginframe就是指这个界面的框架

27 ///setDefaultCloseOperation这是一个使得窗口上面的关闭控件有效的类库方法

28 ///lay是一个网格组布局管理器的对象

29 ///nameinput是用来输入用户名的文本域

30 ///passwordinput是用来输入密码的文本域

31 ///title是用来显示标题的标签

32 ///name是用来显示"姓名"的标签

33 ///password是用来显示"密码"的标签

34 ///ok是一个按钮，使进入系统

35 ///cancel是一个按钮，使退出界面和系统

36 ///ok.addActionListener是一个进入系统动作事件监听方法

37 ///cancel.addActionListener是一个退出系统和界面动作事件的监听方法

38 login（）

39 {

40 loginframe=new JFrame（"信息管理系统"）；

41 loginframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

42 GridBagLayout lay=new GridBagLayout（）；

43 setLayout（lay）；

44 loginframe.add（this, BorderLayout.WEST）；

45 loginframe.setSize（WIDTH, HEIGHT）；

46 Toolkit kit=Toolkit.getDefaultToolkit（）；

47 Dimension screenSize=kit.getScreenSize（）；

48 int width=screenSize.width；

49 int height=screenSize.height；

50 int x=（width-WIDTH）/2；

51 int y=（height-HEIGHT）/2；

52 loginframe.setLocation（x, y）；

53 JButton ok=new JButton（"登录"）；

54 JButton cancel=new JButton（"放弃"）；

55 JLabel title=new JLabel（"信息系统登录窗口"）；

56 JLabel name=new JLabel（"用户名"）；

57 JLabel password=new JLabel（"密码"）；

58 final JTextField nameinput=new JTextField（15）；

59 final JTextField passwordinput=new JTextField（15）；

60 GridBagConstraints constraints=new GridBagConstraints（）；

61 constraints.fill=GridBagConstraints.NONE；

62 constraints.anchor=GridBagConstraints.EAST；

63 constraints.weightx=3；

64 constraints.weighty=4；

65 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

66 add（name, constraints，0，1，1，1）；

67 add（password, constraints，0，2，1，1）；

68 add（nameinput, constraints，2，1，1，1）；

69 add（passwordinput, constraints，2，2，1，1）；

70 add（ok, constraints，0，3，1，1）；

71 add（cancel, constraints，2，3，1，1）；

72 loginframe.setResizable（false）；

73 loginframe.setVisible（true）；

74 ok.addActionListener（new ActionListener（）

75 {

76 public void actionPerformed（ActionEvent Event）

77 {

78 String nametext=nameinput.getText（）；

79 String passwordtext=passwordinput.getText（）；

80 String str=new String（passwordtext）；

81 boolean x=（nametext.equals（"Jason"））；

82 boolean y=（str.equals（"123456"））；//在此设置密码和用户名

83 boolean z=（x＆＆y）；

84 if（z==true）

85 {

86 loginframe.dispose（）；

87 //mainframe main=new mainframe（）；//此界面就是后面的主界面

88 System.out.println（"密码和用户名都对，可以进入学生信息系统了"）；

89 }

90 else if（z==false）

91 {

92 nameinput.setText（""）；

93 passwordinput.setText（""）；

94 }

95 }

96 }）；

97 cancel.addActionListener（new ActionListener（）

98 {

99 public void actionPerformed（ActionEvent Event）

100 {

101 loginframe.dispose（）；

102 }

103 }）；

104 }

105 }

106 public class studentlog

107 {

108 public static void main（String[]args）

109 {

110 login log=new login（）；

111 }

112 }

【代码说明】从第7行可以看出，登录界面是一个容器，它继承自JPanel类。第40～52行设计这个登录界面的大小、位置和标题等。第53～73行在这个界面中添加标签和按钮等控件。第74～96行是登录界面中“登录”按钮的验证，这里设置了默认的用户名和密码。第97～103行是登录界面中“放弃”按钮的验证。

26.3.3　登录界面的测试

通过运行上面的代码，测试登录效果，界面如图26.1所示。现在验证是否能真正起到防止非法用户登录系统的作用。在用户名和密码框中，输入事先规定好的用户名和密码，如图26.2所示。

图　26.1　软件登录界面

图　26.2　输入密码和用户名

单击“登录”按钮，如果密码和用户名都正确，会出现登录成功的字样，如图26.3所示。

图　26.3　正确的密码和用户名得到的结果

如果输入了不正确的密码和用户名（如图26.4所示的操作），单击“登录”按钮后，运行结果如图26.5所示。

可以看到当输入错误的密码和用户名后，系统会自动将登录窗口中的用户名和密码输入框清空，等待用户下一次输入，直到输入正确为止。

图　26.4　输入错误的密码和用户名

图　26.5　输入错误密码和用户名的结果

说明

上述代码中的主运行类，是为了能够运行这个登录界面类而设立，而在整个程序段中不需要。如果将所有的实现代码放入到一个类的构造器中，那么在主运行程序中，只要通过构造一个此类的对象，就可以实现这个类构造器中的所有代码。

26.4　主界面的设计

主界面的设计，主要就是设计整个软件程序的入口，从这个入口可以通向不同的软件界面系统。

26.4.1　主界面类的分析

主界面是整个软件的一个综合界面，是所有不同界面的一个入口。进入到这个界面中，可以通过选择按钮，进入不同的界面，然后进行不同的操作。

这个界面需要做的就是添加按钮，并为每个按钮编写动作事件。下面一小节将给出此类中的代码段，希望读者也能够根据自己的思路，重新编写一个类，这样才能将所学的知识进行融会贯通。

26.4.2　主界面类的代码

【实例26.2】本节主要分析主界面的代码如何实现，其代码如下。

01 import javax.swing.*；

02 import java.awt.*；

03 import java.awt.event.*；

04 ///这是一个主界面的类

05 ///student是指进入学生信息系统的界面控件

06 ///teacher是指进入教师信息系统的界面控件

07 ///leader是指进入领导信息系统的界面控件

08 ///后面紧接着就是这三个控件的动作事件的监视方法，分别是进入三个不同的系统

09 class mainframe

10 {

11 static final int WIDTH=400；

12 static final int HEIGHT=200；

13 JFrame buttonframe；

14 public mainframe（）

15 {

16 buttonframe=new JFrame（）；

17 buttonframe.setTitle（"学校信息管理系统"）；

18 buttonframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

19 buttonframe.setSize（WIDTH, HEIGHT）；

20 Toolkit kit=Toolkit.getDefaultToolkit（）；

21 Dimension screenSize=kit.getScreenSize（）；

22 int width=screenSize.width；

23 int height=screenSize.height；

24 int x=（width-WIDTH）/2；

25 int y=（height-HEIGHT）/2；

26 buttonframe.setLocation（x, y）；

27 buttonframe.setVisible（true）；

28 //buttonframe.setResizable（false）；

29 JButton student=new JButton（"学生信息系统模块"）；

30 JButton teacher=new JButton（"教师信息系统模块"）；

31 JButton leader=new JButton（"学校领导信息系统模块"）；

32 buttonframe.add（student）；

33 buttonframe.add（teacher）；

34 buttonframe.add（leader）；

35 buttonframe.setLayout（new GridLayout（3，1））；buttonframe.pack（）；

36 student.addActionListener（new ActionListener（）

37 {

38 public void actionPerformed（ActionEvent Event）

39 {

40 studentmanageframe studentframe=new studentmanageframe（）；

41 }

42 }）；

43 teacher.addActionListener（new ActionListener（）

44 {

45 public void actionPerformed（ActionEvent Event）

46 {

47 teachermanageframe teacherframe=new teachermanageframe（）；

48 }

49 }）；

50 leader.addActionListener（new ActionListener（）

51 {

52 public void actionPerformed（ActionEvent Event）

53 {

54 leadermanageframe leaderframe=new leadermanageframe（）；

55 }

56 }）；

57 }

58 }

【代码说明】第16～28行创建窗口，并设置窗口的大小和位置。第29～35行在此窗口中添加按钮。第36～56行可以监听每个按钮的操作，并导航到新的窗口。第28行代码设置了窗口不允许改变大小。

【实例26.3】当选择学生信息系统时，又会有另一个界面出现，其代码如下所示。

01 import java.awt.*；

02 import java.awt.event.ActionEvent；

03 import java.awt.event.ActionListener；

04 import java.sql.Connection；

05 import java.util.Vector；

06 import javax.swing.*；

07 ///这是一个学生信息系统的入口框架类

08 ///studentframe是顶层窗口框架

09 ///通过七个按钮控件来分别可以进入到不同班级、不同系的学生信息系统中

10 ///七个按钮控件分别产生七个不同的动作事件，分别进入不同的信息系统

11 ///在布局方面有两个布局管理器，一个是lay即网格组布局，一个是jp即GridLayout布局

12 ///通过不同的SQL语句进入不同的班级和系的管理系统

13 class studentmanageframe extends JPanel

14 {

15 private static final long serialVersionUID=1L；

16 static final int WIDTH=400；

17 static final int HEIGHT=200；

18 JFrame studentframe；

19 public studentmanageframe（）

20 {

21 studentframe=new JFrame（）；

22 studentframe.setTitle（"学生信息管理系统"）；

23 studentframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

24 studentframe.setSize（WIDTH, HEIGHT）；

25 Toolkit kit=Toolkit.getDefaultToolkit（）；

26 Dimension screenSize=kit.getScreenSize（）；

27 int width=screenSize.width；

28 int height=screenSize.height；

29 int x=（width-WIDTH）/2；

30 int y=（height-HEIGHT）/2；

31 studentframe.setLocation（x, y）；

32 studentframe.setVisible（true）；

33 //studentframe.setResizable（false）；

34 studentframe.add（this, BorderLayout.CENTER）；

35 JButton computerone=new JButton（"计算机系一班学生信息系统"）；

36 JButton computertwo=new JButton（"计算机系二班学生信息系统"）；

37 JButton computerthree=new JButton（"计算机系三班学生信息系统"）；

38 JButton bioone=new JButton（"生物系一班学生信息系统"）；

39 JButton mechone=new JButton（"机械系一班学生信息系统"）；

40 JButton mechtwo=new JButton（"机械系二班学生信息系统"）；

41 JButton mechthree=new JButton（"机械系三班学生信息系统"）；

42 JLabel title=new JLabel（"学生信息系统主界面"）；

43 JLabel banket1=new JLabel（）；

44 JLabel banket2=new JLabel（）；

45 GridBagLayout lay=new GridBagLayout（）；

46 setLayout（lay）；

47 GridBagConstraints constraints=new GridBagConstraints（）；

48 constraints.fill=GridBagConstraints.NONE；

49 constraints.anchor=GridBagConstraints.EAST；

50 constraints.weightx=2；

51 constraints.weighty=5；

52 JPanel jp=new JPanel（）；

53 jp.setLayout（new GridLayout（1，3））；

54 jp.add（banket1）；

55 jp.add（title）；

56 jp.add（banket2）；

57 studentframe.add（jp, BorderLayout.NORTH）；

58 add（computerone, constraints，0，1，1，1）；//使用网格组布局添加控件

59 add（computertwo, constraints，0，2，1，1）；

60 add（computerthree, constraints，0，3，1，1）；

61 add（bioone, constraints，0，4，1，1）；

62 add（mechone, constraints，1，1，1，1）；

63 add（mechtwo, constraints，1，2，1，1）；

64 add（mechthree, constraints，1，3，1，1）；

65 ///单击这个按钮，进入到计算机系一班学生信息系统

66 computerone.addActionListener（new ActionListener（）

67 {

68 public void actionPerformed（ActionEvent Event）

69 {

70 String sql="select*from studentinfo where class2='一班'and major='计算机系'"；

71 72

studentinfo info=new studentinfo（"计算机系一班学生信息系统"，sql）；

73 74

}

75 }）；

76 ///单击这个按钮，进入到计算机系二班学生信息系统

77 computertwo.addActionListener（new ActionListener（）

78 {

79 public void actionPerformed（ActionEvent Event）

80 {

81 String sql="select*from studentinfo where class2='二班'and major='计算机系'"；

82 83

studentinfo studentinformation=new studentinfo（"计算机系二班学生信息系统"，sql）；

84 85

}

86 }）；

87 ///单击这个按钮，进入到计算机系三班学生信息系统

88 computerthree.addActionListener（new ActionListener（）

89 {

90 public void actionPerformed（ActionEvent Event）

91 {

92 String sql="select*from studentinfo where class2='三班'and major='计算机系'"；

93 94

studentinfo studentinformation=new studentinfo（"计算机系三班学生信息系统"，sql）；

95 96

}

97 }）；

98 ///单击这个按钮，进入到生物系一班学生信息系统

99 bioone.addActionListener（new ActionListener（）

100 {

101 public void actionPerformed（ActionEvent Event）

102 {

103 String sql="select*from studentinfo where class2='一班'and major='生物系'"；

104 105

studentinfo studentinformation=new studentinfo（"生物系一班学生信息系统"，sql）；

106 107

}

108 }）；

109 ///单击这个按钮，进入到机械系一班学生信息系统

110 mechone.addActionListener（new ActionListener（）

111 {

112 public void actionPerformed（ActionEvent Event）

113 {

114 String sql="select*from studentinfo where class2='一班'and major='机械系'"；

115 116

studentinfo studentinformation=new studentinfo（"机械系一班学生信息系统"，sql）；

117 118

}

119 }）；

120 ///单击这个按钮，进入到机械系二班学生信息系统

121 mechtwo.addActionListener（new ActionListener（）

122 {

123 public void actionPerformed（ActionEvent Event）

124 {

125 String sql="select*from studentinfo where class2='二班'and major='机械系'"；

126 127

studentinfo studentinformation=new studentinfo（"机械系二班学生信息系统"，sql）；

128 129

}

130 }）；

131 ///单击这个按钮，进入到机械系三班学生信息系统

132 mechthree.addActionListener（new ActionListener（）

133 {

134 public void actionPerformed（ActionEvent Event）

135 {

136 String sql="select*from studentinfo where class2='三班'and major='机械系'"；

137 138

studentinfo studentinformation=new studentinfo（"机械系三班学生信息系统"，sql）；

139 140

}

141 }）；

142 }

143 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

144 {

145 constraints.gridx=x；

146 constraints.gridy=y；

147 constraints.gridwidth=w；

148 constraints.gridheight=h；

149 add（c, constraints）；

150 }

151 }

【代码说明】第21～34行创建窗口，并设置窗口的大小和位置。第35～64行在窗口中设计布局，并添加按钮和标签等。第66～142行监听这些按钮的事件，并给出具体的操作。

【实例26.4】当选择教师信息系统时，又会有另一个界面出现，其代码如下所示。

01 import java.awt.BorderLayout；

02 import java.awt.Component；

03 import java.awt.Dimension；

04 import java.awt.GridBagConstraints；

05 import java.awt.GridBagLayout；

06 import java.awt.GridLayout；

07 import java.awt.Toolkit；

08 import java.awt.event.ActionEvent；

09 import java.awt.event.ActionListener；

10 import javax.swing.JButton；

11 import javax.swing.JFrame；

12 import javax.swing.JLabel；

13 import javax.swing.JPanel；

14 ///这是一个教师信息系统的入口框架类

15 ///teacherframe是顶层窗口框架

16 ///通过三个按钮控件来分别可以进入不同系的教师信息系统中

17 ///三个按钮控件分别产生三个不同的动作事件，分别进入不同的信息系统

18 ///在布局方面有两个布局管理器，一个是lay即网格组布局，一个是jp即GridLayout布局

19 ///通过不同的SQL语句进入不同的班级和系的管理系统

20 class teachermanageframe extends JPanel

21 {

22 private static final long serialVersionUID=1L；

23 static final int WIDTH=400；

24 static final int HEIGHT=200；

25 JFrame teacherframe；

26 public teachermanageframe（）

27 {

28 teacherframe=new JFrame（）；

29 teacherframe.setTitle（"教师信息管理系统"）；

30 teacherframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

31 teacherframe.setSize（WIDTH, HEIGHT）；

32 Toolkit kit=Toolkit.getDefaultToolkit（）；

33 Dimension screenSize=kit.getScreenSize（）；

34 int width=screenSize.width；

35 int height=screenSize.height；

36 int x=（width-WIDTH）/2；

37 int y=（height-HEIGHT）/2；

38 teacherframe.setLocation（x, y）；

39 teacherframe.setVisible（true）；

40 //teacherframe.setResizable（false）；

41 teacherframe.add（this, BorderLayout.CENTER）；

42 JButton computerteacher=new JButton（"计算机系教师信息系统"）；

43 JButton bioteacher=new JButton（"生物系教师信息系统"）；

44 JButton mechteacher=new JButton（"机械系教师信息系统"）；

45 JButton beretun=new JButton（"返回"）；

46 JLabel title=new JLabel（"教师信息系统主界面"）；

47 JLabel banket1=new JLabel（）；

48 JLabel banket2=new JLabel（）；

49 GridBagLayout lay=new GridBagLayout（）；

50 setLayout（lay）；

51 GridBagConstraints constraints=new GridBagConstraints（）；

52 constraints.fill=GridBagConstraints.NONE；

53 constraints.anchor=GridBagConstraints.EAST；

54 constraints.weightx=1；

55 constraints.weighty=4；

56 JPanel jp=new JPanel（）；

57 jp.setLayout（new GridLayout（1，3））；

58 jp.add（banket1）；

59 jp.add（title）；

60 jp.add（banket2）；

61 teacherframe.add（jp, BorderLayout.NORTH）；

62 add（computerteacher, constraints，0，1，1，1）；//使用网格组布局添加控件

63 add（bioteacher, constraints，0，4，1，1）；

64 add（mechteacher, constraints，1，1，1，1）；

65 66///单击这个按钮，进入到计算机系教师信息系统

67 computerteacher.addActionListener（new ActionListener（）

68 {

69 public void actionPerformed（ActionEvent Event）

70 {

71 String sql="select*from teacherinfo where duty='教师'and major='计算机系'"；

72 73

teacherinfo teacherinformation=new teacherinfo（"计算机系教师信息系统"，sql）；

74 75

}

76 }）；

77 78///单击这个按钮，进入到生物系教师信息系统

79 bioteacher.addActionListener（new ActionListener（）

80 {

81 public void actionPerformed（ActionEvent Event）

82 {

83 String sql="select*from teacherinfo where duty='教师'and major='生物系'"；

84 teacherinfo teacherinformation=new teacherinfo（"生物系教师信息系统"，sql）；

85 }

86 }）；

87 ///单击这个按钮，进入到机械系教师信息系统

88 mechteacher.addActionListener（new ActionListener（）

89 {

90 public void actionPerformed（ActionEvent Event）

91 {

92 String sql="select*from teacherinfo where duty='教师'and major='机械系'"；

93 teacherinfo teacherinformation=new teacherinfo（"机械系教师信息系统"，sql）；

94 }

95 }）；

96 }

97 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

98 {

99 constraints.gridx=x；

100 constraints.gridy=y；

101 constraints.gridwidth=w；

102 constraints.gridheight=h；

103 add（c, constraints）；

104 }

105 }

【代码说明】第28～41行创建教师管理窗口，并设置窗口的大小和位置。第42～64行在窗口中设计布局，并添加按钮和标签等。第67～96行监听这些按钮的事件，并给出具体的操作。

【实例26.5】当选择领导信息系统时，又会有另一个界面出现，其代码如下所示。

01 import java.awt.BorderLayout；

02 import java.awt.Component；

03 import java.awt.Dimension；

04 import java.awt.event.*；

05 import java.awt.*；

06 import javax.swing.*；

07 ///这是一个领导信息系统的入口框架类

08 ///leaderframe是顶层窗口框架

09 ///通过四个按钮控件来分别可以进入到不同职务、不同系的领导信息系统中

10 ///四个按钮控件分别产生四个不同的动作事件，分别进入不同的信息系统

11 ///在布局方面有两个布局管理器，一个是lay即网格组布局，一个是jp即GridLayout布局

12 ///通过不同的SQL语句进入不同的班级和系的管理系统

13 class leadermanageframe extends JPanel

14 {

15 private static final long serialVersionUID=1L；

16 static final int WIDTH=400；

17 static final int HEIGHT=200；

18 JFrame leaderframe；

19 public leadermanageframe（）{

20 leaderframe=new JFrame（）；

21 leaderframe.setTitle（"领导信息管理系统"）；

22 leaderframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

23 leaderframe.setSize（WIDTH, HEIGHT）；

24 Toolkit kit=Toolkit.getDefaultToolkit（）；

25 Dimension screenSize=kit.getScreenSize（）；

26 int width=screenSize.width；

27 int height=screenSize.height；

28 int x=（width-WIDTH）/2；

29 int y=（height-HEIGHT）/2；

30 leaderframe.setLocation（x, y）；

31 leaderframe.setVisible（true）；

32 //leaderframe.setResizable（false）；

33 leaderframe.add（this, BorderLayout.CENTER）；

34 JButton computerleader=new JButton（"计算机系领导信息系统"）；

35 JButton bioleader=new JButton（"生物系领导信息系统"）；

36 JButton mechleader=new JButton（"机械系领导信息系统"）；

37 JButton schoolleader=new JButton（"学校领导信息系统"）；

38 JLabel title=new JLabel（"领导信息系统主界面"）；

39 JLabel banket1=new JLabel（）；

40 JLabel banket2=new JLabel（）；

41 GridBagLayout lay=new GridBagLayout（）；

42 setLayout（lay）；

43 GridBagConstraints constraints=new GridBagConstraints（）；

44 constraints.fill=GridBagConstraints.NONE；

45 constraints.anchor=GridBagConstraints.EAST；

46 constraints.weightx=2；

47 constraints.weighty=2；

48 JPanel jp=new JPanel（）；

49 jp.setLayout（new GridLayout（1，3））；

50 jp.add（banket1）；

51 jp.add（title）；

52 jp.add（banket2）；

53 leaderframe.add（jp, BorderLayout.NORTH）；

54 add（computerleader, constraints，0，0，1，1）；//使用网格组布局添加控件

55 add（bioleader, constraints，0，1，1，1）；

56 add（mechleader, constraints，1，0，1，1）；

57 add（schoolleader, constraints，1，1，1，1）；

58 ///单击这个按钮，进入到计算机系领导信息系统

59 computerleader.addActionListener（new ActionListener（）

60 {

61 public void actionPerformed（ActionEvent Event）

62 {

63 String sql="select*from teacherinfo where duty＜＞'领导'and major='计算机系'"；

64 leaderinfo leaderinformation=new leaderinfo（"计算机系领导信息系统"，sql）；

65 66

}

67 }）；

68 ///单击这个按钮，进入到生物系领导信息系统

69 bioleader.addActionListener（new ActionListener（）

70 {

71 public void actionPerformed（ActionEvent Event）

72 {

73 String sql="select*from teacherinfo where duty＜＞'领导'and major='生物系'"；

74 leaderinfo leaderinformation=new leaderinfo（"生物系领导信息系统"，sql）；

75 }

76 }）；

77 ///单击这个按钮，进入到机械系领导信息系统

78 mechleader.addActionListener（new ActionListener（）

79 {

80 public void actionPerformed（ActionEvent Event）

81 {

82 String sql="select*from teacherinfo where duty＜＞'领导'and major='机械系'"；

83 leaderinfo leaderinformation=new leaderinfo（"机械系领导信息系统"，sql）；

84 }

85 }）；

86 ///单击这个按钮，进入到学校领导信息系统

87 schoolleader.addActionListener（new ActionListener（）

88 {

89 public void actionPerformed（ActionEvent Event）

90 {

91 String sql="select*from teacherinfo where duty＜＞'领导'and major='学校'"；

92 leaderinfo leaderinformation=new leaderinfo（"学校领导信息系统"，sql）；

93 }

94 }）；

95 }//leadermanageframe（）构造器结束

96 97

public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

98 {

99 constraints.gridx=x；

100 constraints.gridy=y；

101 constraints.gridwidth=w；

102 constraints.gridheight=h；

103 add（c, constraints）；

104 }

105 }

【代码说明】第20～33行创建领导管理窗口，并设置窗口的大小和位置。第34～57行在窗口中设计布局，并添加按钮和标签等。第59～94行监听这些按钮的事件，并给出具体的操作。

26.4.3　主界面类的实现

主界面的代码运行如图26.6所示。单击“学生信息系统模块”按钮，会出现图26.7所示的界面。

图　26.6　主运行界面

图　26.7　学生信息系统界面

单击“教师信息系统模块”按钮，会出现图26.8所示的界面。单击“学校领导信息系统模块”按钮，会出现图26.9所示的界面。

图　26.8　教师信息系统界面

图　26.9　领导信息系统界面

26.5　数据库系统的设计

数据库系统的建立，是整个程序设计的一个关键步骤。如果没有数据库系统，那么整个应用软件就显得无丝毫意义，所以在这一节中，将会讲述数据库系统的建立，以及应该如何与系统取得连接。

26.5.1　数据库的分析

数据库的建立主要分成以下几个步骤：

（1）建立数据库中的表。

（2）再建立一个数据源。

（3）通过建立一个类，将数据源和软件界面进行连接。

在下面的章节中，将会讲述如何实现这三个步骤。

26.5.2　数据库中表的建立

在设计表之前，先建立新数据库和设计一个空表。在SQL Server中，通过SQL语句完成下面的插入语句，将所有的数据直接加入到数据库表中。

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'饶斌'，'950001'，'男'，'21'，'上海市'，'1981-03-18'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'范冰冰'，'950002'，'女'，'19'，'上海市'，'1983-11-03'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'代冉'，'950003'，'女'，'20'，'上海市'，'1982-12-14'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'富怡琴'，'950004'，'女'，'20'，'上海市'，'1982-09-01'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'姜晓燕'，'950005'，'女'，'20'，'上海市'，'1982-01-18'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'顾乔'，'950006'，'男'，'20'，'上海市'，'1982-11-08'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'郭振飞'，'950007'，'男'，'20'，'上海市'，'1982-01-11'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'张韬'，'950008'，'男'，'20'，'上海市'，'1982-05-01'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'杨慕铿'，'950009'，'男'，'20'，'上海市'，'1982-03-01'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'史正男'，'950010'，'男'，'22'，'上海市'，'1982-06-01'，'三班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'徐汉城'，'950011'，'男'，'20'，'上海市'，'1982-07-08'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'陈俊'，'950012'，'男'，'21'，'上海市'，'1981-07-13'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'卓越'，'950013'，'男'，'20'，'上海市'，'1982-08-08'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'魏国峰'，'950014'，'男'，'20'，'上海市'，'1982-03-23'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'袁乃沁'，'950015'，'女'，'18'，'上海市'，'1984-04-04'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'黄晓峰'，'950016'，'男'，'20'，'上海市'，'1982-05-03'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'齐宝华'，'950017'，'男'，'20'，'上海市'，'1982-11-11'，'二班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'节连松'，'950018'，'男'，'19'，'上海市'，'1983-02-08'，'一班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'王晓辉'，'950019'，'男'，'20'，'上海市'，'1982-04-08'，'一班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'秦嵩宁'，'950020'，'男'，'20'，'上海市'，'1982-04-11'，'一班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'金凌'，'950021'，'女'，'20'，'上海市'，'1982-12-18'，'一班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'杨坤'，'950022'，'男'，'20'，'上海市'，'1982-09-08'，'一班'，'计算机系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'徐文波'，'950101'，'男'，'21'，'上海市'，'1981-02-08'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'梁文深'，'950102'，'男'，'20'，'上海市'，'1982-09-08'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'陈雷'，'950103'，'男'，'20'，'上海市'，'1982-09-18'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'夏小勇'，'950104'，'男'，'22'，'上海市'，'1980-09-12'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'句龙'，'950105'，'男'，'20'，'上海市'，'1982-01-02'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'钱信林'，'950106'，'男'，'20'，'上海市'，'1982-03-02'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'张新伟'，'950107'，'男'，'21'，'上海市'，'1981-11-18'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'陈倩媚'，'950108'，'女'，'20'，'上海市'，'1982-04-14'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'卢婷'，'950109'，'女'，'20'，'上海市'，'1982-02-23'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'贺建林'，'950110'，'男'，'20'，'上海市'，'1982-02-01'，'一班'，'生物系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'阮从胜'，'950201'，'男'，'20'，'上海市'，'1982-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'王燕英'，'950202'，'女'，'20'，'上海市'，'1982-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'方成'，'950203'，'男'，'20'，'上海市'，'1982-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'林正新'，'950204'，'男'，'20'，'上海市'，'1982-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'陈曦'，'950205'，'男'，'20'，'上海市'，'1982-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'谭必奎'，'950206'，'男'，'20'，'上海市'，'1982-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'刘东'，'950207'，'男'，'21'，'上海市'，'1981-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）

values（'陈锋'，'950208'，'男'，'21'，'上海市'，'1981-01-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'姜红'，'950209'，'女'，'20'，'上海市'，'1982-01-01'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'张济兴'，'950210'，'男'，'20'，'上海市'，'1982-02-18'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'马木龙'，'950211'，'男'，'20'，'上海市'，'1982-03-08'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'江润生'，'950212'，'男'，'20'，'上海市'，'1982-01-08'，'二班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'洪芙蓉'，'950213'，'女'，'20'，'上海市'，'1982-01-02'，'三班'，'机械系'）；

insert into studentinfo（stname, stcode, stsexy, stage, staddress, stbirthday, class, major）values（'赵军'，'950214'，'男'，'20'，'上海市'，'1982-01-18'，'二班'，'机械系'）；

说明

这是数据库中运行的代码，这里不给代码编号，也不再单独说明，感兴趣的读者可以参考前面介绍JDBC的章节。

数据库中的表已经建立好了。下面设定ODBC数据源。

（1）单击“开始”|“控制面板”，打开窗口如图26.10所示，双击“管理工具”图标，选择“数据源”，如图26.11所示。

图　26.10　打开控制面板

图　26.11　选中数据源选项

（2）选择“数据源”后，弹出如图26.12所示的界面。由于上例中使用的是SQL数据库，而在用户数据源中没有，所以单击“添加”按钮，打开新建数据源对话框，如图26.13所示。

图　26.12　数据源管理器界面

图　26.13　创建新的数据源界面

（3）选择“SQL Server”选项，单击“完成”按钮，弹出对话框，如图26.14所示。

（4）填写好数据库名称，然后选择连接数据库服务器是哪一个，在这里是事先建立好的一个数据库“schoolmanage”，因为是本地数据库，所以服务器设置为本地计算机名或者“local”。如果是网络数据库，此时必须写入网络服务器名称，或者在下拉列表框中选择服务器。单击“下一步”按钮，出现如图26.15所示的界面。

图　26.14　确定数据库名称和服务器名称

图　26.15　设置登录

（5）单击“下一步”按钮，弹出确认对话框，如图26.16所示。继续单击“下一步”按钮，最后会出现一个测试界面。如果测试成功，说明创建数据源成功，如图26.17所示。

图　26.16　确认数据库名称

图　26.17　设置数据源完成界面

至此，数据源建立完毕。数据源建立后，接下来定义一个类，通过这个类将数据源与软件进行连接。其实这个类就是一个应用软件与数据库进行连接的接口，通过此接口，将软件界面中的控件与数据库连接，才可以让数据显示在控件中。

26.5.3　数据库中代码段

【实例26.6】本节学习如何实现数据库的连接，其详细代码如下所示。

01 class sqltest

02 {

03 private Connection con；

04 public sqltest（Connection con）

05 {

06 try

07 {

08 con=this.getConnection（）；

09 }catch（Exception e）

10 {e.printStackTrace（）；}

11 }

12 public Connection getConnection（）

13 {

14 String url1="jdbc：odbc：schoolmanage"；

15 String username=""；

16 String password=""；

17 try

18 {

19 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

20 con=DriverManager.getConnection（url1，username, password）；

21 }

22 catch（SQLException e）{e.printStackTrace（）；}

23 catch（ClassNotFoundException ex）{ex.printStackTrace（）；}

24 return con；

25 }

26 }

【代码说明】第8行获取数据库连接，第14～24行是实现连接的全部代码。第14行指定了ODBC数据源的名字。

上面的代码已经将数据库同软件进行连接的接口设计好了，下面的任务就是将这个类同整个软件中各个界面进行连接。

26.6　学生信息系统界面的设计

学生信息系统界面的设计，是这个软件系统界面的主要部分。通过这个界面，用户可以直接操作数据库。下面将详细地讲述和分析代码段。

26.6.1　学生类的设计

在设计整个程序的过程中，首先要设计一个学生类，然后将这个学生类储存到数据结构中，最后将这个数据结构中的数据同数据库连接。这样在界面上显示出来的数据就是数据库中的数据。

【实例26.7】下面是设计学生类的代码。

01 ///设置了学生的姓名、　性别、　学号、出　生年月、　年龄、年级、专业等变量

02 ///通过构造器设置学生　姓名、　学号的值

03 ///通过设置器设置其他变量的值

04 ///通过访问器获得变量的值

05 class student

06 {

07 private String name；

08 private String code；

09 private String sexy；

10 private String birthday；

11 private String address；

12 private String age；

13 private String grade；

14 private String major；

15 student（String name, String code）

16 {

17 this.name=name；

18 this.code=code；

19 }

20 public String getname（）

21 {

22 return name；

23 }

24 public String getcode（）

25 {

26 return code；

27 }

28 public void setsexy（String sexy）

29 {

30 this.sexy=sexy；

31 }

32 public void setbirthday（String birthday）

33 {

34 this.birthday=birthday；

35 }

36 public void setage（String age）

37 {

38 this.age=age；

39 }

40 public void setaddress（String address）

41 {

42 this.address=address；

43 }

44 public void setgrade（String grade）

45 {

46 this.grade=grade；

47 }

48 public void setmajor（String major）

49 {

50 this.major=major；

51 }

52 public String getsexy（）

53 {

54 return sexy；

55 }

56 public String getbirthday（）

57 {

58 return birthday；

59 }

60 public String getage（）

61 {

62 return age；

63 }

64 public String getaddress（）

65 {

66 return address；

67 }

68 public String getgrade（）

69 {

70 return grade；

71 }

72 public String getmajor（）

73 {

74 return major；

75 }

76 public String toString（）

77 {

78 String information="学生　姓名："+name+"　学号："+code+"　年龄："+age+"出　生年月：

79"+birthday+"　家庭地址："+address+"班级："+grade+"专业："+major；

80 return information；

81 }

82 }

【代码说明】以上代码将数据库中事先设计的表中的所有字段都显示出来，最后按照字符串形式输出。用户能看到的内容就是第78～79行的字符串。第20～75行是学生类的访问器和设置器。

26.6.2　存储类的设计

【实例26.8】选择什么样的数据结构对以上的学生类对象进行存储呢？本例选择Vector这种数据结构，对学生进行存储。这种数据结构同字符串数组的用法几乎一样，具体的代码如下所示。

01 import java.util.Vector；

02 import java.sql.*；

03 class storesystem

04 {

05 public Vector getstudent（Connection con, String sql）

06 {

07 Vector v=new Vector（）；

08 try

09 {

10 Statement st=con.createStatement（）；

11 ResultSet rs=st.executeQuery（sql）；

12 while（rs.next（））

13 {

14 String name=rs.getString（1）；

15 String code=rs.getString（2）；

16 String sexy=rs.getString（3）；

17 String age=rs.getString（4）；

18 String address=rs.getString（5）；

19 String birthday=rs.getString（6）；

20 String grade=rs.getString（7）；

21 String major=rs.getString（8）；

22 student ss=new student（name, code）；

23 ss.setsexy（sexy）；

24 ss.setmajor（major）；

25 ss.setbirthday（birthday）；

26 ss.setaddress（address）；

27 ss.setage（age）；

28 ss.setgrade（grade）；

29 v.add（ss）；

30 }

31 rs.close（）；

32 }

33 catch（Exception e）

34 {

35 e.printStackTrace（）；

36 }

37 return v；

38 }

39 40

public student getobject（Connection con, String stname）

41 {

42 student sst=null；

43 try

44 {

45 Statement st=con.createStatement（）；

46 String sql="select*from studentinfo where stname='"+stname+"'"；

47 ResultSet rs=st.executeQuery（sql）；

48 ///通过结果集中的getString方法从数据库表中提取表字段的数据

49 ///再将提取出来的数据赋值给学生对象

50 ///最后将学生对象存储到vector数据结构中

51 while（rs.next（））

52 {

53 String code=rs.getString（2）；

54 String sexy=rs.getString（3）；

55 String age=rs.getString（4）；

56 String address=rs.getString（5）；

57 String birthday=rs.getString（6）；

58 String grade=rs.getString（7）；

59 String major=rs.getString（8）；

60 sst=new student（stname, code）；

61 sst.setsexy（sexy）；

62 sst.setmajor（major）；

63 sst.setbirthday（birthday）；

64 sst.setaddress（address）；

65 sst.setage（age）；

66 sst.setgrade（grade）；

67 }

68 rs.close（）；

69 70

}

71 catch（Exception e）

72 {

73 e.printStackTrace（）；

74 }

75 return sst；

76 }

77 ///通过设置数据库的URL、密码、用户名来建立与数据库的连接

78 public Connection getConnection（）

79 {

80 Connection con=null；

81 String url1="jdbc：odbc：schoolmanage"；

82 String username=""；

83 String password=""；

84 try

85 {

86 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

87 con=DriverManager.getConnection（url1，username, password）；

88 }

89 catch（SQLException e）{e.printStackTrace（）；

90 }

91 catch（ClassNotFoundException ex）{ex.printStackTrace（）；

92 }

93 return con；

94 }

95 }

【代码说明】以上代码从数据库中提取数据，然后按照每个字段的不同，分别赋值给学生类中的每个字段。最后，将学生类的对象添加到Vector数据结构中。这样数据存取速度大大加快，每次对数据库的操作，就不必在数据库中执行。

本程序属于本地程序，如果是在网络数据库中，这样操作会增加网络的负担。解决方案是可以将网络数据库下载到本地的Vector的数据结构中，然后直接操作本地数据结构，等操作完后，再提交到数据库中，这样就能加快速度。

26.6.3　学生信息界面的设计

学生信息界面主要包括显示信息的控件、添加对象的控件、删除对象的控件。在这个学生信息界面中，将学生信息界面抽象成一个类，然后让所有不同班级和不同系的学生信息系统都能调用它。

【实例26.9】下面设计学生信息的界面，代码如下所示。

01 import javax.swing.*；

02 import java.awt.*；

03 import java.awt.event.*；

04 import java.util.Vector；

05 import java.sql.*；

06 ///这是学生类信息系统的框架类

07 ///studentinfoframe是顶层窗口框架

08 ///通过八个文本域控件来分别显示不同班级、不同系的学生信息

09 ///三个按钮控件分别产生三个不同的动作事件，分别是添加、删除和更新。addition是

10 ///添加，delete是删除，update是更新

11 ///在布局方面有两个布局管理器，一个是lay即网格组布局，一个是jp即GridLayout布局

12 ///通过不同的SQL语句进入不同的班级和系的管理系统

13 ///先将数据库中的数据导入到vec中。然后再将vec中的数据导入学生类中。最后再将数据

14 ///输出

15 class studentinfo extends JPanel

16 {

17 private static final long serialVersionUID=1L；

18 static final int WIDTH=700；

19 static final int HEIGHT=400；

20 final JComboBox nameinput；

21 final JTextField codeinput；

22 final JTextField sexyinput；

23 final JTextField birthdayinput；

24 final JTextField ageinput；

25 final JTextField addressinput；

26 final JTextField gradeinput；

27 final JTextField majorinput；

28 JFrame studentinfoframe；

29 storesystem store=new storesystem（）；

30 Connection con=store.getConnection（）；

31 addframe af=new addframe（）；

32 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

33 {

34 constraints.gridx=x；

35 constraints.gridy=y；

36 constraints.gridwidth=w；

37 constraints.gridheight=h；

38 add（c, constraints）；

39 }

40 public studentinfo（String str, String sql）

41 {

42 studentinfoframe=new JFrame（）；

43 studentinfoframe.setTitle（str）；

44 studentinfoframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

45 studentinfoframe.setSize（WIDTH, HEIGHT）；

46 Toolkit kit=Toolkit.getDefaultToolkit（）；

47 Dimension screenSize=kit.getScreenSize（）；

48 int width=screenSize.width；

49 int height=screenSize.height；

50 int x=（width-WIDTH）/2；

51 int y=（height-HEIGHT）/2；

52 studentinfoframe.setLocation（x, y）；

53 studentinfoframe.setVisible（true）；

54 //studentinfoframe.setResizable（false）；

55 studentinfoframe.add（this, BorderLayout.CENTER）；

56 GridBagLayout lay=new GridBagLayout（）；

57 setLayout（lay）；

58 JLabel name=new JLabel（"姓名"）；

59 JLabel code=new JLabel（"　学号"）；

60 JLabel sexy=new JLabel（"　性别"）；

61 JLabel age=new JLabel（"　年龄"）；

62 JLabel birthday=new JLabel（"出　生年月"）；

63 JLabel address=new JLabel（"　家庭地址"）；

64 JLabel grade=new JLabel（"班级"）；

65 JLabel major=new JLabel（"专业"）；

66 JLabel title=new JLabel（str）；

67 nameinput=new JComboBox（）；

68 codeinput=new JTextField（10）；

69 sexyinput=new JTextField（10）；

70 ageinput=new JTextField（10）；

71 birthdayinput=new JTextField（10）；

72 addressinput=new JTextField（10）；

73 gradeinput=new JTextField（10）；

74 majorinput=new JTextField（10）；

75 ///将存储在Vector中的数据提取出来重新赋值给学生对象。再利用学生对象中getname方法来

76 ///提取出学生　姓名来，最后使用组合列表框的添加功能将这些名字添加到列表中

77 Vector vec=store.getstudent（con, sql）；

78 for（int i=0；i＜vec.size（）；i++）

79 {

80 student one=（student）vec.get（i）；

81 String nameselect=one.getname（）；

82 nameinput.addItem（nameselect）；

83 }

84 String namestring=（String）nameinput.getSelectedItem（）；

85 student p=store.getobject（con, namestring）；

86 String inputcode=p.getcode（）；

87 String inputsexy=p.getsexy（）；

88 String inputage=p.getage（）；

89 String inputbirthday=p.getbirthday（）；

90 String inputaddress=p.getaddress（）；

91 String inputgrade=p.getgrade（）；

92 String inputmajor=p.getmajor（）；

93 codeinput.setText（inputcode）；

94 sexyinput.setText（inputsexy）；

95 ageinput.setText（inputage）；

96 birthdayinput.setText（inputbirthday）；

97 addressinput.setText（inputaddress）；

98 gradeinput.setText（inputgrade）；

99 majorinput.setText（inputmajor）；

100 JButton addition=new JButton（"添加"）；

101 JButton delete=new JButton（"删除"）；

102 JButton update=new JButton（"更新"）；

103 JButton bereturn=new JButton（"返回"）；

104 GridBagConstraints constraints=new GridBagConstraints（）；

105 constraints.fill=GridBagConstraints.NONE；

106 constraints.weightx=4；

107 constraints.weighty=6；

108 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

109 add（name, constraints，0，1，1，1）；

110 add（code, constraints，0，2，1，1）；

111 add（sexy, constraints，0，3，1，1）；

112 add（age, constraints，0，4，1，1）；

113 add（nameinput, constraints，1，1，1，1）；

114 add（codeinput, constraints，1，2，1，1）；

115 add（sexyinput, constraints，1，3，1，1）；

116 add（ageinput, constraints，1，4，1，1）；

117 add（birthday, constraints，2，1，1，1）；

118 add（address, constraints，2，2，1，1）；

119 add（grade, constraints，2，3，1，1）；

120 add（major, constraints，2，4，1，1）；

121 add（birthdayinput, constraints，3，1，1，1）；

122 add（addressinput, constraints，3，2，1，1）；

123 add（gradeinput, constraints，3，3，1，1）；

124 add（majorinput, constraints，3，4，1，1）；

125 add（addition, constraints，0，5，1，1）；

126 add（delete, constraints，1，5，1，1）；

127 add（update, constraints，2，5，1，1）；

128 add（bereturn, constraints，3，5，1，1）；

129 ///通过单击每一个列表框中的名字，会相应地显示出此名字的所有信息

130 nameinput.addItemListener（new ItemListener（）

131 {

132 public void itemStateChanged（ItemEvent e）

133 {

134 String namestring=（String）nameinput.getSelectedItem（）；

135 student p=store.getobject（con, namestring）；

136 String inputcode=p.getcode（）；

137 String inputsexy=p.getsexy（）；

138 String inputage=p.getage（）；

139 String inputbirthday=p.getbirthday（）；

140 String inputaddress=p.getaddress（）；

141 String inputgrade=p.getgrade（）；

142 String inputmajor=p.getmajor（）；

143 codeinput.setText（inputcode）；

144 sexyinput.setText（inputsexy）；

145 ageinput.setText（inputage）；

146 birthdayinput.setText（inputbirthday）；

147 addressinput.setText（inputaddress）；

148 gradeinput.setText（inputgrade）；

149 majorinput.setText（inputmajor）；

150 }

151 }）；

152 ///返回主菜单

153 bereturn.addActionListener（new ActionListener（）

154 {

155 public void actionPerformed（ActionEvent Event）

156 {

157 studentmanageframe manageframe=new studentmanageframe（）；

158 manageframe.setVisible（true）；

159 studentinfoframe.dispose（）；

1 }

161 }）；

162 ///进入添加主界面

163 addition.addActionListener（new ActionListener（）

164 {

165 public void actionPerformed（ActionEvent Event）

166 {

167 addframe addfr=new addframe（）；

168 addfr.setVisible（true）；

169 }

170 }）；

171 ///更新按钮，使得从数据库表中，将数据提取显示到界面中

172 update.addActionListener（new ActionListener（）

173 {

174 public void actionPerformed（ActionEvent Event）

175 {

176 String name1=af.ss.getname（）；

177 String code1=af.ss.getcode（）；

178 String age1=af.ss.getage（）；

179 String sexy1=af.ss.getsexy（）；

180 String birthday1=af.ss.getbirthday（）；

181 String address1=af.ss.getaddress（）；

182 String grade1=af.ss.getgrade（）；

183 String major1=af.ss.getmajor（）；

184 nameinput.addItem（name1）；

185 nameinput.setSelectedItem（name1）；

186 codeinput.setText（code1）；

187 ageinput.setText（age1）；

188 sexyinput.setText（sexy1）；

189 addressinput.setText（address1）；

190 birthdayinput.setText（birthday1）；

191 gradeinput.setText（grade1）；

192 majorinput.setText（major1）；

193 }

194 }）；

195 ///删除数据，且更新到数据库中

196 delete.addActionListener（new ActionListener（）

197 {

198 public void actionPerformed（ActionEvent Event）

199 {

200 String namestring=（String）nameinput.getSelectedItem（）；

201 storesystem store=new storesystem（）；

202 try

203 {

204 Connection con=store.getConnection（）；

205 Statement st=con.createStatement（）；

206 String sql="delete*from studentinfo where='"+namestring+"'"；

207 st.executeUpdate（sql）；

208 }

6 0

209 catch（Exception e）{}

210 nameinput.removeItem（namestring）；

211 }

212 213

}）；

214 }

215 }

【代码说明】第42～55行创建窗口并设置窗口的各个属性。第56～128行设置标签和按钮控件等。第130～151行侦听下拉列表框的选择并作出反馈。第172～194行从数据库表提取数据并显示到界面中。第196～213行删除数据。

这样整个学生信息系统的界面就设计完毕。

【运行效果】下面运行程序，如图26.18所示。

图　26.18　学生信息系统界面

单击“添加”按钮，弹出一个对话框，这是一个框架界面。下面演示这个框架界面如何实现。

【实例26.10】设计显示信息的控件组，代码如下所示。

01 ///主运行类

02 ///main指主框架

03 ///setDefaultCloseOperation指让框架右上角的按钮都有效

04 ///show让框架能显示出来

05 import java.awt.*；

06 import java.awt.event.*；

07 import java.sql.*；

08 import java.util.Vector；

09 import javax.swing.*；

10 ///这个是学生信息系统添加信息框架类

11 ///通过八个文本域将不同信息的学生分别输出在其中

12 ///通过一个添加按钮，将数据添加到数据库中

13 ///在这里的布局是通过网格组布局管理器来设定的

14 ///在回送到信息界面时将学生对象ss设定为静态。放置新建对象时，其值会改变

15 16 class addframe extends JPanel

17 {

18 String codetext；

19 String agetext；

20 String sexytext；

21 String birthdaytext；

22 String addresstext；

23 String gradetext；

24 String majortext；

25 public final JTextField nameinput=new JTextField（10）；

26 private static final long serialVersionUID=1L；

27 static final int WIDTH=700；

28 static final int HEIGHT=400；

29 static student ss；

30 JFrame studentaddframe；

31 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

32 {

33 constraints.gridx=x；

34 constraints.gridy=y；

35 constraints.gridwidth=w；

36 constraints.gridheight=h；

37 add（c, constraints）；

38 }

39 public addframe（）

40 {

41 studentaddframe=new JFrame（）；

42 studentaddframe.setTitle（"学生添加系统"）；

43 studentaddframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

44 studentaddframe.setSize（WIDTH, HEIGHT）；

45 Toolkit kit=Toolkit.getDefaultToolkit（）；

46 Dimension screenSize=kit.getScreenSize（）；

47 int width=screenSize.width；

48 int height=screenSize.height；

49 int x=（width-WIDTH）/2；

50 int y=（height-HEIGHT）/2；

51 studentaddframe.setLocation（x, y）；

52 studentaddframe.setVisible（true）；

53 //studentaddframe.setResizable（false）；

54 studentaddframe.add（this, BorderLayout.CENTER）；

55 GridBagLayout lay=new GridBagLayout（）；

56 setLayout（lay）；

57 JLabel name=new JLabel（"姓名"）；

58 JLabel code=new JLabel（"　学号"）；

59 JLabel sexy=new JLabel（"　性别"）；

60 JLabel age=new JLabel（"　年龄"）；

61 JLabel birthday=new JLabel（"出　生年月"）；

62 JLabel address=new JLabel（"　家庭地址"）；

63 JLabel grade=new JLabel（"班级"）；

64 JLabel major=new JLabel（"专业"）；

65 final JTextField codeinput=new JTextField（10）；

66 final JTextField sexyinput=new JTextField（10）；

67 final JTextField ageinput=new JTextField（10）；

68 final JTextField birthdayinput=new JTextField（10）；

69 final JTextField addressinput=new JTextField（10）；

70 final JTextField gradeinput=new JTextField（10）；

71 final JTextField majorinput=new JTextField（10）；

72 JLabel title=new JLabel（"学生被添加的基本信息"）；

73 JButton additionbutton=new JButton（"添加"）；

74 GridBagConstraints constraints=new GridBagConstraints（）；

75 constraints.fill=GridBagConstraints.NONE；

76 constraints.weightx=4；

77 constraints.weighty=6；

78 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

79 add（name, constraints，0，1，1，1）；

80 add（code, constraints，0，2，1，1）；

81 add（sexy, constraints，0，3，1，1）；

82 add（age, constraints，0，4，1，1）；

83 add（nameinput, constraints，1，1，1，1）；

84 add（codeinput, constraints，1，2，1，1）；

85 add（sexyinput, constraints，1，3，1，1）；

86 add（ageinput, constraints，1，4，1，1）；

87 add（birthday, constraints，2，1，1，1）；

88 add（address, constraints，2，2，1，1）；

89 add（grade, constraints，2，3，1，1）；

90 add（major, constraints，2，4，1，1）；

91 add（birthdayinput, constraints，3，1，1，1）；

92 add（addressinput, constraints，3，2，1，1）；

93 add（gradeinput, constraints，3，3，1，1）；

94 add（majorinput, constraints，3，4，1，1）；

95 add（additionbutton, constraints，0，5，4，1）；

96 ///将每个文本域中的信息赋值给变量，再将变量以值的形式存储到数据库的表中

97 ///最后，再将这些值存储到学生对象中，并且这个学生对象是一个静态的对象

98 additionbutton.addActionListener（new ActionListener（）

99 {

100 public void actionPerformed（ActionEvent Event）

101 {

102 try

103 {

104 String nametext=nameinput.getText（）；

105 codetext=codeinput.getText（）；

106 agetext=ageinput.getText（）；

107 sexytext=sexyinput.getText（）；

108 birthdaytext=birthdayinput.getText（）；

109 addresstext=addressinput.getText（）；

110 gradetext=gradeinput.getText（）；

111 majortext=majorinput.getText（）；

112 storesystem store=new storesystem（）；

113 Connection con=store.getConnection（）；

114 Statement st=con.createStatement（）；

115 String sql="insert into studentinfo

116 values（'"+nametext+"'，'"+codetext+"'，'"+sexytext+"'，'"+agetext+"'，'"+addresstext+"'，'"

117+birthdaytext+"'，'"+gradetext+"'，'"+majortext+"'）"；

118 st.executeUpdate（sql）；

119 ss=new student（nametext, codetext）；

120 ss.setage（agetext）；

121 ss.setsexy（sexytext）；

122 ss.setaddress（addresstext）；

123 ss.setbirthday（birthdaytext）；

124 ss.setgrade（gradetext）；

125 ss.setmajor（majortext）；

126 Vector vec=new Vector（）；

127 vec.add（ss）；

128 }

129 catch（Exception e）{}

130 studentaddframe.dispose（）；

131 }

132 }）；

133 }

134 }

【代码说明】第18～30行定义一些变量和常量，为下面的操作做准备。第41～95行创建窗口，并添加窗口中的标签、按钮和文本框等。第100～131行实现添加学生到数据库的操作。

【运行效果】运行上述代码，效果如图26.19所示。

图　26.19　添加学生系统的界面

说明　添加完成后，可以去数据库看看是否已经完成了数据的添加。

26.7　教师信息系统界面的设计

教师信息系统界面的设计，也是这个软件系统的界面的主要部分。通过这个界面，用户可以直接操作数据库。

26.7.1　教师类的设计

在设计整个程序的过程中，首先要设计一个教师类，然后将这个教师类储存到一个数据结构中，最后将数据结构中的数据同数据库连接。这样在界面上显示出来的数据就是数据库中的数据。

【实例26.11】设计教师类的代码如下所示。

01 ///设置了教师的姓名、　性别、工号、出　生年月、　年龄、薪水、专业等变量

02 ///通过构造器设置　教师姓名、工号的值

03 ///通过设置器设置其他变量的值

04 ///通过访问器获得变量的值

05 class teacher

06 {

07 private String name；

08 private String code；

09 private String sexy；

10 private String birthday；

11 private String address；

12 private String age；

13 private String salary；

14 private String major；

15 private String duty；

16 public teacher（String name, String code）

17 {

18 this.name=name；

19 this.code=code；

20 }

21 public String getname（）

22 {

23 return name；

24 }

25 public String getcode（）

26 {

27 return code；

28 }

29 public void setsexy（String sexy）

30 {

31 this.sexy=sexy；

32 }

33 public void setbirthday（String birthday）

34 {

35 this.birthday=birthday；

36 }

37 public void setage（String age）

38 {

39 this.age=age；

40 }

41 public void setaddress（String address）

42 {

43 this.address=address；

44 }

45 public void setsalary（String salary）

46 {

47 this.salary=salary；

48 }

49 public void setmajor（String major）

50 {

51 this.major=major；

52 }

53 public void setduty（String duty）

54 {

55 this.duty=duty；

56 }

57 public String getsexy（）

58 {

59 return sexy；

60 }

61 public String getbirthday（）

62 {

63 return birthday；

64 }

65 public String getage（）

66 {

67 return age；

68 }

69 public String getaddress（）

70 {

71 return address；

72 }

73 public String getsalary（）

74 {

75 return salary；

76 }

77 public String getmajor（）

78 {

79 return major；

80 }

81 public String getduty（）

82 {

83 return duty；

84 }

85 }

【代码说明】以上代码将整个数据库中事先设计的表中的所有字段都显示出来，最后按照字符串形式输出。如果此时还没有在数据库中添加teacherinfo表，根据上述代码设计此表的字段并添加到schoolmanage数据库中。

26.7.2　存储类的设计

那么将选择什么样的数据结构，对以上的教师类对象进行存储呢？本例选择Vector数据结构，对教师进行存储。

【实例26.12】这种数据结构同字符串数组的用法一样，具体代码如下所示。

01 import java.sql.Connection；

02 import java.sql.DriverManager；

03 import java.sql.ResultSet；

04 import java.sql.SQLException；

05 import java.sql.Statement；

06 import java.util.Vector；

07 class storesystem2

08 {

09 public Vector getteacher（Connection con, String sql）

10 {

11 Vector v=new Vector（）；

12 try

13 {

14 Statement st=con.createStatement（）；

15 ResultSet rs=st.executeQuery（sql）；

16 ///通过结果集中的getString方法从数据库表中提取表字段的数据

17 ///再将提取出来的数据赋值给教师对象

18 ///最后将教师对象存储到vector数据结构中

19 while（rs.next（））

20 {

21 String name=rs.getString（1）；

22 String code=rs.getString（2）；

23 String sexy=rs.getString（3）；

24 String age=rs.getString（4）；

25 String address=rs.getString（5）；

26 String birthday=rs.getString（6）；

27 String salary=rs.getString（7）；

28 String major=rs.getString（8）；

29 String duty=rs.getString（9）；

30 teacher ss=new teacher（name, code）；

31 ss.setsexy（sexy）；

32 ss.setmajor（major）；

33 ss.setbirthday（birthday）；

34 ss.setaddress（address）；

35 ss.setage（age）；

36 ss.setsalary（salary）；

37 ss.setduty（duty）；

38 v.add（ss）；

39 }

40 rs.close（）；

41 }

42 catch（Exception e）

43 {

44 e.printStackTrace（）；

45 }

46 return v；

47 }

48 public teacher getobject（Connection con, String stname）

49 {

50 teacher sst=null；

51 try

52 {

53 Statement st=con.createStatement（）；

54 String sql="select*from teacherinfo where name='"+stname+"'"；

55 ResultSet rs=st.executeQuery（sql）；

56 while（rs.next（））

57 {

58 String code=rs.getString（2）；

59 String sexy=rs.getString（3）；

60 String age=rs.getString（4）；

61 String address=rs.getString（5）；

62 String birthday=rs.getString（6）；

63 String salary=rs.getString（7）；

64 String major=rs.getString（8）；

65 String duty=rs.getString（9）；

66 sst=new teacher（stname, code）；

67 sst.setsexy（sexy）；

68 sst.setmajor（major）；

69 sst.setbirthday（birthday）；

70 sst.setaddress（address）；

71 sst.setage（age）；

72 sst.setsalary（salary）；

73 sst.setduty（duty）；

74 }

75 rs.close（）；

76 77

}

78 catch（Exception e）

79 {

80 e.printStackTrace（）；

81 }

82 return sst；

83 }

84 ///通过设置数据库的URL、密码、用户名来建立与数据库的连接

85 public Connection getConnection（）

86 {

87 Connection con=null；

88 String url1="jdbc：odbc：schoolmanage"；

89 String username=""；

90 String password=""；

91 try

92 {

93 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

94 con=DriverManager.getConnection（url1，username, password）；

95 }

96 catch（SQLException e）{e.printStackTrace（）；

97 }

98 catch（ClassNotFoundException ex）{ex.printStackTrace（）；

99 }

100 return con；

101 }

102 }

【代码说明】以上代码是让程序从数据库中提取数据，然后按照每个字段的不同，分别赋值给教师类中的每个字段，最后，将教师类的对象添加到Vector数据结构中。这样每次对数据库的操作就不需要在数据库中执行。

上述程序是本地程序，如果是网络数据库，这样操作会增加网络的负担。解决方法是可以将网络数据库下载到本地的Vector数据结构中，然后直接操作本地数据结构，操作完毕后，再提交到数据库中，这样就大大提高了数据操作的速度。

26.7.3　教师信息界面的设计

教师信息界面主要包括显示信息的控件、添加对象的控件、删除对象的控件。在教师信息界面中，将教师信息界面抽象成一个类，然后让所有不同系的教师信息系统都能调用它。

【案例26.13】设计教师信息的界面，代码如下所示。

01 import java.awt.BorderLayout；

02 import java.awt.*；

03 import javax.swing.*；

04 import java.awt.event.*；

05 import java.util.Vector；

06 import java.sql.*；

07 ///这是教师类信息系统的框架类

08 ///teacherinfoframe是顶层窗口框架

09 ///通过九个文本域控件来分别可以显示不同专业、不同职务的教师信息

10 ///三个按钮控件分别产生三个不同的动作事件，分别是添加、删除和更新。addition是

11 ///添加，delete是删除，update是更新

12 ///在布局方面有两个布局管理器，一个是lay即网格组布局，另一个是jp即GridLayout布局

13 ///通过不同的SQL语句进入不同的管理系统

14 ///先将数据库中的数据导入到vec中。然后再将vec中的数据导入到教师类中。最后再将数据

15 ///输出

16 class teacherinfo extends JPanel

17 {

18 private static final long serialVersionUID=1L；

19 static final int WIDTH=700；

20 static final int HEIGHT=400；

21 final JTextField codeinput；

22 final JTextField sexyinput；

23 final JTextField birthdayinput；

24 final JTextField ageinput；

25 final JTextField addressinput；

26 final JTextField salaryinput；

27 final JTextField majorinput；

28 final JTextField dutyinput；

29 JFrame teacherinfoframe；

30 storesystem2 store=new storesystem2（）；

31 Connection con=store.getConnection（）；

32 addframe2 af=new addframe2（）；

33 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

34 {

35 constraints.gridx=x；

36 constraints.gridy=y；

37 constraints.gridwidth=w；

38 constraints.gridheight=h；

39 add（c, constraints）；

40 }

41 public teacherinfo（String str, String sql）

42 {

43 teacherinfoframe=new JFrame（）；

44 teacherinfoframe.setTitle（str）；

45 teacherinfoframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

46 teacherinfoframe.setSize（WIDTH, HEIGHT）；

47 Toolkit kit=Toolkit.getDefaultToolkit（）；

48 Dimension screenSize=kit.getScreenSize（）；

49 int width=screenSize.width；

50 int height=screenSize.height；

51 int x=（width-WIDTH）/2；

52 int y=（height-HEIGHT）/2；

53 teacherinfoframe.setLocation（x, y）；

54 teacherinfoframe.setVisible（true）；

55 //teacherinfoframe.setResizable（false）；

56 teacherinfoframe.add（this, BorderLayout.CENTER）；

57 GridBagLayout lay=new GridBagLayout（）；

58 setLayout（lay）；

59 JLabel name=new JLabel（"姓名"）；

60 JLabel code=new JLabel（"工号"）；

61 JLabel sexy=new JLabel（"　性别"）；

62 JLabel age=new JLabel（"　年龄"）；

63 JLabel birthday=new JLabel（"出　生年月"）；

64 JLabel address=new JLabel（"　家庭地址"）；

65 JLabel salary=new JLabel（"薪水"）；

66 JLabel major=new JLabel（"专业"）；

67 JLabel duty=new JLabel（"职务"）；

68 JLabel title=new JLabel（str）；

69 nameinput=new JComboBox（）；

70 codeinput=new JTextField（10）；

71 sexyinput=new JTextField（10）；

72 ageinput=new JTextField（10）；

73 birthdayinput=new JTextField（10）；

74 addressinput=new JTextField（10）；

75 salaryinput=new JTextField（10）；

76 majorinput=new JTextField（10）；

77 dutyinput=new JTextField（10）；

78 ///将存储在Vector中的数据提取出来重新赋值给教师对象。再利用教师对象中的getname方法

79 ///来提取出　教师姓名来，最后使用组合列表框的添加功能将这些名字添加到列表中

80 Vector vec=store.getteacher（con, sql）；

81 for（int i=0；i＜vec.size（）；i++）

82 {

83 teacher one=（teacher）vec.get（i）；

84 String nameselect=one.getname（）；

85 nameinput.addItem（nameselect）；

86 }

87 String namestring=（String）nameinput.getSelectedItem（）；

88 teacher p=store.getobject（con, namestring）；

89 String inputcode=p.getcode（）；

90 String inputsexy=p.getsexy（）；

91 String inputage=p.getage（）；

92 String inputbirthday=p.getbirthday（）；

93 String inputaddress=p.getaddress（）；

94 String inputsalary=p.getsalary（）；

95 String inputmajor=p.getmajor（）；

96 String inputduty=p.getduty（）；

97 codeinput.setText（inputcode）；

98 sexyinput.setText（inputsexy）；

99 ageinput.setText（inputage）；

100 birthdayinput.setText（inputbirthday）；

101 addressinput.setText（inputaddress）；

102 salaryinput.setText（inputsalary）；

103 majorinput.setText（inputmajor）；

104 dutyinput.setText（inputduty）；

105 JButton addition=new JButton（"添加"）；

106 JButton delete=new JButton（"删除"）；

107 JButton update=new JButton（"更新"）；

108 JButton bereturn=new JButton（"返回"）；

109 GridBagConstraints constraints=new GridBagConstraints（）；

110 constraints.fill=GridBagConstraints.NONE；

111 constraints.weightx=4；

112 constraints.weighty=7；

113 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

114 add（name, constraints，0，1，1，1）；

115 add（code, constraints，0，2，1，1）；

116 add（sexy, constraints，0，3，1，1）；

117 add（age, constraints，0，4，1，1）；

118 add（nameinput, constraints，1，1，1，1）；

119 add（codeinput, constraints，1，2，1，1）；

120 add（sexyinput, constraints，1，3，1，1）；

121 add（ageinput, constraints，1，4，1，1）；

122 add（birthday, constraints，2，1，1，1）；

123 add（address, constraints，2，2，1，1）；

124 add（salary, constraints，2，3，1，1）；

125 add（major, constraints，2，4，1，1）；

126 add（duty, constraints，0，5，1，1）；

127 add（birthdayinput, constraints，3，1，1，1）；

128 add（addressinput, constraints，3，2，1，1）；

129 add（salaryinput, constraints，3，3，1，1）；

130 add（dutyinput, constraints，1，5，1，1）；

131 add（majorinput, constraints，3，4，1，1）；

132 add（addition, constraints，0，6，1，1）；

133 add（delete, constraints，1，6，1，1）；

134 add（update, constraints，2，6，1，1）；

135 add（bereturn, constraints，3，6，1，1）；

136 ///通过单击每一个列表框中的名字，会相应地显示出此名字的所有信息

137 nameinput.addItemListener（new ItemListener（）

138 {

139 public void itemStateChanged（ItemEvent e）

140 {

141 String namestring=（String）nameinput.getSelectedItem（）；

142 teacher p=store.getobject（con, namestring）；

143 String inputcode=p.getcode（）；

144 String inputsexy=p.getsexy（）；

145 String inputage=p.getage（）；

146 String inputbirthday=p.getbirthday（）；

147 String inputaddress=p.getaddress（）；

148 String inputsalary=p.getsalary（）；

149 String inputmajor=p.getmajor（）；

150 String inputduty=p.getduty（）；

151 codeinput.setText（inputcode）；

152 sexyinput.setText（inputsexy）；

153 ageinput.setText（inputage）；

154 birthdayinput.setText（inputbirthday）；

155 addressinput.setText（inputaddress）；

156 salaryinput.setText（inputsalary）；

157 majorinput.setText（inputmajor）；

158 dutyinput.setText（inputduty）；

159 }

160 }）；

161 ///返回主菜单

162 bereturn.addActionListener（new ActionListener（）

163 {

164 public void actionPerformed（ActionEvent Event）

165 {

166 teachermanageframe manageframe=new teachermanageframe（）；

167 manageframe.setVisible（true）；

168 teacherinfoframe.dispose（）；

169 }

170 }）；

171 ///进入添加主界面

172 addition.addActionListener（new ActionListener（）

173 {

174 public void actionPerformed（ActionEvent Event）

175 {

176 addframe2 addfr=new addframe2（）；

177 addfr.setVisible（true）；

178 }

179 }）；

180 ///更新按钮，使得从数据库表中，将数据提取显示到界面中

181 update.addActionListener（new ActionListener（）

182 {

183 public void actionPerformed（ActionEvent Event）

184 {

185 String name1=af.ss.getname（）；

186 String code1=af.ss.getcode（）；

187 String age1=af.ss.getage（）；

188 String sexy1=af.ss.getsexy（）；

189 String birthday1=af.ss.getbirthday（）；

190 String address1=af.ss.getaddress（）；

191 String salary1=af.ss.getsalary（）；

192 String major1=af.ss.getmajor（）；

193 String duty1=af.ss.getduty（）；

194 nameinput.addItem（name1）；

195 nameinput.setSelectedItem（name1）；

196 codeinput.setText（code1）；

197 ageinput.setText（age1）；

198 sexyinput.setText（sexy1）；

199 addressinput.setText（address1）；

200 birthdayinput.setText（birthday1）；

201 salaryinput.setText（salary1）；

202 majorinput.setText（major1）；

203 dutyinput.setText（duty1）；

204 }

205 }）；

206 ///删除数据，且更新到数据库中

207 delete.addActionListener（new ActionListener（）

208 {

209 public void actionPerformed（ActionEvent Event）

210 {

211 String namestring=（String）nameinput.getSelectedItem（）；

212 storesystem2 store=new storesystem2（）；

213 try

214 {

215 Connection con=store.getConnection（）；

216 Statement st=con.createStatement（）；

217 String sql="delete*from teacherinfo where='"+namestring+"'"；

218 st.executeUpdate（sql）；

219 }

220 catch（Exception e）{}

221 nameinput.removeItem（namestring）；

222 }

223 }）；

224 }

225 }

【代码说明】代码第43～56行创建窗口并设置窗口的各个属性。第57～135行设置标签和按钮控件等。第137～160行侦听下拉列表框的选择并作出反馈。第181～205行从数据库表提取数据并显示到界面中。第207～223行删除数据。

【运行效果】这样整个教师信息系统的界面设计完毕，程序段的运行结果如图26.20所示。

图　26.20　教师添加信息系统

【实例26.14】单击“添加”按钮，会弹出一个对话框，这是一个框架界面。下面实现这个框架界面的设计，代码如下所示。

01 ///主运行类

02 ///main指主框架

03 ///setDefaultCloseOperation指让框架右上角的按钮都有效

04 ///show让框架能显示出来

05 import java.awt.BorderLayout；

06 import java.awt.Component；

07 import java.awt.Dimension；

08 import java.awt.GridBagConstraints；

09 import java.awt.GridBagLayout；

10 import java.awt.Toolkit；

11 import java.awt.event.ActionEvent；

12 import java.awt.event.ActionListener；

13 import java.sql.Connection；

14 import java.sql.Statement；

15 import java.util.Vector；

16 import javax.swing.JButton；

17 import javax.swing.JFrame；

18 import javax.swing.JLabel；

19 import javax.swing.JTextField；

20 import javax.swing.JPanel；

21 ///这个是教师信息系统添加信息框架类

22 ///通过九个文本域将不同信息的教师分别输出在其中

23 ///通过一个添加按钮，将数据添加到数据库中

24 ///在这里的布局是通过网格组布局管理器来设定的

25 ///在回送到信息界面时将教师对象ss设定为静态。放置新建对象时，其值会改变

26 class addframe2 extends JPanel

27 {

28 String codetext；

29 String agetext；

30 String sexytext；

31 String birthdaytext；

32 String addresstext；

33 String salarytext；

34 String majortext；

35 String dutytext；

36 public final JTextField nameinput=new JTextField（10）；

37 private static final long serialVersionUID=1L；

38 static final int WIDTH=700；

39 static final int HEIGHT=400；

40 static teacher ss；

41 JFrame teacheraddframe；

42 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

43 {

44 constraints.gridx=x；

45 constraints.gridy=y；

46 constraints.gridwidth=w；

47 constraints.gridheight=h；

48 add（c, constraints）；

49 }

50 public addframe2（）

51 {

52 teacheraddframe=new JFrame（）；

53 teacheraddframe.setTitle（"教师添加系统"）；

54 teacheraddframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

55 teacheraddframe.setSize（WIDTH, HEIGHT）；

56 Toolkit kit=Toolkit.getDefaultToolkit（）；

57 Dimension screenSize=kit.getScreenSize（）；

58 int width=screenSize.width；

59 int height=screenSize.height；

60 int x=（width-WIDTH）/2；

61 int y=（height-HEIGHT）/2；

62 teacheraddframe.setLocation（x, y）；

63 teacheraddframe.setVisible（true）；

64 //teacheraddframe.setResizable（false）；

65 teacheraddframe.add（this, BorderLayout.CENTER）；

66 GridBagLayout lay=new GridBagLayout（）；

67 setLayout（lay）；

68 JLabel name=new JLabel（"姓名"）；

69 JLabel code=new JLabel（"工号"）；

70 JLabel sexy=new JLabel（"　性别"）；

71 JLabel age=new JLabel（"　年龄"）；

72 JLabel birthday=new JLabel（"出　生年月"）；

73 JLabel address=new JLabel（"　家庭地址"）；

74 JLabel salary=new JLabel（"薪水"）；

75 JLabel major=new JLabel（"专业"）；

76 JLabel duty=new JLabel（"职务"）；

77 final JTextField codeinput=new JTextField（10）；

78 final JTextField sexyinput=new JTextField（10）；

79 final JTextField ageinput=new JTextField（10）；

80 final JTextField birthdayinput=new JTextField（10）；

81 final JTextField addressinput=new JTextField（10）；

82 final JTextField salaryinput=new JTextField（10）；

83 final JTextField majorinput=new JTextField（10）；

84 final JTextField dutyinput=new JTextField（10）；

85 JLabel title=new JLabel（"教师被添加的基本信息"）；

86 JButton additionbutton=new JButton（"添加"）；

87 GridBagConstraints constraints=new GridBagConstraints（）；

88 constraints.fill=GridBagConstraints.NONE；

89 constraints.weightx=4；

90 constraints.weighty=7；

91 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

92 add（name, constraints，0，1，1，1）；

93 add（code, constraints，0，2，1，1）；

94 add（sexy, constraints，0，3，1，1）；

95 add（age, constraints，0，4，1，1）；

96 add（nameinput, constraints，1，1，1，1）；

97 add（codeinput, constraints，1，2，1，1）；

98 add（sexyinput, constraints，1，3，1，1）；

99 add（ageinput, constraints，1，4，1，1）；

100 add（birthday, constraints，2，1，1，1）；

101 add（address, constraints，2，2，1，1）；

102 add（salary, constraints，2，3，1，1）；

103 add（major, constraints，2，4，1，1）；

104 add（duty, constraints，0，5，1，1）；

105 add（birthdayinput, constraints，3，1，1，1）；

106 add（addressinput, constraints，3，2，1，1）；

107 add（salaryinput, constraints，3，3，1，1）；

108 add（dutyinput, constraints，1，5，1，1）；

109 add（majorinput, constraints，3，4，1，1）；

110 add（additionbutton, constraints，0，6，4，1）；

111 ///将每个文本域中的信息赋值给变量，再将变量以值的形式存储到数据库的表中

112 ///最后，再将这些值存储到教师对象中，并且这个教师对象是一个静态的对象

113 additionbutton.addActionListener（new ActionListener（）

114 {

115 public void actionPerformed（ActionEvent Event）

116 {

117 try

118 {

119 String nametext=nameinput.getText（）；

120 codetext=codeinput.getText（）；

121 agetext=ageinput.getText（）；

122 sexytext=sexyinput.getText（）；

123 birthdaytext=birthdayinput.getText（）；

124 addresstext=addressinput.getText（）；

125 salarytext=salaryinput.getText（）；

126 dutytext=dutyinput.getText（）；

127 majortext=majorinput.getText（）；

128 storesystem2 store=new storesystem2（）；

129 Connection con=store.getConnection（）；

130 Statement st=con.createStatement（）；

131 String sql="insert into teacherinfo

132 values（'"+nametext+"'，'"+codetext+"'，'"+sexytext+"'，'"+agetext+"'，'"+addresstext+"'，'"

133+birthdaytext+"'，'"+salarytext+"'，'"+majortext+"'，'"+dutytext+"'）"；

134 st.executeUpdate（sql）；

135 ss=new teacher（nametext, codetext）；

136 ss.setage（agetext）；

137 ss.setsexy（sexytext）；

138 ss.setaddress（addresstext）；

139 ss.setbirthday（birthdaytext）；

140 ss.setsalary（salarytext）；

141 ss.setmajor（majortext）；

142 ss.setduty（dutytext）；

143 Vector vec=new Vector（）；

144 vec.add（ss）；

145 }

146 catch（Exception e）{}

147 teacheraddframe.dispose（）；

148 }

149 }）；

150 }

151 }

【代码说明】第28～41行定义一些变量和常量，为下面的操作做准备。第52～110行创建教师添加窗口，并添加窗口中的标签、按钮和文本框等。第113～149行实现添加教师到数据库的操作。

【运行效果】程序段的运行结果如图26.21所示。

图　26.21　教师添加信息系统界面

26.8　领导信息系统界面的设计

领导信息系统界面的设计也是整个软件系统的界面的主要部分之一。当进入这个界面时，界面自动会装载数据库，用户也就可以直接操作数据库。下面将详细地讲述和分析代码段。

26.8.1　领导类的设计

在设计整个程序的过程中，先要设计一个领导类，然后将这个领导类储存到一个数据结构中，最后将这个数据结构中的数据同数据库连接。这样界面上显示出来的数据就是数据库中的数据。

【实例26.15】下面是领导类的设计代码。

01 ///设置了领导的姓名、　性别、工号、出　生年月、　年龄、薪水、专业等变量

02 ///通过构造器设置领导姓名、工号的值

03 ///通过设置器设置其他变量的值

04 ///通过访问器获得变量的值

05 class leader{

06 private String name；

07 private String code；

08 private String sexy；

09 private String birthday；

10 private String address；

11 private String age；

12 private String salary；

13 private String major；

14 private String duty；

15 public leader（String name, String code）

16 {

17 this.name=name；

18 this.code=code；

19 }

20 public String getname（）

21 {

22 return name；

23 }

24 public String getcode（）

25 {

26 return code；

27 }

28 public void setsexy（String sexy）

29 {

30 this.sexy=sexy；

31 }

32 public void setbirthday（String birthday）

33 {

34 this.birthday=birthday；

35 }

36 public void setage（String age）

37 {

38 this.age=age；

39 }

40 public void setaddress（String address）

41 {

42 this.address=address；

43 }

44 public void setsalary（String salary）

45 {

46 this.salary=salary；

47 }

48 public void setmajor（String major）

49 {

50 this.major=major；

51 }

52 public void setduty（String duty）

53 {

54 this.duty=duty；

55 }

56 public String getsexy（）

57 {

58 return sexy；

59 }

60 public String getbirthday（）

61 {

62 return birthday；

63 }

64 public String getage（）

65 {

66 return age；

67 }

68 public String getaddress（）

69 {

70 return address；

71 }

72 public String getsalary（）

73 {

74 return salary；

75 }

76 public String getmajor（）

77 {

78 return major；

79 }

80 public String getduty（）

81 {

82 return duty；

83 }

84 }

【代码说明】以上代码将整个数据库中事先设计的表中的所有字段全部显示出来，最后按照字符串形式输出。

26.8.2　存储类的设计

在这里，仍然选择Vector数据结构对领导进行存储。领导类的存储类设计同教师类的一模一样，在这里不再提供。

程序从数据库中提取数据，然后按照每个字段的不同，分别赋值给领导类中的每个字段，最后，将领导类的对象添加到Vector数据结构中。

26.8.3　领导信息界面的设计

领导信息界面同样包括了显示信息的控件、添加对象的控件、删除对象的控件。在这个领导信息界面中，将这个领导信息界面抽象成一个类，然后，能让所有的不同系的领导信息系统（包括学校领导信息系统）都能调用它。

【实例26.16】领导信息界面的实现代码如下所示。

01 import java.awt.BorderLayout；

02 import java.awt.Component；

03 import java.awt.Dimension；

04 import java.awt.GridBagConstraints；

05 import java.awt.GridBagLayout；

06 import java.awt.Toolkit；

07 import java.awt.event.ActionEvent；

08 import java.awt.event.ActionListener；

09 import java.awt.event.ItemEvent；

10 import java.awt.event.ItemListener；

11 import java.sql.Connection；

12 import java.sql.Statement；

13 import java.util.Vector；

14 import javax.swing.JPanel；

15 import javax.swing.JButton；

16 import javax.swing.JComboBox；

17 import javax.swing.JFrame；

18 import javax.swing.JLabel；

19 import javax.swing.JTextField；

20 ///这是领导类信息系统的框架类

21 ///leaderinfoframe是顶层窗口框架

22 ///通过九个文本域控件来分别可以显示不同专业、不同职务的领导信息

23 ///三个按钮控件分别产生三个不同的动作事件，分别是添加、删除和更新。addition是

24 ///添加，delete是删除，update是更新

25 ///在布局方面有两个布局管理器，一个是lay即网格组布局，一个是jp即GridLayout布局

26 ///通过不同的SQL语句进入不同的管理系统。

27 ///先将数据库中的数据导入到vec中。然后再将vec中的数据导入领导类中。最后再将数据

28 ///输出

29 class leaderinfo extends JPanel

30 {

31 private static final long serialVersionUID=1L；

32 static final int WIDTH=700；

33 static final int HEIGHT=400；

34 final JComboBox nameinput；

35 final JTextField codeinput；

36 final JTextField sexyinput；

37 final JTextField birthdayinput；

38 final JTextField ageinput；

39 final JTextField addressinput；

40 final JTextField salaryinput；

41 final JTextField majorinput；

42 final JTextField dutyinput；

43 JFrame teacherinfoframe；

44 storesystem2 store=new storesystem2（）；

45 Connection con=store.getConnection（）；

46 addframe2 af=new addframe2（）；

47 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

48 {

49 constraints.gridx=x；

50 constraints.gridy=y；

51 constraints.gridwidth=w；

52 constraints.gridheight=h；

53 add（c, constraints）；

54 }

55 public leaderinfo（String str, String sql）

56 {

57 teacherinfoframe=new JFrame（）；

58 teacherinfoframe.setTitle（str）；

59 teacherinfoframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

60 teacherinfoframe.setSize（WIDTH, HEIGHT）；

61 Toolkit kit=Toolkit.getDefaultToolkit（）；

62 Dimension screenSize=kit.getScreenSize（）；

63 int width=screenSize.width；

64 int height=screenSize.height；

65 int x=（width-WIDTH）/2；

66 int y=（height-HEIGHT）/2；

67 teacherinfoframe.setLocation（x, y）；

68 teacherinfoframe.setVisible（true）；

69 //teacherinfoframe.setResizable（false）；

70 teacherinfoframe.add（this, BorderLayout.CENTER）；

71 GridBagLayout lay=new GridBagLayout（）；

72 setLayout（lay）；

73 JLabel name=new JLabel（"姓名"）；

74 JLabel code=new JLabel（"工号"）；

75 JLabel sexy=new JLabel（"　性别"）；

76 JLabel age=new JLabel（"　年龄"）；

77 JLabel birthday=new JLabel（"出　生年月"）；

78 JLabel address=new JLabel（"　家庭地址"）；

79 JLabel salary=new JLabel（"薪水"）；

80 JLabel major=new JLabel（"专业"）；

81 JLabel duty=new JLabel（"职务"）；

82 JLabel title=new JLabel（str）；

83 nameinput=new JComboBox（）；

84 codeinput=new JTextField（10）；

85 sexyinput=new JTextField（10）；

86 ageinput=new JTextField（10）；

87 birthdayinput=new JTextField（10）；

88 addressinput=new JTextField（10）；

89 salaryinput=new JTextField（10）；

90 majorinput=new JTextField（10）；

91 dutyinput=new JTextField（10）；

92 ///将存储在Vector中的数据提取出来重新赋值给领导对象。再利用领导对象中的getname方法来

93 ///提取出领导姓名来，最后使用组合列表框的添加功能将这些名字添加到列表中

94 Vector vec=store.getteacher（con, sql）；

95 for（int i=0；i＜vec.size（）；i++）

96 {

97 teacher one=（teacher）vec.get（i）；

98 String nameselect=one.getname（）；

99 nameinput.addItem（nameselect）；

100 }

101 String namestring=（String）nameinput.getSelectedItem（）；

102 teacher p=store.getobject（con, namestring）；

103 String inputcode=p.getcode（）；

104 String inputsexy=p.getsexy（）；

105 String inputage=p.getage（）；

106 String inputbirthday=p.getbirthday（）；

107 String inputaddress=p.getaddress（）；

108 String inputsalary=p.getsalary（）；

109 String inputmajor=p.getmajor（）；

110 String inputduty=p.getduty（）；

111 codeinput.setText（inputcode）；

112 sexyinput.setText（inputsexy）；

113 ageinput.setText（inputage）；

114 birthdayinput.setText（inputbirthday）；

115 addressinput.setText（inputaddress）；

116 salaryinput.setText（inputsalary）；

117 majorinput.setText（inputmajor）；

118 dutyinput.setText（inputduty）；

119 JButton addition=new JButton（"添加"）；

120 JButton delete=new JButton（"删除"）；

121 JButton update=new JButton（"更新"）；

122 JButton bereturn=new JButton（"返回"）；

123 GridBagConstraints constraints=new GridBagConstraints（）；

124 constraints.fill=GridBagConstraints.NONE；

125 constraints.weightx=4；

126 constraints.weighty=7；

127 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

128 add（name, constraints，0，1，1，1）；

129 add（code, constraints，0，2，1，1）；

130 add（sexy, constraints，0，3，1，1）；

131 add（age, constraints，0，4，1，1）；

132 add（nameinput, constraints，1，1，1，1）；

133 add（codeinput, constraints，1，2，1，1）；

134 add（sexyinput, constraints，1，3，1，1）；

135 add（ageinput, constraints，1，4，1，1）；

136 add（birthday, constraints，2，1，1，1）；

137 add（address, constraints，2，2，1，1）；

138 add（salary, constraints，2，3，1，1）；

139 add（major, constraints，2，4，1，1）；

140 add（duty, constraints，0，5，1，1）；

141 add（birthdayinput, constraints，3，1，1，1）；

142 add（addressinput, constraints，3，2，1，1）；

143 add（salaryinput, constraints，3，3，1，1）；

144 add（dutyinput, constraints，1，5，1，1）；

145 add（majorinput, constraints，3，4，1，1）；

146 add（addition, constraints，0，6，1，1）；

147 add（delete, constraints，1，6，1，1）；

148 add（update, constraints，2，6，1，1）；

149 add（bereturn, constraints，3，6，1，1）；

150 ///通过单击每一个列表框中的名字，会相应地显示出此名字的所有信息

151 nameinput.addItemListener（new ItemListener（）

152 {

153 public void itemStateChanged（ItemEvent e）

154 {

155 String namestring=（String）nameinput.getSelectedItem（）；

156 teacher p=store.getobject（con, namestring）；

157 String inputcode=p.getcode（）；

158 String inputsexy=p.getsexy（）；

159 String inputage=p.getage（）；

160 String inputbirthday=p.getbirthday（）；

161 String inputaddress=p.getaddress（）；

162 String inputsalary=p.getsalary（）；

163 String inputmajor=p.getmajor（）；

164 String inputduty=p.getduty（）；

165 codeinput.setText（inputcode）；

166 sexyinput.setText（inputsexy）；

167 ageinput.setText（inputage）；

168 birthdayinput.setText（inputbirthday）；

169 addressinput.setText（inputaddress）；

170 salaryinput.setText（inputsalary）；

171 majorinput.setText（inputmajor）；

172 dutyinput.setText（inputduty）；

173 }

174 }）；

175 ///返回主菜单

176 bereturn.addActionListener（new ActionListener（）

177 {

178 public void actionPerformed（ActionEvent Event）

179 {

180 teachermanageframe manageframe=new teachermanageframe（）；

181 manageframe.setVisible（true）；

182 teacherinfoframe.dispose（）；

183 }

184 }）；

185 ///进入添加主界面

186 addition.addActionListener（new ActionListener（）

187 {

188 public void actionPerformed（ActionEvent Event）

189 {

190 addframe2 addfr=new addframe2（）；

191 addfr.setVisible（true）；

192 }

193 }）；

194 ///更新按钮，使得从数据库表中，将数据提取显示到界面中

195 update.addActionListener（new ActionListener（）

196 {

197 public void actionPerformed（ActionEvent Event）

198 {

199 String name1=af.ss.getname（）；

200 String code1=af.ss.getcode（）；

201 String age1=af.ss.getage（）；

202 String sexy1=af.ss.getsexy（）；

203 String birthday1=af.ss.getbirthday（）；

204 String address1=af.ss.getaddress（）；

205 String salary1=af.ss.getsalary（）；

206 String major1=af.ss.getmajor（）；

207 String duty1=af.ss.getduty（）；

208 nameinput.addItem（name1）；

209 nameinput.setSelectedItem（name1）；

210 codeinput.setText（code1）；

211 ageinput.setText（age1）；

212 sexyinput.setText（sexy1）；

213 addressinput.setText（address1）；

214 birthdayinput.setText（birthday1）；

215 salaryinput.setText（salary1）；

216 majorinput.setText（major1）；

217 dutyinput.setText（duty1）；

218 }

219 }）；

220 ///删除数据，且更新到数据库中

221 delete.addActionListener（new ActionListener（）

222 {

223 public void actionPerformed（ActionEvent Event）

224 {

225 String namestring=（String）nameinput.getSelectedItem（）；

226 storesystem2 store=new storesystem2（）；

227 try

228 {

229 Connection con=store.getConnection（）；

230 Statement st=con.createStatement（）；

231 String sql="delete*from teacherinfo where='"+namestring+"'"；

232 st.executeUpdate（sql）；

233 }

234 catch（Exception e）{}

235 nameinput.removeItem（namestring）；

236 }

237 }）；

238 }

239 }

【代码说明】代码第57～70行创建窗口并设置窗口的各个属性。第71～149行设置标签和按钮控件等。第151～174行侦听下拉列表框的选择并作出反馈。第195～219行从数据库表提取数据并显示到界面中。第221～237行删除数据。

【运行效果】整个领导信息系统的界面设计完毕。程序段的运行结果如图26.22所示。

图　26.22　领导信息系统界面

【实例26.17】单击“添加”按钮，会弹出一个对话框，这是一个框架界面。下面显示信息的控件组的实现代码。

01 ///主运行类

02 ///main指主框架

03 ///setDefaultCloseOperation指让框架右上角的按钮都有效

04 ///show让框架能显示出来

05 import java.awt.*；

06 import javax.swing.*；

07 import java.sql.*；

08 import java.awt.event.*；

09 import java.util.Vector；

10 ///这个是领导信息系统添加信息框架类

11 ///通过九个文本域将不同信息的领导分别输出在其中

12 ///通过一个添加按钮，将数据添加到数据库中

13 ///在这里的布局是通过网格组布局管理器来设定的

14 ///在回送到信息界面时将领导对象ss设定为静态。放置新建对象时，其值会改变

15 class addframe3 extends JPanel

16 {

17 String codetext；

18 String agetext；

19 String sexytext；

20 String birthdaytext；

21 String addresstext；

22 String salarytext；

23 String majortext；

24 String dutytext；

25 public final JTextField nameinput=new JTextField（10）；

26 private static final long serialVersionUID=1L；

27 static final int WIDTH=700；

28 static final int HEIGHT=400；

29 static teacher ss；

30 JFrame leaderraddframe；

31 public void add（Component c, GridBagConstraints constraints, int x, int y, int w, int h）

32 {

33 constraints.gridx=x；

34 constraints.gridy=y；

35 constraints.gridwidth=w；

36 constraints.gridheight=h；

37 add（c, constraints）；

38 }

39 public addframe3（）

40 {

41 leaderraddframe=new JFrame（）；

42 leaderraddframe.setTitle（"领导添加系统"）；

43 leaderraddframe.setDefaultCloseOperation（JFrame.EXIT_ON_CLOSE）；

44 leaderraddframe.setSize（WIDTH, HEIGHT）；

45 Toolkit kit=Toolkit.getDefaultToolkit（）；

46 Dimension screenSize=kit.getScreenSize（）；

47 int width=screenSize.width；

48 int height=screenSize.height；

49 int x=（width-WIDTH）/2；

50 int y=（height-HEIGHT）/2；

51 leaderraddframe.setLocation（x, y）；

52 leaderraddframe.setVisible（true）；

53 //leaderraddframe.setResizable（false）；

54 leaderraddframe.add（this, BorderLayout.CENTER）；

55 GridBagLayout lay=new GridBagLayout（）；

56 setLayout（lay）；

57 JLabel name=new JLabel（"姓名"）；

58 JLabel code=new JLabel（"工号"）；

59 JLabel sexy=new JLabel（"　性别"）；

60 JLabel age=new JLabel（"　年龄"）；

61 JLabel birthday=new JLabel（"出　生年月"）；

62 JLabel address=new JLabel（"　家庭地址"）；

63 JLabel salary=new JLabel（"薪水"）；

64 JLabel major=new JLabel（"专业"）；

65 JLabel duty=new JLabel（"职务"）；

66 final JTextField codeinput=new JTextField（10）；

67 final JTextField sexyinput=new JTextField（10）；

68 final JTextField ageinput=new JTextField（10）；

69 final JTextField birthdayinput=new JTextField（10）；

70 final JTextField addressinput=new JTextField（10）；

71 final JTextField salaryinput=new JTextField（10）；

72 final JTextField majorinput=new JTextField（10）；

73 final JTextField dutyinput=new JTextField（10）；

74 JLabel title=new JLabel（"领导被添加的基本信息"）；

75 JButton additionbutton=new JButton（"添加"）；

76 GridBagConstraints constraints=new GridBagConstraints（）；

77 constraints.fill=GridBagConstraints.NONE；

78 constraints.weightx=4；

79 constraints.weighty=7；

80 add（title, constraints，0，0，4，1）；//使用网格组布局添加控件

81 add（name, constraints，0，1，1，1）；

82 add（code, constraints，0，2，1，1）；

83 add（sexy, constraints，0，3，1，1）；

84 add（age, constraints，0，4，1，1）；

85 add（nameinput, constraints，1，1，1，1）；

86 add（codeinput, constraints，1，2，1，1）；

87 add（sexyinput, constraints，1，3，1，1）；

88 add（ageinput, constraints，1，4，1，1）；

89 add（birthday, constraints，2，1，1，1）；

90 add（address, constraints，2，2，1，1）；

91 add（salary, constraints，2，3，1，1）；

92 add（major, constraints，2，4，1，1）；

93 add（duty, constraints，0，5，1，1）；

94 add（birthdayinput, constraints，3，1，1，1）；

95 add（addressinput, constraints，3，2，1，1）；

96 add（salaryinput, constraints，3，3，1，1）；

97 add（dutyinput, constraints，1，5，1，1）；

98 add（majorinput, constraints，3，4，1，1）；

99 add（additionbutton, constraints，0，6，4，1）；

100 ///将每个文本域中的信息赋值给变量，再将变量以值的形式存储到数据库的表中

101 ///最后，再将这些值存储到领导对象中，并且这个领导对象是一个静态的对象

102 additionbutton.addActionListener（new ActionListener（）

103 {

104 public void actionPerformed（ActionEvent Event）

105 {

106 try

107 {

108 String nametext=nameinput.getText（）；

109 codetext=codeinput.getText（）；

110 agetext=ageinput.getText（）；

111 sexytext=sexyinput.getText（）；

112 birthdaytext=birthdayinput.getText（）；

113 addresstext=addressinput.getText（）；

114 salarytext=salaryinput.getText（）；

115 dutytext=dutyinput.getText（）；

116 majortext=majorinput.getText（）；

117 storesystem2 store=new storesystem2（）；

118 Connection con=store.getConnection（）；

119 Statement st=con.createStatement（）；

120 String sql="insert into teacherinfo

121 values（'"+nametext+"'，'"+codetext+"'，'"+sexytext+"'，'"+agetext+"'，'"+addresstext+"'，'"

122+birthdaytext+"'，'"+salarytext+"'，'"+majortext+"'，'"+dutytext+"'）"；

123 st.executeUpdate（sql）；

124 ss=new teacher（nametext, codetext）；

125 ss.setage（agetext）；

126 ss.setsexy（sexytext）；

127 ss.setaddress（addresstext）；

128 ss.setbirthday（birthdaytext）；

129 ss.setsalary（salarytext）；

130 ss.setmajor（majortext）；

131 ss.setduty（dutytext）；

132 Vector vec=new Vector（）；

133 vec.add（ss）；

134 }

135 catch（Exception e）{}

136 leaderraddframe.dispose（）；

137 }

138 }）；

139 }

140 }

【代码说明】第17～30行定义一些变量和常量，为下面的操作做准备。第41～99行创建领导添加窗口，并添加窗口中的标签、按钮和文本框等。第102～138行实现添加领导到数据库的操作。

【运行效果】程序段的运行结果如图26.23所示。

图　26.23　领导添加信息系统界面

至止，程序全部设计完毕。其实，这个实例程序还有点粗糙，希望读者能够在学习的时候，再添加一些功能，从而检验自己对本书的掌握情况。

26.9　小结

每个应用程序都采用模块方式。学生信息系统中有7个信息界面，但在这里只定义了一个类，通过参数来达到所有的界面共用一个类，这也就是面向对象编程的一个特色。本章的开发流程完全符合实际应用程序开发的流程，使用的技巧也是应用开发中常用的技巧，希望读者能动手试验。

第27章　电话单辅助分析工具

本章介绍一个基于Java语言的Application软件，该软件目的是为安全保卫部门利用手机话单分析案情提供辅助分析工具。在该软件中读者会充分体会到如何创建Swing中各种控件，如建立菜单、建立工具栏、创建JTable、打开和保存文件等。在该软件中也提供了一个高级功能对表格中的数据按照类型排序。

本章重点：

❑掌握如何进行系统设计。

❑如何为各个功能设计流程图。

❑掌握Java与Excel表格的交互。

❑学习软件如何打包。

❑掌握数据库配置的技巧。

27.1　系统设计

在安全部门的办案过程中，需要分析大量的嫌疑人的手机话单，如搜索在某一个事件段内通话最多的对方号码，并且需要知道该号码的归属地区；两个嫌疑人在某个时间段内是否与同一个对方号码通话或者是在某个通话话单中无论是被叫还是主叫与嫌疑人通话频率最高的对方号码等。这些都是在实际工作中，相关安全部门经常使用的话单处理方式。但是对于大量的话单，如何实现上述的对方号码的搜索和定位，显然使用人工的方式是不现实的。因此依据上述的需要设计并实现该话单分析软件。

27.2　系统目标

该软件系统的目标如下：

用户方便地操作话单文件，可以打开指定的话单文件，该文件类型为.xls文件（即Excel表格文件），另存操作完成的文件、打印文件。要求话单维护功能，因为在一个界面上打开了多个文件，需要根据需要标示该文件的归属信息，需要文件名称的维护。由于话单中的号码有很多前缀类型如96531、105等字冠，可以根据用户的需要清除号码的前缀。在话单中如果直到对方号码的使用者姓名，可以维护该信息，使得在读入的表格中显示对方号码的使用者，这样可以清楚地知道双方通话者的名字。对方号码归属地的信息也需要动态维护，因为有些号段可能是当前数据库中所没有的，需要用户根据自己已知的号码归属地信息来更新数据库。打开的话单文件应该可以更改文字大小和选择内容的背景颜色以标识不同的用户需求。

文件操作是该软件的核心功能，要求对多个话单（可以是一个）进行各种统计，按照通话频率、通话时间段和通话时长分别统计，如果是一张话单则按照三种统计方式分别实现统计功能，如果是多张话单，则按照三种统计方式统计的信息按照话单的先后顺序排列，如话单一的通话频率统计，接着是话单二的通话频率统计……多张话单统计的结果显示在同一个用户界面中。

根据用户提出的系统要求，笔者在满足用户需求的同时按照功能进行了分解，为了更清除地说明这些系统功能（用户可以实现的操作），给出用例图，分为文件操作用例图、话单操作用例图和话单维护用例图。下面依次介绍。

说明

因为本例是笔者实际工作中的一个项目，鉴于公司的保密协议，本例没有给出全部代码，只是给出整个流程中最关键的技术点和难点，希望读者能在这个基础上，开发出属于自己的辅助分析工具。

27.2.1　文件操作用例

文件操作的用例图如图27.1所示。操作员为使用该软件的用户，用户可以对文件实现的四种操作分别是打开文件、保存文件、打印文件和退出程序操作。

图　27.1　文件操作的用例图

27.2.2　话单操作用例

话单操作的用例图如图27.2所示。操作员为使用该软件的用户，用户可以对话单实现五种操作，分别是话单中数据按照属性排序、按照通话频率统计、按照通话时长统计、按照通话时段统计和对于一个号码的关联分析。

图　27.2　话单操作的用例图

27.2.3　话单维护用例

话单维护的用例图如图27.3所示。操作员为使用该软件的用户，用户可以对话单实现五种维护操作，分别是对方姓名维护、号码前缀维护、号码归属地维护、话单重命名维护、话单字体和背景色维护。

图　27.3　话单维护的用例图

用例图使软件内部对象或软件使用者对软件的操作更加直观，用例图也展示了系统的部分功能，对于设计软件系统提供直观的参考，一个用例图说明一种操作类型，相似的操作放置在同一个用例图中，这样可以参考该用例图进行相应的模块划分，每个模块根据用例图提供不同的子功能。

27.3　系统功能

该软件系统按照需求设计为4个主要功能，即文件操作功能、话单维护功能、话单操作功能和系统维护功能。每个功能对应该软件的一类操作，如文件操作功能显然的是对应各种文件的操作，如打开文件、保存文件和关闭文件等。

27.3.1　文件操作功能

文件操作功能实现对话单文件的操作，首先是打开文件，此时应该显示打开文件的对话框，并且文件类型定位在.xls文件，一旦用户选择了指定的话单文件则读入程序，供用户进行需要的操作。其次是保存文件，用户根据自己的需要随时可以保存自己选择的当前话单。保存话单时，根据需要自由地为文件起名。在必要时可以打印当前文件。

27.3.2　话单维护功能

根据用户的系统需求把话单维护分为6个子功能，分别是话单重命名维护、对方姓名维护、号码前缀维护、对方号码归属地维护、话单字体和背景色维护。

27.3.3　话单操作功能

话单操作主要涉及3种类型的统计，分别是按照通话频率统计、按照通话时长统计和按照通话时段统计。这些统计功能可以同时针对多个表格的操作。

27.3.4　系统维护功能

在登录软件系统时需要用户名和密码，在退出软件系统时要求自动保存当前打开的话单。对使用该软件的用户进行注册管理，用户名和密码保存在数据库中，并且通过用户名可以恢复该用户上次退出时打开的文件。出于安全考虑，一旦用户关闭软件时，则自动保存当前打开的文件。

27.4　业务流程

业务流程说明了该软件基本操作的步骤，通过该流程程序员可以清楚地知道操作步骤，对于代码的编写提供很好的帮助。

27.4.1　文件操作流程

文件操作流程（选择打开文件、输入主叫姓名或号码、话单操作/话单维护、保存当前文件、退出程序、自动保存当前打开的文件、退出程序）。操作流程如图27.4文件所示。

图　27.4　文件操作流程

27.4.2　话单维护流程

话单维护流程，该业务主要是对话单中的某一属性进行维护，如与对方号码对应的姓名、对方号码的归属地等。话单维护的操作步骤简单，以号码归属地维护为例（单击要维护的对方号码、单击号码维护、输入对方号码归属地、确定、修改数据库、刷新当前话单。）说明维护的流程。号码归属地维护流程如图27.5所示。

图　27.5　号码归属地维护流程

27.4.3　话单操作流程

话单操作流程针对辅助分析的功能要求实现各种统计功能，这是该软件的核心功能，该功能的实现与否直接影响该软件的质量（选择一种统计功能、选择要统计的话单（可多选）、选择统计参数，显示统计结果）。话单操作中的三种统计功能的流程相似，这里不再以具体的某种统计为例说明，而只说明选择某种统计。话单操作流程图如图27.6所示。

图　27.6　话单操作流程

27.5　软件模块设计

该软件由四个模块组成，分别是主界面模块、文件操作模块、话单操作模块和话单维护模块。这些模块的划分主要根据类似的功能分类。下面分别介绍这四个模块的设计与实现。涉及复杂的算法时会首先给出算法的流程图以利于读者理解。

27.5.1　主界面模块

主界面模块主要包括菜单栏、工具栏和数据显示区三个部分，而文件数据显示区是容器的一个部分，在读入文件时，自动在容器的该位置以表格的形式显示数据，所以这里就只介绍菜单栏设置和工具栏设置。

1.菜单栏设置

菜单栏根据软件系统的功能设置三个菜单，分别为“文件”菜单、“通话统计”菜单和“工具”菜单，其中文件包括诸如打开文件、存储文件、退出系统等功能，“通话统计”菜单包括按照通话频率统计、按照通话时长统计和按照通话时段统计三个子菜单，“工具”菜单包括话单重命名、姓名维护、前缀码维护、归属地维护和关联分析。

【实例27.1】下面展示菜单栏设置中文件菜单的设置方式。

01 //创建JMenuBar对象，在该菜单栏上放置菜单对象

02 JMenuBar menubar=new JMenuBar（）；

03 //创建JMenu对象，在菜单上添加子菜单

04 JMenu filemenu=new JMenu（"文件"）；

05 filemenu.setFont（new java.awt.Font（"Dialog"，0，14））；

06 //创建子菜单，子菜单名为"打开"，设置图示对象为openicon，并且设置了字体

07 JMenuItem openitem=new JMenuItem（"打开"，openicon）；

08 openitem.setFont（new java.awt.Font（"Dialog"，0，14））；

09 //创建子菜单，子菜单名为"保存"，设置图示对象为openicon，并且设置了字体

10 JMenuItem saveitem=new JMenuItem（"保存"，saveicon）；

11 saveitem.setFont（new java.awt.Font（"Dialog"，0，14））；

12 //创建子菜单，子菜单名为"打印"，设置图示对象为openicon，并且设置了字体

13 JMenuItem printitem=new JMenuItem（"打印"，new ImageIcon（"images/printer.gif"））；

14 printitem.setFont（new java.awt.Font（"Dialog"，0，14））；

15 //创建子菜单，子菜单名为"退出"，设置图示对象为openicon，并且设置了字体

16 JMenuItem exititem=new JMenuItem（"退出"，exiticon）；

17 exititem.setFont（new java.awt.Font（"Dialog"，0，14））；

18 //在文件菜单上增加子菜单

19 filemenu.add（openitem）；

20 filemenu.add（saveitem）；

21 filemenu.add（printitem）；

22 //在文件菜单上增加一条分隔线

23 filemenu.addSeparator（）；

24 filemenu.add（exititem）；

25 //将文件菜单添加到菜单栏上

26 menubar.add（filemenu）；

27 //设置uppanel的布局方式为网格管理器，网格分布为两行一列

28 uppanel.setLayout（new GridLayout（2，1））；

29 //默认将toolbar添加到uppanel的第1行

30 uppanel.add（menubar）；

【代码说明】其他两个菜单包括“通话统计”菜单和“工具”菜单。其中前者有三个子菜单分别是“通话频率统计”、“通话时长统计”和“通话时段统计”。而后者包括“重命名维护”、“姓名维护”、“前缀码维护”、“归属地维护”和“关联分析”。这些菜单和子菜单的设置布局方式和上述“文件”菜单和子菜单的布局方式相同。布局结果如图27.7所示。

图　27.7　主界面中的菜单布局

2.工具栏设置

工具栏提供用户经常使用的功能，以方便用户使用，要求在工具菜单上有工具提示，并且在用户鼠标滑过该工具栏时，在状态栏中显示当前工具的作用和简洁的操作方法。按照需要在工具上设置了11种常用工具，分别是“打开”、“保存”、“打印”、“重命名”、“姓名维护”、“前缀码维护”、“归属地维护”、“关联分析”、“颜色”、“字体”和“退出”。

【实例27.2】工具栏的布局设置如下所示。

01 //创建JMenuBar对象，在该菜单栏上放置菜单对象

02 //创建打开文件快捷工具

03 ImageIcon openicon=new ImageIcon（"images/open.gif"）；

04 JButton openbutton=new JButton（"打开"，openicon）；

05 openbutton.setBorder（BorderFactory.createEmptyBorder（））；

06 openbutton.setBorderPainted（false）；

07 openbutton.setFont（new java.awt.Font（"Dialog"，0，14））；

08 openbutton.setToolTipText（"打开话单的excel文件"）；

09 //创建保存文件快捷工具

10 ImageIcon saveicon=new ImageIcon（"images/save.gif"）；

11 JButton savebutton=new JButton（"保存"，saveicon）；

12 savebutton.setBorder（BorderFactory.createEmptyBorder（））；

13 savebutton.setBorderPainted（false）；

14 savebutton.setFont（new java.awt.Font（"Dialog"，0，14））；

15 savebutton.setToolTipText（"保存为excel文件"）；

16 //创建打印当前文件快捷工具

17 ImageIcon printericon=new ImageIcon（"images/printer.gif"）；

18 JButton printerbutton=new JButton（"打印"，printericon）；

19 printerbutton.setBorder（BorderFactory.createEmptyBorder（））；

20 printerbutton.setBorderPainted（false）；

21 printerbutton.setFont（new java.awt.Font（"Dialog"，0，14））；

22 printerbutton.setToolTipText（"打印当前表"）；

23 //创建关联分析快捷工具

24 ImageIcon guanlianicon=new ImageIcon（"images/save.png"）；

25 JButton guanlianbutton=new JButton（"关联"，guanlianicon）；

26 guanlianbutton.setBorder（BorderFactory.createEmptyBorder（））；

27 guanlianbutton.setBorderPainted（false）；

28 guanlianbutton.setFont（new java.awt.Font（"Dialog"，0，14））；

29 guanlianbutton.setToolTipText（"敏感数据关联"）；

30……//这里为了节省篇幅，省略了其他工具菜单的设置方式，读者可以参考上面的代码设置

31 //设置颜色选项工具

32 JComboBox cbox=new JComboBox（）；

33 cbox.setBackground（Color.WHITE）；

34 cbox.setFont（new java.awt.Font（"Dialog"，0，14））；

35 cbox.addItem（new ImageIcon（"images/red.gif"））；

36 cbox.addItem（new ImageIcon（"images/green.gif"））；

37 cbox.addItem（new ImageIcon（"images/yellow.gif"））；

38 cbox.addItem（new ImageIcon（"images/lianglanse.gif"））；

39 cbox.addItem（new ImageIcon（"images/huilanse.gif"））；

40 cbox.createToolTip（）.setTipText（"设置所选行的背景色"）；

41 //设置字体大小选项工具

42 JComboBox zitibox=new JComboBox（）；

43 zitibox.setBackground（Color.white）；

44 zitibox.setFont（new java.awt.Font（"Dialog"，0，14））；

45 zitibox.repaint（1，1，1，1）；

46 zitibox.addItem（"16"）；

47 zitibox.addItem（"18"）；

48 zitibox.addItem（"20"）；

49 zitibox.addItem（"22"）；

50 zitibox.addItem（"24"）；

51 //向工具栏增加工具按钮

52 JToolBar toolbar=new JToolBar（）；

53 toolbar.add（openbutton）；

54 toolbar.add（savebutton）；

55 toolbar.add（printerbutton）；

56 toolbar.add（renamebutton）；

57 toolbar.add（nameweihubutton）；

58 toolbar.add（guishubutton）；

59 toolbar.add（weihubutton）；

60 toolbar.add（guanlianbutton）；

61 //向工具栏增加颜色选项工具按钮

62 toolbar.add（cbox）；

63 JLabel clabel=new JLabel（"颜色"）；

64 clabel.setFont（new java.awt.Font（"Dialog"，0，14））；

65 toolbar.add（clabel）；

66 //向工具栏增加字体选项工具按钮

67 toolbar.add（zitibox）；

68 JLabel zitilabel=new JLabel（"字体"）；

69 zitilabel.setFont（new java.awt.Font（"Dialog"，0，14））；

70 toolbar.add（zitilabel）；

71 toolbar.add（exitbutton）；

72 //设置uppanel的布局方式为网格管理器，网格分布为两行一列

73 uppanel.setLayout（new GridLayout（2，1））；

74 //默认将toolbar添加到uppanel的第2行

75 uppanel.add（toolbar）；

【代码说明】工具栏和菜单栏放置在一个面板上，该面板设置为网格布局，分为两行一列，这样先添加的组件菜单栏就放置在第一行，而后添加的组件工具栏就放置在第2行。工具栏的布局结果如图27.8所示。

图　27.8　主界面中的工具栏布局

主窗体中还有一个部分显示提示信息的文本框，如鼠标滑过工具栏上的快捷菜单时，该工具的作用和操作方式会显示在文本框中，该文本框设计放置在主界面的最下面。

27.5.2　文件操作模块

文件操作模块实现三个功能，即打开文件、保存当前文件和打印文件。这里不涉及复杂的算法，但是对于Swing控件的使用会对读者很有帮助。对于文件模块的三个操作对应的菜单和工具栏快捷工具在主界面设计中已经完成，这里只需要设置监听器，在监听器中编写相应的处理代码。

1.打开文件操作

【实例27.3】打开文件的代码如下所示。

01 //为openbutton按钮注册监听器

02 openbutton.addActionListener（new ActionListener（）{

03 public void actionPerformed（ActionEvent event）{

04 JFileChooser c=new JFileChooser（）；

05 //创建文件过滤器，打开文件对话框时在文件类型中只显示".xls"文件类型

06 c.addChoosableFileFilter（new ExcelFileFilter（））；

07 c.showOpenDialog（null）；

08 //GetOwnerNumber类提供一个对话框，提示用户输入当前话单的用户名或手机号

09 GetOwnerNumber geton=new GetOwnerNumber（getthis（））；

10 try{

11 int ivalue=0；

12 if（ivalue==JFileChooser.APPROVE_OPTION）{

13 //将GetOwnerNumber类对象放置在主界面的中心位置

14 geton.setLocation（（（int）getthis（）.getLocation（）.getX（）+getthis（）.getWidth（）/2）-

15 geton.getWidth（）/2，（（int）getthis（）.getLocation（）.getY（）+getthis（）.getHeight（）

16 /2）-geton.getHeight（）/2）；

17 geton.show（）；

18 String filename=null；

19 String filedirectory=null；

20 file_and_directoryname=null；

21 //获得用户选择的文件名

22 filename=c.getSelectedFile（）.getName（）；

23 //获得用户选择的文件目录

24 filedirectory=c.getCurrentDirectory（）.toString（）；

25 //把该目录和文件传给ReadAndAnalyzeDataTread

26 file_and_directoryname=filedirectory+"\\"+filename；

27 getInputISWorkbook（file_and_directoryname）；

28 }

29 }

30 catch（Exception ex）

31 {

32 JOptionPane.showMessageDialog（null，"取消该次保存"）；

33 geton.dispose（）；

34 }

35 }

36 }

37）；

38 openbutton.addMouseListener（new MouseAdapter（）{

39 public void mouseEntered（MouseEvent event）{

40 textfield.setText（"当前功能提示：打开已经保存的话单Excel文件，此时要求输入话单用户

41 的手机号码，以方便识别该用户。"）；

42 }

43 public void mouseExited（MouseEvent event）{

44 textfield.setText（"当前状态提示"）；

45 }

46 }

47）；

【代码说明】上述代码完成打开对话框的功能，在该程序中，用户单击打开文件对话框的“打开”按钮后不会立即读入话单文件，而是先打开提示用户输入用户名或用户号码的对话框，只有单击该对话框中的“确定”按钮才打开话单的Excel表格，并将其显示在用户界面上。

打开文件对话框如图27.9所示。当用户单击打开文件对话框中的“打开”按钮时，会弹出如图27.10所示的获得用户号码对话框。

图　27.9　打开文件对话框

图　27.10　用户号码输入对话框

此时用户单击“确定”按钮，程序会调用一个线程读取话单文件的内容并显示在用户主界面上。接下来会介绍如何实现读取Excel表格数据的类和方法。

【实例27.4】在打开文件代码中涉及文件过滤的功能，该功能很好地满足了用户对于过滤特定文件的需要，该文件过滤功能类的实现主要是继承javax.swing.filechooser.FileFilter类，覆写其getDescription（）方法。该类的实现如下所示。

01 class ExcelFileFilter extends javax.swing.filechooser.FileFilter{

02 public boolean accept（File file）{

03 if（file.isDirectory（）||file.getPath（）.toLowerCase（）.endsWith（".xls"））

04 return true；

05 else

06 return false；

07 }

08 public String getDescription（）{

09 return".xls"；

10 }

11 }

【代码说明】第1行说明该类继承自FileFilter类，第8～10行书写覆盖getDescription（）的代码。在打开文件代码中还涉及getInputISWorkbook（）方法，该方法的具体实现如下所示。

01 public void getInputISWorkbook（String path）{

02 wb=null；

03 try{

04 //获得文件输入流

05 InputStream is=new FileInputStream（path）；

06 //获得工作簿对象

07 this.wb=Workbook.getWorkbook（is）；

08 sheetname=null；

09 //通过工作簿对象获得Excel文件中第一个表单的名字

10 sheetname=wb.getSheet（0）.getName（）；

11 }

12 catch（Exception ex）{

13 ex.printStackTrace（）；

14 }

15 }

【代码说明】该方法通过用户选择的路径和文件名称，获得文件输入流对象，通过Workbook的静态方法getWorkbook（InputStream is）获得一个工作簿对象，通过该对象来读取当前Excel表中话单的对应信息。

【实例27.5】刚才我们讲过，一旦用户单击图27.10中的“确定”按钮，程序会启动一个新的线程通过工作簿对象wb来读取话单Excel表格中的数据信息。这里主要分析一下程序中的一个读取数据的线程，名称为ReadAndAnalyzeDataTread。该线程的主要功能体现在一个方法readandanalyzesheetdata（）上，如下所示。

01 private void readandanalyzesheetdata（）{

02 //获得当前Excel表的第一个表单对象

03 jxl.Sheet st=（window.wb）.getSheet（0）；

04 //获得当前Excel表的第一个表单的行数

05 int sheetrow=st.getRows（）；

06 //获得当前Excel表的第一个表单的列数

07 int sheetcolumn=st.getColumns（）；

08 //通过当前Excel表的第一个表单的行和列数读取表单中每个cell的数据，并根据数据类型

09 //作了相应的数据类型的转换

10 for（int i=0；i＜sheetrow；i++）{

11 rowdata1=null；

12 Cell[]datacell=st.getRow（i）；

13 rowdata1=new Object[datacell.length+1]；

14 for（int j=0；j＜datacell.length；j++）{

15 //判断CellType是否为日期类型

16 if（datacell[j].getType（）==CellType.DATE）{

17 TimeZone gmtZone=TimeZone.getTimeZone（"GMT"）；

18 SimpleDateFormat format=new SimpleDateFormat（"yyyy/MM/dd hh：mm"）；

19 format.setTimeZone（gmtZone）；

20 DateCell dateCell=（DateCell）datacell[3]；

21 String dateString=format.format（dateCell.getDate（））；

22 System.out.println（dateString）；

23 rowdata1[j+1]=dateString；

24 }

25 //判断CellType是否为数字类型

26 if（datacell[j].getType（）==CellType.NUMBER）{

27 NumberCell nc=（NumberCell）datacell[j]；

28 rowdata1[j+1]=new Double（nc.getValue（））；

29 }

30 else

31 rowdata1[j+1]=datacell[j].getContents（）；

32 }

33 rowdata1[0]=window.ownernumber；

34 //向用户主界面中增加一行数据

35 window.shoujimodel.addRow（rowdata1）；

36 //对每列数据分析数据类型，然后按响应的数据类型处理

37 }

38 }

【代码说明】上述代码读取话单Excel表格中的数据信息，在读取时，需要判断表格中的各列的数据类型。第16行的CellType.DATE判断是否是日期型，第26行的CellType.NUMBER判断是否是数字型。

2.保存文件的实现

保存文件首先需要打开一个保存文件的对话框，用户输入文件名，文件自动保存为Excel文件。这里有两个关键点，一个是打开文件对话框的实现，另一个是如何把当前的经过处理（排序、关联处理等）的话单数据写入到用户命名的Excel文件中。

【实例27.6】首先介绍保存文件对话框，程序如下所示。

01 savebutton.addActionListener（new ActionListener（）{

02 public void actionPerformed（ActionEvent event）{

03 //创建JFileChooser对象c

04 JFileChooser c=new JFileChooser（）；

05 //通过JFileChooser对象c为其增加一个文件过滤器对象，只允许保存为Excel文件

06 c.addChoosableFileFilter（new ExcelFileFilter（））；

07 //打开保存文件对话框

08 c.showSaveDialog（null）；

09 try{

10 int ivalue=0；

11 if（ivalue==JFileChooser.APPROVE_OPTION）{

12 //如果用户单击了"保存"按钮，则执行以下代码，首先是获得用户输入的文件名，而后创建线

13 //程类SaveTableData对象，并启动该线程来向用户输入的文件中写入数据

14 File f=c.getSelectedFile（）；

15 String filename=c.getName（f）；

16 savedirectoryandfilename=f.toString（）；

17 SaveTableData savethread=new SaveTableData（getthis（））；

18 savethread.run（）；

19 }

20 }

21 catch（Exception ex）{

22 OptionPane.showMessageDialog（null，"您没有选择存储路径，取消该次保存！

23 "）；

24 }

25 c.setVisible（false）；

26 }

27 }

28）；

29 savebutton.addMouseListener（new MouseAdapter（）{

30 public void mouseEntered（MouseEvent event）{

31 //注册鼠标监听器，一旦鼠标滑过保存按钮时，则在文本框textfield中显示提示信息

32 textfield.setText（"当前功能提示：保存当前的表单为Excel表，默认保存为两张表，

33 分为"固话"表和"手机"表。"）；

34 }

35 public void mouseExited（MouseEvent event）{

36 textfield.setText（"当前状态提示"）；

37 }

38 }

39）；

【代码说明】上例演示了打开文件对话框的事件监听器，通过该监听器的处理使用户可以方便地为文件命名，并保存当前的话单文件。

打开的保存文件对话框如图27.11所示。

图　27.11　保存当前文件对话框

用户一旦单击“保存”按钮，程序会启动一个写数据线程类SaveTableData向用户定义的Excel文件中写入数据。

【实例27.7】下面分析一下如何实现向Excel表中写入数据，在该类中笔者设计了一个方法processShoujiTableData（）来完成写数据的功能，如下代码所示的方法。

01 private void processShoujiTableData（）{

02 //获得用户选择存储保存后的文件的目录和文件名，创建文件对象f

03 File f=new File（window.savedirectoryandfilename+".xls"）；

04 //下面try区块中的代码实现向Excel表格中写入当前话单的数据

05 try{

06 //创建新的工作簿对象

07 WritableWorkbook workbook=Workbook.createWorkbook（f）；

08 //通过工作簿对象创建新的话单对象

09 WritableSheet sheet=workbook.createSheet（"第一页"，0）；

10 //获得当前需要保存的话单的行数

11 int row=saveshoujitable.getRowCount（）；

12 //获得当前需要保存的话单的列数

13 int column=saveshoujitable.getColumnCount（）；

14 //获得当前话单的表头信息作为列名

15 for（int j=0；j＜column；j++）{

16 String columnname=saveshoujitable.getColumnName（j）；

17 Label l=new Label（j，0，columnname）；

18 sheet.addCell（l）；

19 }

20 //依次把当前话单的数据写入新创建的话单对象sheet

21 for（int i=1；i＜row+1；i++）{

22 for（int j=0；j＜column；j++）{

23 Label l=new Label（j, i，saveshoujitable.getValueAt（i-1，j）.toString（））；

24 sheet.addCell（l）；

25 }

26 }

27 sheet.setColumnView（80，80）；

28 //将数据写入工作簿对象，即写入用户命名的Excel文件

29 workbook.write（）；

30 //关闭工作簿对象，停止向文件中的写操作

31 workbook.close（）；

32 }

33 catch（Exception ex）{

34 ex.printStackTrace（）；

35 }

36 }

【代码说明】第3行获得用户选择存储保存后的文件名。第6～9行创建话单对象，第11～13行获取表格的行数和列数。第15～26行通过两个循环获取单元格信息。

3.打印操作

【实例27.8】首先看如何打开打印文件对话框。这里主要通过一个按钮监听器实现。

01 printitem.addActionListener（new ActionListener（）{

02 public void actionPerformed（ActionEvent event）{

03 PrintService[]services=PrintServiceLookup.lookupPrintServices（null, null）；

04 PrintService svc=PrintServiceLookup.lookupDefaultPrintService（）；

05 PrintRequestAttributeSet attrset=new HashPrintRequestAttributeSet（）；

06 PrintService selection=ServiceUI.printDialog（null，100，100，services, svc, null, attrset）；

07 }

08 }

09）；

【代码说明】具体实现打印当前的文件这里不再详细介绍，读者只要参考任何一本关于讲解“打印”操作的资料都可以获得。

一旦单击“打印”按钮将弹出对话框，如图27.12所示。

27.5.3　话单操作模块

话单操作主要涉及三个统计功能，即按照通话频率统计、按照通话时长统计和按照通话时段统计。本节主要介绍实现这些功能时涉及的算法，这些算法会通过具体的方法实现，至于较负载的算法如按照通话时间长度统计会辅以算法流程图。

图　27.12　打印文件对话框

1.按照通话频率统计

该功能要求对于用户选择的话单按照对方号码出现的次数统计，统计结果显示在新的话单中，这样新统计的话单中对方号码不会重复出现，并且新增加一个属性“通话频率”，如果用户选择了多个话单，程序会按照话单的顺序依次统计。其算法流程图如图27.13所示。

在实现统计分析时笔者设计了两个方法，一个方法为calculateFrequency（String[]string, JTable table），该方法有两个参数，第一个为号码数组，第二个为用户选择的话单对应的表格对象；另一个方法是processTableData（int[]index），该方法获得用户选择的话单，对每个话单进行统计，此时需要调用calculateFrequency（String[]string, JTable table）方法。

【实例27.9】下面介绍实现calculateFrequency（String[]string, JTable table）方法。

01 private void calculateFrequency（String[]string, JTable table）{

02 for（int i=0；i＜string.length；i++）{

03 int count=0；

04 if（！string[i].equals（""））{

05 String currentstring=string[i]；

06 for（int j=i；j＜string.length；j++）{

07 if（string[j].equals（currentstring））{

08 string[j]=""；

09 count=++count；

10 }

11 }

12 Object[]newdata=new Object[5]；

13 newdata[0]=table.getValueAt（i，0）；

14 newdata[1]=table.getValueAt（i，2）；

15 newdata[2]=table.getValueAt（i，3）；

16 newdata[3]=new Integer（count）；

17 newdata[4]=table.getValueAt（i，8）；

18 tablemodel.addRow（newdata）；

19 }

20 }

21 }

图　27.13　按照通话频率统计算法流程图

【代码说明】该方法首先获得一个号码数组，找出第一个不为空的号码，在号码数组中搜索记录其出现次数，同时把数组中该号码的相应位置置空，把该号码、号码出现频率信息和与该号码相关的信息一起显示在新的话单上。继续搜索直到号码数组循环上述操作直到号码数组中没有号码为止。

【实例27.10】下面介绍实现processTableData（int[]index）方法，如下所示。

01 public void processTalbeData（int[]index）{

02 for（int i=0；i＜index.length；i++）{

03 int tabindex=index[i]；

04 JTable table=（（JTable）window.shoujitablemap.get（Integer.toString（tabindex）））；

05 int countrow=table.getRowCount（）；

06 String[]newdata=new String[countrow]；

07 for（int j=0；j＜countrow；j++）{

08 newdata[j]=table.getValueAt（j，2）.toString（）；

09 }

10 calculateFrequency（newdata, table）；

11 }

12 }

【代码说明】方法首先获得用户选择的话单列表索引，即方法的参数，对于每一个话单，调用calculateFrequency（newdata, table）方法，来计算每个话单中号码出现的频率。

【运行效果】在按照频率统计时，用户获得的话单索引即为当前选择需要统计的话单在全部话单的列表中的位置。通话频率统计对话框如图27.14所示。

2.按照通话时长统计

图　27.14　通话频率统计对话框

该功能要求对于用户选择的话单按照通话时间长短进行统计，在统计前需要用户选择一个整型参数，如果选择2则表示统计该话单中同一号码通话时间最常和次长的记录，如果存在部分通话只用一个记录则相对应地选择一个记录，如果通话记录很多但是通话时间一样，则选择同样时长的多个记录。

该算法的思想是首先对用户选择的话单列表中的一个话单按照号码进行分类，并把分完类的号码表数据放入一个新的表，再调用一个方法处理该新表，首先对用户选择的时长参数数组进行排序，按照用户的时长参数选择符合参数要求的时长记录，并把该记录所在行的记录数据放置到一个新的表模型中，显示在用户主界面上。其算法流程图如图27.15所示。

在实现时长统计分析时，笔者设计了三个方法：

（1）第一个方法是processTalbeData（int[]index），该方法有一个参数，表示用户选择的话单列表索引。

（2）第二个方法是processTimeLength（String[]string, JTable table），该方法把当前话单中所有不同的对方号码进行分类，每类放入一个新的表模型存储，并调用第三个方法完成剩余的按照时长参数统计记录的功能。

图　27.15　按照通话时长统计算法流程图

（3）第三个方法是getRequiredRecord（JTable table, int timelength），该方法对第二个方法中的每一个新模型进行处理，首先是对时长属性列数据存入一个新的数组，并对该数据进行排序，按照时长参数的选择，选择符合的记录（如通话时间最长的记录、时间长度和排好序的数组中第一个时间长度相符的记录），最后把这些记录放置在用户界面上。

【实例27.11】下面介绍如何实现getRequiredRecord（JTable table, int timelength）方法。

01 private void getRequiredRecord（JTable table, int timelength）{

02 int singletimecounter=0；

03 int countnew=0；

04 double[]singletime=new double[table.getRowCount（）]；

05 double currentdouble=0；

06 double[]timeshuzu=new double[table.getRowCount（）]；

07 for（int i=0；i＜table.getRowCount（）；i++）{

08 timeshuzu[i]=Double.parseDouble（table.getValueAt（i，5）.toString（））；

09 }

10 for（int i=0；i＜timeshuzu.length；i++）{

11 if（！（timeshuzu[i]==0））{

12 countnew=0；

13 currentdouble=timeshuzu[i]；

14 singletime[singletimecounter++]=currentdouble；

15 //此处用数组存储不同的时间长度值

16 for（int j=i；j＜timeshuzu.length；j++）{

17 if（timeshuzu[j]==currentdouble）{

18 timeshuzu[j]=0；

19 countnew=++countnew；

20 }

21 }

22 }

23 }

24 //这里获得了同一长表上，相同号码的按时间排序的数组。下面完成了不同时间长度的排序

25 for（int h=0；h＜singletime.length；h++）{

26 this.bubble（singletime）；

27 if（timelength＞singletime.length）{

28 timelength=singletime.length；

29 }

30 }

31 //singletime存储了不同的时间长度的值，需要对它排序

32 //这里获得了一个存储了同一号码表中不同的通话时间的一个数组。下面对数组排序，排序

33 //完按照timelength参数

34 //选择前几个时长，再在表中搜索相应的记录打到用户界面上

35 for（int z=0；z＜timelength；z++）{

36 double linshidouble=singletime[z]；

37 for（int q=0；q＜table.getRowCount（）；q++）{

38 double time=Double.parseDouble（table.getValueAt（q，5）.toString（））；

39 if（linshidouble==time）{

40 Object[]newdata=new Object[table.getColumnCount（）]；

41 for（int p=0；p＜table.getColumnCount（）；p++）{

42 newdata[p]=table.getValueAt（q, p）；

43 }

44 window.shoujimodel.addRow（newdata）；

45 }

46 }

47 }

48 }

【代码说明】该方法的作用是对表模型中的记录，按照时长参数进行选择，并显示在用户界面上。第25～30行是相同号码按时间的排序。

【实例27.12】下面介绍实现processTimeLength（）方法。

01 private void processTimeLength（String[]string, JTable table）{

02 Object[]newdata=new Object[table.getColumnCount（）]；

03 for（int i=0；i＜string.length；i++）{

04 CellTableModel middleModel=new CellTableModel（new String[]{"d"，"b"，"c"}）；

05 JTable middletable=new JTable（middleModel）；

06 int count=0；

07 if（！string[i].equals（""））{

08 String currentstring=string[i]；

09 for（int j=i；j＜string.length；j++）{

10 if（string[j].equals（currentstring））{

11 string[j]=""；

12 count=++count；

13 for（int l=0；l＜table.getColumnCount（）；l++）{

14 newdata[l]=table.getValueAt（j, l）；

15 }

16 //把该号码相关的所有记录放到了一张新表模型上（middletable）

17 middleModel.addRow（newdata）；

18 }

19 }

20 //调用getRequiredRecord方法，并把两个参数middletable和paixupara传给该方法

21 getRequiredRecord（middletable, paixupara）；

22 }

23 }

24 }

【代码说明】该方法对一个话单中不同的号码进行分类，每一类记录放置在一个表模型中。

【实例27.13】下面介绍实现processTalbeData（）方法。

01 public void processTalbeData（int[]index）{

02 for（int i=0；i＜index.length；i++）{

03 int tabindex=index[i]；

04 //获得一个话单表对象

05 JTable table=（（JTable）window.shoujitablemap.get（Integer.toString（

06 tabindex）））；

07 if（table.getColumnName（6）.equals（"通话频率"））{

08 return；

09 }

10 else{

11 int countrow=table.getRowCount（）；

12 String[]newdata=new String[countrow]；

13 //把话单对象的时长存入一个新的数组

14 for（int j=0；j＜countrow；j++）{

15 newdata[j]=table.getValueAt（j，2）.toString（）；

16 }

17 //调用processTimeLength方法对当前的话单表对象进行处理

18 processTimeLength（newdata, table）；

19 }

20 }

21 if（window.shoujimodel.getRowCount（）==0）{

22 JOptionPane.showMessageDialog（null，"没有匹配数据"）；

23 }

24 }

【代码说明】该方法获得用户选择的话单列表索引，并对每一个话单表对象调用processTimeLength（）方法。在按照频率统计时，用户获得的话单索引即为当前选择需要统计的话单在全部话单中的列表中的位置。

【运行效果】通话时长统计对话框如图27.16所示。

3.按照通话时段统计

该统计功能实现对一个或多个话单按照通话时间段选择记录，用户可以在时段统计对话框中选择起始和终止时间，这样就确定了一个时间段。该功能实现算法较简单，算法流程如图27.17所示。

图　27.16　通话时长统计对话框

图　27.17　按照通话时段统计算法流程图

【实例27.14】这里只介绍如何对一个话单实现按照时段统计，在程序中笔者定义了一个方法processDate（），该方法如下所示。

01 private void processDate（）{

02 int indexlength=countindex.length；

03 //按手机号码处理

04 try{

05 for（int i=0；i＜indexlength；i++）{

06 int tableindex=countindex[i]；

07 //获得要处理的话单表对象

08 JTable shoujitable=（（JTable）window.shoujitablemap.get（Integer.toString（tableindex）））；

09 if（shoujitable.getColumnName（6）.equals（"通话频率"））{

10 return；

11 }

12 int countrow=shoujitable.getRowCount（）；

13 Object[]matchdata=new Object[shoujitable.getColumnCount（）]；

14 for（int j=0；j＜countrow；j++）{

15 Date currentdate=new Date（shoujitable.getValueAt（j，4）.toString（））；

16 processCurrentDate（this.alldate, currentdate）；

17 //判断如果当前话单的时长参数在时间段内则获取符合条件的记录，存入数组matchdata

18 if（currentdate.after（startdate）＆＆currentdate.before（enddate）||

19 currentdate.equals（startdate）||currentdate.equals（enddate））{

20 for（int l=0；l＜matchdata.length；l++）{

21 matchdata[l]=shoujitable.getValueAt（j, l）；

22 }

23 //在用户主界面上增加一个新话单表显示符合时段条件的记录

24 window.shoujimodel.addRow（matchdata）；

25 //每次向表模型添加数据后，需要更新数据视图

26 window.shoujitable.revalidate（）；

27 }

28 }

29 }

30 }

31 catch（Exception ex）{

32 JOptionPane.showMessageDialog（null，"无法读取时间数据，类型不匹配"）；

33 window.tabpane.remove（window.counttab）；

34 }

35 }

【代码说明】第8行获取要处理的话单表对象，第20～22行的matchdata数组中保存的是当前话单的时长参数在时间段内的记录。

【运行效果】在按照时段统计时，用户选择需要统计的话单，在时间段参数中选择时段，单击“确定”按钮则在程序主界面上显示统计结果。按照时段统计对话框如图27.18所示。

图　27.18　按照时段统计对话框

4.按属性排序

该功能要求单击打开话单的表头标题，则当前列按照属性类型排序，若是数字信息则按照数值大小排序，第一次单击按照升序排列，第二次单击按照降序排列，第二次单击恢复当初的原始序列。在主类MainWindow中有如下代码涉及表排序功能。

shoujimodel=new CellTableModel（teststring）；

shoujistm=new SortedTableModel（shoujimodel）；

shoujitable=new JTable（shoujistm）；

类SortedTableModel是关键类，该类也是笔者编写的一个类，其构造函数接受一个表模型对象（DefaultTableModel）得到一个排序表模型对象，使用该排序表模型对象作为参数构造的JTable对象，可以实现表中单击表头标题则按照标题属性排序的功能。

【实例27.15】该类的实现代码如下所示。

01 import javax.swing.table.*；

02 import java.util.*；

03 public class SortedTableModel extends AbstractTableModel{

04 protected DefaultTableModel sourceModel；

05 protected int[]indexValues；

06 public SortedTableModel（DefaultTableModel model）{

07 super（）；

08 sourceModel=model；

09 }

……//为了节省篇幅这里省略覆写抽象表模型的各种方法

10 public Class getColumnClass（int column）{

11 return sourceModel.getColumnClass（column）；

12 }

13 public int getSourceIndex（int index）{

14 if（indexValues！=null）{

15 return indexValues[index]；

16 }

17 return-1；

18 }

19 //一旦用户单击相应header（表的头标题）就调用该方法完成行排序

20 public void sortRows（int column, boolean ascending）{

21 int columnflag=column；

22 SortedItemHolder holder；

23 TreeSet sortedList=new TreeSet（）；

24 int count=getRowCount（）；

25 for（int i=0；i＜count；i++）{

26 holder=new SortedItemHolder（sourceModel.getValueAt（i, column），i）；

27 sortedList.add（holder）；

28 }

29 indexValues=new int[count]；

30 Iterator iterator=sortedList.iterator（）；

31 int index=（ascending?0：count-1）；

32 while（iterator.hasNext（））{

33 holder=（SortedItemHolder）（iterator.next（））；

34 indexValues[index]=holder.position；

35 index+=（ascending?1：-1）；

36 }

37 refreshViews（）；

38 }

39 public void clearSort（）{

40 indexValues=null；

41 refreshViews（）；

42 }

43 public void refreshViews（）{

44 fireTableDataChanged（）；

45 }

46 //SortedItemHolder类实现了Comparable接口

47 class SortedItemHolder implements Comparable{

48 public final Object value；

49 public final int position；

50 public SortedItemHolder（Object value, int position）{

51 this.value=value；

52 this.position=position；

53 }

54 //覆盖并定义compareTo方法

55 public int compareTo（Object parm）{

56 SortedItemHolder holder=（SortedItemHolder）parm；

57 Comparable comp=（Comparable）value；

58 int result=comp.compareTo（holder.value）；

59 if（result==0）{

60 result=（position＜holder.position）?-1：1；

61 }

62 return result；

63 }

64 public int hashCode（）{

65 return position；

66 }

67 //覆盖并定义equals方法

68 public boolean equals（Object comp）{

69 if（comp instanceof SortedItemHolder）{

70 SortedItemHolder other=（SortedItemHolder）comp；

71 if（（position==other.position）＆＆（value==other.value））{

72 return true；

73 }

74 }

75 return false；

76 }

77 }

78 }

说明

如果读者需要实现和笔者程序中同样的对表格中不同属性的列排序的功能，可以直接使用该类（SortedTableModel）。

【代码说明】第20～38行的sortRows（）方法实现单击表头标题后内容的排序。第47行的SortedItemHolder类实现了Comparable接口，所以它必须实现第55～63行的compareTo（）方法。

【运行效果】图27.19给出排序的示例结果，对表格中通话“时长”属性进行降序排序，此时“时长（秒）”属性的左边出现一个带向下箭头的按钮，这样表格中每行数据按照通话时间从高到低排序，通话时间相同的按照在原始表格中出现的顺序而定。

图　27.19　按照“时长”属性降序排列表数据

27.5.4　话单维护模块

话单维护模块主要实现以下几个功能：话单重命名、对方姓名维护、前缀码维护、归属地维护和关联分析。

在介绍这些功能时，采用一种固定的模式，即首先介绍实现该功能的算法描述，简单的功能会给出涉及的控件，复杂的功能给出算法流程图和实现算法的详细代码，这样使读者可以把握各种功能是如何实现的，对将来设计开发软件提供一个参考。

1.话单重命名维护

选择当前要打开的一个话单（该话单在JTabbedPane面板上使用JTable对象实现），然后“单击”工具栏上的重命名工具则弹出如图27.20所示的对话框，当前话单的名字就显示在该对话框中，用户只需要在该对话框中输入新的表单名字，就可实现修改功能。该功能涉及一行代码：

window.tabpane.setTitleAt（window.getn（），jTextField1.getText（））；

该代码在实现重命名对话框的类中，该类会调用主窗口对象的JTabbedPane对象，在用户选择的子面板上设置名字为用户输入的字符串即通过jTextField1.getText（）获得的字符串。

图　27.20　重命名话单对话框

说明　图27.20中表单名文本框中已经显示了“贾六.xls”，该名字是从打开的话单中自动获得的，涉及该话单的程序可参考图27.24所示。下面设计的话单维护都是以图27.24所示的话单维护为例说明。

2.对方姓名维护

其功能是将用户指定的号码与号码的使用者对应起来，并且对当前所有话单中的该号码对应的姓名信息都同时更改。这里只给出一个流程，因为不设计复杂的算法，也不再给出详细的代码以节省篇幅。流程如图27.21所示。

图　27.21　姓名维护的流程图

3.前缀码维护

【实例27.16】该功能实现清除当前话单中的前缀码，如下所示。

01 private void updatetable（）{

02 //搜索所有当前的话单

03 for（int i=0；i＜window.tabpane.getTabCount（）；i++）{

04 JTable shoujitable=（（JTable）window.shoujitablemap.get（Integer.toString（i）））；

05 int countrow=shoujitable.getRowCount（）；

06 for（int j=0；j＜countrow；j++）{

07 String rowdata=shoujitable.getValueAt（j，2）.toString（）；

08 for（int l=0；l＜window.currentguhuaqianzhui.length；l++）{

09 String selectednumber=window.currentguhuaqianzhui[l]；

10 //判断号码是否以用户欲清除的前缀匹配，如果匹配则清除该前缀并修改该号码

11 if（rowdata.toString（）.startsWith（selectednumber））{

12 rowdata=rowdata.toString（）.substring（selectednumber.length（），

13 rowdata.toString（）.length（））；

14 shoujitable.setValueAt（rowdata, j，2）；

15 }

16 }

17 }

18 //更新表视图

19 shoujitable.repaint（）；

20 }

21 }

【代码说明】算法思想是对用户输入的前缀依次在当前话单的号码中搜索，一旦发现则清除该前缀。该功能涉及一个更新表方法，该方法的思想是对每一个话单中的对方号码依次匹配要删除的前缀，如果相同则删除该前缀并重新设置该号码的值，如果不是则不处理继续搜索。直到搜索完整个话单列表。

【运行效果】前缀码维护对话框如图27.22所示。

4.归属地维护

【实例27.17】该功能实现对对方号码归属地的更新或修改。该操作在笔者设计的MaintainphoneArea类中，以下示例了类中主要的部分代码。

图　27.22　前缀码维护对话框

01 public class MaintainphoneArea extends Thread{

02……

03 //在构造函数中实现加载并链接数据库

04 public MaintainphoneArea（）{

05 try{

06 //加载数据库驱动程序

07 Class.forName（"sun.jdbc.odbc.JdbcOdbcDriver"）；

08 }

09 catch（ClassNotFoundException ex）{

10 ex.printStackTrace（）；

11 }

12 //通过数据源url链接数据库

13 String url="jdbc：odbc：test"；

14 try{

15 conn=DriverManager.getConnection（url）；

16 stmt=conn.createStatement（）；

17 }

18 catch（SQLException ex）{

19 ex.printStackTrace（）；

20 }

21 }

22 //该方法实现数据库中号码对应的归属地更新，基本思路是先删除该条记录再插入一条新的

23 //记录来实现修改或更新数据记录

24 public void UpdateDB（）throws SQLException{

25 ResultSet rs=stmt.executeQuery（"select area from list where num="+"'"+number+"'"）；

26 if（rs.next（））{

27 String deletestr="DELETE FROM list where num='"+number+"'"；

28 stmt.executeUpdate（deletestr）；

29 String updatestr="INSERT INTO list（num, area）VALUES（'"+number+"'，'"+area+"'）"；

30 stmt.executeUpdate（updatestr）；

31 }

32 else{

33 String updatestr="INSERT INTO list（num, area）VALUES（'"+number+"'，'"+area+"'）"；

34 stmt.executeUpdate（updatestr）；

35 }

36 //最后读数据库操作，关闭数据库链接，释放链接占用的内存资源

37 stmt.close（）；

38 conn.close（）；

39 }

40 //启动该线程就是运行run（）方法体中的代码

41 public void run（）{

42 try{

43 this.UpdateDB（）；

44 }catch（SQLException ex）{

45 ex.printStackTrace（）；

46 }

47 }

48 }

【代码说明】该段代码的算法思想是用户一旦更新某个号码的归属地，程序首先会修改当前打开的所有话单与该号码对应的归属地信息，然后在数据库中修改，该操作涉及了数据库操作。此处详细介绍与数据库操作的实现。

5.关联分析

该功能实现在多个话单中查找对方号码同时出现的通话记录。其算法思想是用户指定一个关联号码，程序在当前所有打开的话单中搜索与该号码对应的记录，并把记录添加到新的话单表中。实现该功能不涉及复杂的算法，所以这里不详细介绍该功能的具体实现。但用户单击“关联”工具，会弹出如图27.23所示的对话框。

图　27.23　关联分析对话框

至此，读者应该了解了整个软件的设计和实现过程，在设计完成后，笔者根据系统的功能对软件实现了模块划分，这样对每个模块的功能实现了具体的定义，在实现时就容易把握，实现各个击破了。而对于模块中功能的实现关键是算法的实现，算法从宏观上说是实现某种功能的思想，该思想通过具体的代码实现，这就实现了使用某种语言编写的软件功能模块。

【运行结果】程序运行结果如图27.24所示，并打开了两个话单，话单名字分别为“王五.xls”和“贾六.xls”。

当然在编译环境中设计程序，只能供编写者自己调试或使用。软件的发行需要一些工具来打包编写的代码，使得程序可以安装在用户的主机上。27.6节将介绍如何打包发行软件，及其程序的运行测试。

图　27.24　程序运行结果

27.6　软件打包发行

Java程序打包成安装程序自然需要打包工具，InstallAnyWhere是很好的工具软件，该软件操作简单、直观且自动绑定JVM，所以无须考虑用户的计算机上有无虚拟机，该程序都可以正确执行。下面演示该软件如何制作安装程序。

（1）打开InstallAnyWhere软件，首界面如图27.25所示。此时默认选择Create New Project，必须选择项目文件名字，单击“Save As”按钮，弹出图27.26所示对话框。

（2）设置新项目所在的文件夹，在Path中选择目录，单击“New”按钮创建文件名字为080817test。然后单击“Save”按钮返回图27.25，但是此时的“Next”按钮已经高亮了。单击该“Next”按钮弹出如图27.27所示对话框。

（3）设置项目信息，此时包括4个选项：项目名称、安装程序名称、安装文件夹和应用程序简称（在安装后应用程序的图示中显示），设置完成后单击“Next”按钮，弹出如图27.28所示对话框。

（4）添加项目所需文件。这些文件不但包括笔者编写的类文件，还包括所有导入的各种jar包工具，如果缺少文件在程序安装时会提示无法完成安装。

单击“Add Files”按钮，弹出如图27.29所示的对话框。该部分设置完成后单击“Done”按钮回到图27.28，但是此时“Next”按钮高亮，单击该按钮弹出图27.30所示对话框。

（5）设置程序主类。读者可以自动发现主类，根据文件数的多少等待时间不同，如果涉及的类很多，最好自己填写主类的名字，同时安装程序的图示也最好符合软件功能的需要。

笔者选择的结果如图27.31所示。单击“Next”按钮弹出如图27.32所示对话框，此时读者可以选择自动设置类路径，这样InstallAnyWhere会自动完成程序中需要的类路径的设置。此部分设置完成后单击“Next”按钮弹出如图27.33所示的对话框。

图　27.25　InstallAnyWhere首界面

图　27.26　保存新项目对话框

图　27.27　设置项目信息

图　27.28　添加程序所需文件

图　27.29　项目中添加文件

图　27.30　选择主类对话框

（6）构建安装程序。此时可以选择程序安装的系统平台，如Mac OS X、Windows或Linux，并且可以选择是否需要打包虚拟机。默认选项是Windows操作系统和带虚拟机。笔者选择默认设置，然后单击“Build”按钮，弹出如图27.34所示对话框。

图　27.31　选择主类对话框

图　27.32　设置类路径

图　27.33　构建安装程序

图　27.34　构建程序过程

当该程序构建完成，会自动弹出如图27.35所示对话框，显示安装信息。经过上述6个步骤后，安装程序制作完成，此时在图27.36所示的目录中可以看到一个安装程序，名称为“话单分析工具”，该名称在第3步中设置。

图　27.35　安装完成对话框

图　27.36　安装软件所在目录

27.7　数据库配置

在程序运行前需要进行数据库设置。打开控制面板，选择“管理工具”，继续选择“数据源（ODBC）”，弹出的ODBC数据源管理器对话框如图27.37所示。单击“添加”按钮，弹出如图27.38所示的对话框。

图　27.37　ODBC数据源管理器

图　27.38　加载驱动程序

因为笔者设计的软件采用了Access数据库，所以此时加载Microsoft Access Driver，然后单击“完成”按钮，弹出如图27.39所示的对话框。该数据源和一个数据库文件对应，在图27.39中单击“选择”按钮，来选择一个数据库文件。这里可根据自己的需要和库文件的位置选择，笔者的移动电话和归属地关系的数据库文件MobileDb.mdb存储在D：\phone\phonenew目录下。选择结果如图27.39所示。

图　27.39　设置Access数据源

27.8　小结

本章是一个完整的项目系统开发案例，从前期的系统设计、系统目标，一直到系统功能的分析和开发，都是在开发过程中真正必须要走的步骤。本章最后还介绍了设计完成的软件，如何打包发行，如果到了其他的机器上，该如何进行数据库的配置，这些都是发布一个系统所必须要经过的步骤。希望读者通过本章的学习，能掌握一个软件开发的过程和技巧。