面向对象程序设计(JAVA) 刘栓 信息工程学院.

Slides:



Advertisements
Similar presentations
主要内容 Java 的常用包 Java 的常用包 “ == ” 和 “ equals ” 的用法 “ == ” 和 “ equals ” 的用法 基本数据类型与引用类型 基本数据类型与引用类型 String 和 StringBuffer String 和 StringBuffer 对象的克隆( clone.
Advertisements

第 2 章 Java 运行环境搭建 2.1 Java 的运行系统 2.1 Java 的运行系统 2.2 JDK 的安装与配置2.2 JDK 的安装与配置 2.3 Java 开发工具包 2.4 Java 源文件编辑环境的选择 2.5 Application 和 Applet 程序的编写与运行.
Java 程序分类 Java Application :是完整程序,需要独立的解 释器解释运行;以 “.java” 为后缀的文件,以 main() 方法作为程序入口,由 java 编译器编译生 成字节码,由 Java 解释器加载执行字节码。 Java Applet 没有 main() 方法作为程序入口,是嵌在.
第一章 Java 程序设计技术 概述  什么是 Java 语言  一个简单的 Java 程序  程序的编译和运行  常见错误  使用 Java 核心 API 文档.
Java 程序设计 案例教程 北京大学出版社 第 01 章. Java 程序设计案例教程 第 01 章 Java 语言与面向对象程序设计 Java 语言的历史 Java 语言的特点 Java 程序的分类 Java 环境配置 Eclipse 的安装 Java 程序的调试 教学目标.
Java 程序设计(第二版) 普通高等教育 “ 十一五 ” 国家级规划教材 辛运帷等 编著 徐小平 主讲.
第四章 类、对象和接口.
3.2 Java的类 Java 类库的概念 语言规则——程序的书写规范 Java语言 类库——已有的有特定功能的Java程序模块
JAVA 编 程 技 术 主编 贾振华 2010年1月.
第1章 Java语言概述.
《 Java开发环境配置》 主讲人:耿力.
Java 2实用教程(第3版)教学课件 主讲教师:张国平
Java程序设计教程 第一讲 Java概述.
四資二甲 第三週作業 物件導向程式設計.
第4章 类与对象 本章导读 0. 面向对象编程 1. 类声明和类体 2. 类体的构成 3. 构造方法与对象的创建 4. 对象的引用与实体
Hello小程序的运行和编译 Java AppletJava小程序的构成 1、关键字
面向对象的程序设计(一).
《Java程序设计之网络编程》 教学课件 重庆大学计算机学院
第五章 字符串.
南京理工大学 第2章 Java基本语法 本章我们将学习Java编程语言的基本语法,包括变量、操作符、表达式、语句、字符串、数组、控制流以及如何使用帮助文档。 使用下面的编程框架: public class Test{ public static void main(String []args){ //以下添加测试代码.
Java Applet的运行原理 在网页向Java Applet传值 在Java Applet中播放声音 在Java Applet中使用组件
第11章 Java多媒体技术.
第三部分 Java语言编程应用篇 第6章 Java语言的 图形用户界面开发技术 (之二).
第二章 JAVA语言基础.
第二部分 Java语言基础篇 第4章 Java语言与面向对象 (之一).
第1章 java简介及环境搭建 第1章 Java简介及开发环境搭建.
第5章 进一步讨论对象和类.
再回首: Java关键字 数据类型:byte boolean char double float int long short ,
第5章 Java中类、对象、接口 及包的概念 5.1 类的基本概念 5.2 类的继承概念 5.3 抽象类和接口 5.4 包.
Ch02 視窗Swing套件 物件導向系統實務.
2.1 基本資料型別 2.2 變數 2.3 運算式與運算子 2.4 輸出與輸入資料 2.5 資料型別轉換 2.6 實例
第5章 面向对象程序设计 本章要点 5.1 面向对象程序设计概述 5.2 Java语言的面向对象程序设计 5.3 方法的使用和对象数组
2018/11/15 面向对象与多线程综合实验-GUI设计 教师:段鹏飞.
第二章 C# 基础知识.
Ch07 Java Applets 物件導向系統實務.
2018/11/20 第一章 Java概述 武汉大学计算机学院计算机应用系 2018/11/20 14:33.
2018/11/22 Java语言程序设计-程序流程 教师:段鹏飞.
Chapter 9 設計的精細製作: 行動計算 Software Engineering – An Engineering Approach, James F. Peters & Witold Pedrycz.
本單元介紹何謂變數,及說明變數的宣告方式。
2018/11/27 Java语言程序设计-程序流程 教师:段鹏飞.
程式設計實作.
抽象类 File类 String类 StringBuffer类
CH09 套件 物件導向程式設計(II).
第2章回顾 标识符:不用记,动手 关键字:if, else, switch, for, while, do, break, continue, void, …… 局部变量和成员变量 ①变量作用域 ②内存布局 基本数据类型 ①4类8种 ②互相转换 流程控制语句 ①分支 if……else, switch.
Java程序设计 第9章 继承和多态.
JAVA 2 新觀念教本 ---邁向SCJP專業認證--- 易瓏資訊 林新德 著.
Java语言程序设计 第八部分 Applet小程序.
Java基础入门 第1章 Java开发入门 · Java语言的特点 · Java开发环境的搭建 · 环境变量的配置 · Java的运行机制.
3.1 数据类型 3.2 标识符与关键字 3.3 常量 3.4 变量 3.5 运算符与表达式 3.6 一个编程实例
Ch04 事件處理 物件導向系統實務.
2019/1/16 Java语言程序设计-类与对象 教师:段鹏飞.
2019/1/17 Java语言程序设计-程序流程 教师:段鹏飞.
3.7 Java的工具类.
2.1 Java语法基础 2.2 Java 流程控制 2.3 数组 2.4 字符串
《JAVA程序设计》 语音答疑 辅导老师:高旻.
第二章Java基本程序设计.
第三课 标识符、关键字、数据类型.
第二章 Java基本语法 讲师:复凡.
第二章 Java语法基础.
第二章 Java基本语法 讲师:复凡.
第6章 面向对象的高级特征 学习目标 本章要点 上机练习 习 题.
第4章 数组与字符串 学习目标 本章要点 上机练习 习 题.
Java语言程序设计 清华大学出版社 第6章 java图形与图像处理.
PPT注意事项: 当前PPT课件文件必须和提供的源代码文件夹“代码”在同一目录中即不要移动文件夹“代码”的默认位置。
第2章 Java语言基础.
Applet.
第 5 章 常用类的使用 伍孝金
第二章 Java基础语法 北京传智播客教育
Summary
变量定位图形 Java中数据的类型分为四种:基本数据类型、数组类型、类类型以及接口类型。任何常量和变量都一定是上述四种数据类型中的一种。简单数据类型的实例化有两种:变量和常量。 变量名和常量名必须是Java语言中合法的标识符。 常量是在程序运行期间值不改变的量。 变量是在程序运行期间值可通过赋值改变的量,
Presentation transcript:

面向对象程序设计(JAVA) 刘栓 信息工程学院

目 录 第1章 Java环境及配置 第2章 Java基本语法 第3章 类和接口 第4章 Java Applet 第5章 Java图形处理 目 录 第1章 Java环境及配置 第2章 Java基本语法 第3章 类和接口 第4章 Java Applet 第5章 Java图形处理 第6章 Java用户界面技术 第7章 异常、事件和多线程机制 第8章 输入输出技术 第9章 Java数据库技术 第10章 Java安全技术 第11章 Java网络技术(一) 第12章 Java网络技术(二) 第13章 Servlet技术 第14章 Java读写XML技术

第1章Java环境及配置 1.1 Java概述 1.2 Java语言的特点 1.3 Java应用分类 1.4 JDK包的下载与安装 1.6 例子程序 习 题

1.1 Java 概 述 Java是一种编程语言,它提供了一个同时用于程序开发、应用和部署的环境。Java语言主要定位于网络编程,使得程序可以最大限度地利用网络资源。

1.2 Java 语 言 的 特 点 1. 跨平台性 所谓的跨平台性,是指软件可以不受计算机硬件和操作系统的约束而在任意计算机环境下正常运行。这是软件发展的趋势和编程人员追求的目标。之所以这样说,是因为计算机硬件的种类繁多,操作系统也各不相同,不同的用户和公司有自己不同的计算机环境偏好,而软件为了能在这些不同的环境里正常运行,就需要独立于这些平台。

而在Java语言中,Java自带的虚拟机很好地实现了跨平台性。Java源程序代码经过编译后生成二进制的字节码是与平台无关的,但是可被Java虚拟机识别的一种机器码指令。Java虚拟机提供了一个字节码到底层硬件平台及操作系统的屏障,使得Java语言具备跨平台性。

2. 面向对象 面向对象是指以对象为基本粒度,其下包含属性和方法。对象的说明用属性表达,而通过使用方法来操作这个对象。面向对象技术使得应用程序的开发变得简单易用,节省代码。Java是一种面向对象的语言,也继承了面向对象的诸多好处,如代码扩展、代码复用等。

3. 安全性 安全性可以分为四个层面,即语言级安全性、编译时安全性、运行时安全性、可执行代码安全性。 语言级安全性指Java的数据结构是完整的对象,这些封装过的数据类型具有安全性。编译时要进行Java语言和语义的检查,保证每个变量对应一个相应的值,编译后生成Java类。运行时Java类需要类加载器载入,并经由字节码校验器校验之后才可以运行。Java类在网络上使用时,对它的权限进行了设置,保证了被访问用户的安全性。

4. 多线程 多线程在操作系统中已得到了最成功的应用。多线程是指允许一个应用程序同时存在两个或两个以上的线程,用于支持事务并发和多任务处理。Java除了内置的多线程技术之外,还定义了一些类、方法等来建立和管理用户定义的多线程。

5. 简单易用 Java源代码的书写不拘泥于特定的环境,可以用记事本、文本编辑器等编辑软件来实现,然后将源文件进行编译,编译通过后可直接运行,通过调试则可得到想要的结果。

1.3 Java 应 用 分 类 1. 应用程序 典型的通用程序可以在具备Java运行环境的设备中独立运行,它又分为: GUI应用程序:即图形用户界面程序,可实现丰富的输入界面和输出显示。 命令行程序:无需界面,只需在命令行下运行,运行结果只在后台发生变化,可以将输出存放到文件中。 嵌入式应用程序:Java语言的平台独立性决定了它可以嵌入到不同的设备中,且只需具备必要的运行环境即可。

2. Servlets服务器端应用程序 服务器端的应用程序用来收集客户端的数据输入,对数据进行处理之后,返回相应的响应给客户。它主要用来实现与客户端的交互。

3. Applets小应用程序 Applets应用于网络上,嵌入在HTML网页中,支持Java的浏览器都可以对它进行解释并运行。通常通过一个HTML标签<APPLET></ APPLET >来识别并运行Applets。小应用程序的类在服务器端,当浏览器显示网页时,它随之下载到本地,由本地的浏览器载入运行。

1.4 JDK包的下载与安装 Java Develop Kit简称为JDK,是Sun公司免费发行的软件包,可以从Sun网站http://www.sun.com免费下载,也可以从其它国内地址下载。JDK版本从1.02开始,目前版本发展到1.4,其中高级版本对低级版本实现向下兼容。运用这个软件包,就可以对Java源程序进行编译和运行。本书中下载使用的JDK包为j2sdk-1_4_0_012-windows-i586.exe。 下载后双击图标,即可进行安装,默认的安装目录为C:\j2sdk1.4.0_01。本书作者将安装目录改为D:\j2sdk1.4.0_01。

1.5 Java 环 境 配 置 JDK包安装完成后,需要设置环境变量。用鼠标右键单击桌面上的图标“我的电脑”,选择“属性”项,出现标题为“系统特性”的对话框,点击“高级”标签,可以看见有一个“环境变量”按钮,如图1.1所示。

图1.1 “系统特性”对话框

单击“环境变量”按钮,可以看见本机环境变量,如图1 单击“环境变量”按钮,可以看见本机环境变量,如图1.2所示。上面为用户变量,下面为系统变量,随着操作系统或用户环境的不同,变量名、值有所不同。这里需要修改三个用户变量:include、lib和path,分别将JDK包安装之后的相应路径包含到这三个用户变量中。

图1.2 “环境变量”对话框

选中include变量,单击“编辑”按钮,弹出标题为“编辑用户变量”的对话框,如图1.3所示。在变量值一栏的最后添加“; D:\j2sdk1.4.0_01\include”,“;”表示与前面的各项隔开,后面的路径是JDK包的安装路径下的include目录。图1.3为作者修改include变量的情况,注意你的安装路径可能与作者的有所不同,要以你的安装路径为基准进行修改。

图1.3 编辑include变量

选中lib变量,单击“编辑”按钮,弹出标题为“编辑用户变量”的对话框,如图1. 4所示。在变量值一栏的最后添加“; D:\j2sdk1. 4 选中lib变量,单击“编辑”按钮,弹出标题为“编辑用户变量”的对话框,如图1.4所示。在变量值一栏的最后添加“; D:\j2sdk1.4.0_01\lib”,“;”表示与前面的各项隔开,后面的路径是JDK包的安装路径下的lib目录。图1.4为作者修改lib变量的情况,注意你的安装路径可能与作者的有所不同,要以你的安装路径为基准进行修改。

图1.4 编辑lib变量

选中path变量,单击“编辑”按钮,弹出标题为“编辑用户变量”的对话框,如图1.5所示。在变量值一栏的最后添加“; D:\j2sdk1.4.0_01\bin”,“;”表示与前面的各项隔开,后面的路径是JDK包的安装路径下的bin目录。图1.5为作者修改path变量的情况,注意你的安装路径可能与作者的有所不同,同样要以你的安装路径为基准进行修改。

图1.5 编辑path变量

1.6 例 子 程 序 【例1.1】源程序名称为HelloWorld.java,命令行提示符下输出字符串“Hello World”。源代码如下: //程序文件名称为HelloWorld.java public class HelloWorld { public static void main(String args[]) System.out.println("Hello World"); }

用记事本或者专用的编辑工具如EditPlus等进行编辑,并将文件存为HelloWorld 用记事本或者专用的编辑工具如EditPlus等进行编辑,并将文件存为HelloWorld.java。建议使用像EditPlus这样的编辑软件,可使得代码更加清晰且风格良好。 运行“开始”菜单→程序→附件→命令提示符,载入命令行程序,在命令行状态下,进入源程序所在的目录,图1.6所示的例子程序的目录在“E:\_Work\Java\sample”下,然后键入命令“javac HelloWorld.java”。若编译不通过,会产生错误提示。若编译通过,则没有任何提示,同时进入命令行等待状态,如图1.6所示。这时,命令行虽然没有提示,但在源程序的路径下生成一个新的文件为HelloWorld.class。这个.class文件就是编译后生成的类文件,运行此文件,需在命令行状态中键入命令“java HelloWorld”,然后按回车键,此时程序就会运行并输出“Hello World”。输出完毕,立即退出程序,进入命令行等待状态,如图1.7所示。

图1.6 编译源程序HelloWorld

图1.7 运行HelloWorld应用程序

这里用到的命令Javac和Java都是JDK软件包自带的。从JDK安装路径的bin目录下可以看到javac 这里用到的命令Javac和Java都是JDK软件包自带的。从JDK安装路径的bin目录下可以看到javac.exe,这是编译程序,源程序编译通过后就生成.class文件;而Java.exe就是载入类的运行程序,运行时根据源程序的指令要求产生正确的输出或结果。如果没有进行环境配置,直接编译或者运行Java源程序,系统会提示找不到这些命令,所以必须进行环境配置后再使用。

【例1.2】小应用程序的例子。输出“Hello World!”,如图1.8所示。源程序代码如下: //程序文件名称为HelloApplet.java import java.awt.Graphics; import java.applet.Applet; public class HelloApplet extends Applet { public void paint (Graphics g ) g.drawString ("Hello World!",50,25); }

小应用程序代码书写和编译完成后,无法独立运行,需要一个载体或者容器。下面的HTML网页代码就是小应用程序载入的容器。 <!-- 程序文件名称为HelloApplet.html --> <HTML> <HEAD> <TITLE> HTML Test Page </TITLE> </HEAD>

<BODY> HelloApplet will appear below in a Java enabled browser.<BR> <APPLET CODEBASE = "." CODE = "HelloApplet.class" NAME = "TestApplet" WIDTH = 400 HEIGHT = 300 HSPACE = 0 VSPACE = 0 ALIGN = middle > </APPLET> </BODY> </HTML>

图1.8 Applet显示“Hello World!”

习 题 1. 简述Java的特点。 2. 简述Java的分类情况。 3. 进行Java环境的安装和配置。 习 题 1. 简述Java的特点。 2. 简述Java的分类情况。 3. 进行Java环境的安装和配置。 4. 编写应用程序,屏幕上输出“欢迎来到Java世界!”。 5. 编写Applet,输出“欢迎来到Java世界!”。

第2章 Java基本语法 2.1 Java程序的构成 2.2 数据类型、变量和常量 2.3 运算符和表达式 2.4 流程控制 2.2 数据类型、变量和常量 2.3 运算符和表达式 2.4 流程控制 2.5 数组的使用 习 题

2.1 Java程序的构成 2.1.1 逻辑构成 Java源程序逻辑构成分为两大部分:程序头包的引用和类的定义。 1. 程序头包的引用 2.1.1 逻辑构成 Java源程序逻辑构成分为两大部分:程序头包的引用和类的定义。 1. 程序头包的引用 主要是指引用JDK软件包自带的包,也可以是自己定义的类。引用之后程序体中就可以自由应用包中的类的方法和属性等。

2. 类的定义 Java源程序中可以有多个类的定义,但必须有一个主类,这个主类是Java程序运行的入口点。在应用程序中,主类为包含main方法的类;在Applet中,主类为用户自定义的系统Applet类的扩展类。在Java源程序中,主类的名字同文件名一致。 类的定义又包括类头声明和类体定义。类体中包括属性声明和方法描述。下面来看一个例子,其中斜体表示的语句行为主类类头,主类类头下面从大括号“{”开始到“}”结束的部分称为主类类体。

【例2.1】下面是一个应用程序,也是一个Applet,既可以在命令行下运行,也可以嵌入到HTML网页中用appletviewer命令运行。运行时在界面上的第一个文本框中输入你的名字,按回车键后,在第二个文本框中会显示“XXX,欢迎你来到Java世界!”,运行结果如图2.1所示。 //程序文件名称为WelcomeApplet.java 注释语句 引入包 public class WelcomeApplet extends Applet implements ActionListener 主类类头 {

属 性 init方法

actionPerformed 方法

main主方法

图2.1 程序界面

2.1.2 物理构成 Java源程序物理上由三部分构成,分别为语句、块和空白。 (1) 语句指一行以分号“;”结束的语句。 (2) 块指用括号对{}界定的语句序列,块可以嵌套使用。 (3) 空白指语句之间、块内部或者块之间的空白行。空白不影响Java源程序的编译和运行,适当地运用空白,可以形成良好的代码风格。

在例1.1中, Label lblName; TextField txtName; TextField txtDisp; 都是语句,而 { lblName = new Label("请输入您的名字:"); txtName = new TextField(8); txtDisp = new TextField(20); add(lblName); add(txtName); add(txtDisp); txtName.addActionListener(this); } 是块,语句之间、块之间或块内部的空行都为空白。

例如,下面的语句就是注释语句用来说明程序文件名称的。 2.1.3 注释语句 注释语句主要用来进行一些说明,或者标记一些无用的程序语句。有两种注释方法,行注释为以//开始的行;块注释以/*开始和*/结束,Java编译器忽略注释后的程序语句或说明。 例如,下面的语句就是注释语句用来说明程序文件名称的。 //程序文件名称为WelcomeApplet.java 上述的语句注释可以更改为: /*程序文件名称为WelcomeApplet.java*/ 或 /* 程序文件名称为 WelcomeApplet.java */

2.1.4 标识符、关键字和转义符 在Java语言中,标识符是赋予变量、类和方法等的名称。标识符由编程者自己指定,但需要遵循一定的语法规范: (1) 标识符由字母、数字、下划线(_)、美元符号($)组成,但美元符号用得较少。 (2) 标识符从一个字母、下划线或美元符号开始。 (3) Java语言中,标识符大小写敏感,必须区别对待。 (4) 标识符没有最大长度的限制,但最好表达特定的意思。 (5) 标识符定义不能是关键字。

关键字又称保留字,是指Java语言中自带的用于标志数据类型名或者程序构造名等的标识符,如public、double等。 转义符是指一些有特殊含义的、很难用一般方式表达的字符,如回车、换行等。所有的转义符以反斜线(\)开头,后面跟着一个字符来表示某个特定的转义符,如表2.1所示。

表2.1 转 义 符

2.2 数据类型、变量和常量 2.2.1 数据类型 Java编程语言定义了八种基本的数据类型(见表2.2),共分为四类:整数类(byte、short、int、long)、文本类(char)、浮点类(double、float)和逻辑类(boolean)。

表2.2 Java的数据类型

1. 整数类 (1) 采用三种进制——十进制、八进制和十六进制。 2 —— 十进制值是2; 077 —— 首位的0表示这是一个八进制的数值; 0xBAAC —— 首位的0x表示这是一个十六进制的数值。 (2) 具有缺省int。 (3) 用字母“L”和“l”定义long。 (4) 所有Java编程语言中的整数类型都是带符号的数字。

2. 文本类 (1) 代表一个16 bit Unicode字符。 (2) 必须包含用单引号(' ')引用的文字。 (3) 使用下列符号: 'a'——一个字符。 '\t'--一个制表符。 '\u???? '--一个特殊的Unicode字符,????应严格使用四个十六进制数进行替换。

3. 浮点类 默认为double类型,如果一个数字包括小数点或指数部分,或者在数字后带有字母F或f(float)、D或d(double),则该数字为浮点数。

4. 逻辑类 boolean数据类型有两种值:true和false。 例如:boolean flag = true; 上述语句声明变量flag为boolean 类型,它被赋予的值为true。

2.2.2 变量与常量 常量是指整个运行过程中不再发生变化的量,例如数学中的π= 3.1415……,在程序中需要设置成常量。而变量是指程序的运行过程中发生变化的量,通常用来存储中间结果,或者输出临时值。 变量的声明也指变量的创建。执行变量声明语句时,系统根据变量的数据类型在内存中开辟相应的存储空间并赋予初始值。变量有一个作用范围,超出它声明语句所在的块就无效。

下面看一个使用各种类型变量声明并改变的示例。程序中pi为常量,s1、i1、l1、ch1、f1、d1、b1为全局变量,可以在方法change中发生改变,然后在方法main中输出。而s2、i2、l2、ch2、f2、d2、b2是方法main的局部变量,它们的作用范围只局限于方法main中。 【例2.2】测试不同数据类型的变量,程序输出如图2.2所示。源程序代码如下: //程序文件名称为SetVariable.java public class SetVariable { //全局变量

static double pi = 3.141592654;//数学常量 static short s1; static int i1; static long l1; static char ch1; static float f1; static double d1; static boolean b1; public static void main(String args[]) {

//局部变量 short s2 = 35; int i2 = -32; long l2 = 34555L; char ch2 = 'A'; float f2 = 897.89F; double d2 = 34.345; boolean b2 = false; //输出常量 System.out.println("数学常量pi = " + pi); //输出局部变量

System.out.println("******局部变量******"); System.out.println("短整型变量s2 = " + s2); System.out.println("整型变量i2 = " + i2); System.out.println("长整型变量l2 = " + l2); System.out.println("字符变量ch2 = " + ch2); System.out.println("浮点数类型f2 = " + f2); System.out.println("双精度型变量d2 = " + d2); System.out.println("布尔型变量b2 = " + b2); //调用方法修改全局变量的值

change(); //输出全局变量的值 System.out.println("******全局变量******"); System.out.println("短整型变量s1 = " + s1); System.out.println("整型变量i1 = " + i1); System.out.println("长整型变量l1 = " + l1); System.out.println("字符变量ch1 = " + ch1); System.out.println("浮点数类型f1 = " + f1); System.out.println("双精度型变量d1 = " + d1); System.out.println("布尔型变量b1 = " + b1); }

//方法:修改全局变量的值 public static void change() { s1 = 125; i1 = 88; l1 = 987654321L; ch1 = 'B'; f1 = 3.2590F; d1 = -1.04E-5; b1 = true; }

图2.2 变量输出结果

2.3 运算符和表达式 Java常用的运算符分为五类:算术运算符、赋值运算符、关系运算符、布尔逻辑运算符、位运算符。位运算符除了简单的按位操作外,还有移位操作。按位操作返回布尔值。 表达式是由常量、变量、对象、方法调用和操作符组成的式子。表达式必须符合一定的规范,才可被系统理解、编译和运行。表达式的值就是对表达式自身运算后得到的结果。 根据运算符的不同,表达式相应地分为以下几类:算术表达式、关系表达式、逻辑表达式、赋值表达式,这些都属于数值表达式。

2.3.1 算术运算符及算术表达式 Java中常用的算术运算符如下: + 加运算符 - 减运算符 * 乘运算符 / 除运算符 % 取模运算(除运算的余数) ++ 增量运算符 -- 减量运算符

【例2.3】测试运算符及表达式,程序输出如图2.3所示。源程序代码如下: //程序文件名称为NumberOper.java public class NumberOper { public static void main(String args[]) //变量初始化 int a = 30; int b = 20; //定义结果变量 int r1,r2,r3,r4,r5,r6,r7,r8,r9; //计算结果 r1 = a + b;

r2 = a-b; r3 = a * b; r4 = a / b; r5 = a % b; r6 = a ++; r7 = b--; r8 = ++ a; r9 = -- b; //输出结果 System.out.println("a = " + a + " b = " + b); //a,b的值 System.out.println("a+b = " + r1); System.out.println("a-b = " + r2);

System.out.println("a*b = " + r3); System.out.println("a++ =" + r6); System.out.println("b-- =" + r7); System.out.println("++a =" + r8); System.out.println("--b =" + r9); }

图2.3 程序输出结果

2.3.2 关系运算符 关系运算符用于比较两个数据之间的大小关系,关系运算表达式返回布尔值,即“真”或“假”。Java中的常用关系运算符如下: = = 等于 ! = 不等于 > 大于 < 小于 >= 大于等于 <= 小于等于

【例2.4】编写程序,测试关系运算符及其表达式,程序输出如图2.4所示。源程序代码如下: //程序文件名称为TestRelation.java public class TestRelation { public static void main(String args[]) //变量初始化 int a = 30; int b = 20; //定义结果变量 boolean r1,r2,r3,r4,r5,r6; //计算结果

r1 = a == b; r2 = a != b; r3 = a > b; r4 = a < b; r5 = a >= b; r6 = a <= b; //输出结果 System.out.println("a = " + a + " b = " + b); System.out.println("a==b = " + r1); System.out.println("a!=b = " + r2); System.out.println("a>b = " + r3); System.out.println("a<b = " + r4); System.out.println("a>=b = " + r5); System.out.println("a<=b = " + r6); }

图2.4 程序输出结果

2.3.3 布尔逻辑运算符 表2.3 布尔运算符及规则

图2.3为布尔逻辑运算符及其规则示例等。其中简洁与和简洁或的执行结果分别与非简洁与和非简洁或的执行结果是一致的,不同在于简洁与检测出符号左端的值为假时,不再判断符号右端的值,直接将运算结果置为假;而简洁或与非简洁或的不同在于简洁或检测出符号左端为真时,不再判断符号右端的值,直接将运算结果置为真。 例如: Boolean a = false; Boolean b = true; a && b检测到a为假,则无需判断b的值,直接将值置为假;而b || a时检测到b为真,则无需判断a的值,直接将值置为真。

【例2.5】测试布尔表达式,程序输出结果如图2.5所示。源程序代码如下: //程序文件名称为TestLogic.java public class TestLogic { public static void main(String args[]) //变量初始化 boolean a = false; boolean b = true; //定义结果变量 boolean r1,r2,r3,r4,r5,r6; //计算结果

r1 = !a; r2 = a & b; r3 = a | b; r4 = a ^ b; r5 = a && b; r6 = a || b; //输出结果 System.out.println("a = " + a + " b = " + b); System.out.println("!a = " + r1); System.out.println("a&b = " + r2); System.out.println("a|b = " + r3); System.out.println("a^b = " + r4); System.out.println("a&&b = " + r5); System.out.println("a||b = " + r6); }

图2.5 程序输出结果

2.3.4 位运算符 Java中的常用位运算符如下: ~ 位求反 & 按位与 | 按位或 ^ 按位异或 << 左移 >> 右移 >>> 不带符号右移 右移运算符对应的表达式为x>>a,运算的结果是操作数x被2的a次方来除,左移运算符对应的表达式为x<<a,运算的结果是操作数x乘以2的a次方。

【例2.6】测试位运算符<<和>>,程序输出结果如图2.6所示。源程序代码如下: //程序文件名称为TestBit.java public class TestBit { public static void main(String args[]) //变量初始化 int a = 36; int b = 2; //定义结果变量 int r1,r2;

//计算结果 r1 = a >> b; r2 = a << b; //输出结果 System.out.println("a = " + a + " b = " + b); System.out.println("a>>b = " + r1); System.out.println("a<<b = " + r2); }

图2.6 程序输出结果

2.3.5 赋值运算符 赋值运算符分为简单运算符和复杂运算符。简单运算符指“=”,而复杂运算符是指算术运算符、逻辑运算符、位运算符中的双目运算符后面再加上“=”。表2.4列出Java常用的赋值运算符及其等价表达式。

表2.4 赋值运算符及其等价表达式

2.3.6 其它操作符及其表达式 三目运算符(?:)相当于条件判断,表达式x?y:z用于判断x是否为真,如果为真,表达式的值为y,否则表达式的值为z。 例如: int x = 5; int a = (x>3)?5:3; 则a的值为5。如果x = 2,则a的值为3。

对象运算符(instanceof)用来判断一个对象是否属于某个指定的类或其子类的实例,如果是,返回真(true),否则返回假(false)。 例如: boolean b = userObject instanceof Applet 用来判断userObject类是否是Applet类的实例。

2.3.7 优先级 表2.5 运算符优先级

2.4 流 程 控 制 流程控制分为三种基本结构:顺序结构、分支结构和循环结构。顺序结构是指命令行顺序执行,这是最常见的一个格式;分支结构是一种选择结构,根据条件的值选择不同的执行流程,可以得到不同的结果。分支结构包括单分支语句(if-else语句)和多分支语句(switch语句);循环结构是指对于一些重复执行的语句,用户指定条件或次数,由机器自动识别执行。循环结构包括次数循环语句(for语句)和条件循环语句(while语句)。

2.4.1 分支语句 分支语句分为两类:单分支语句和多选语句。 1. if-else语句 if-else语句的基本格式为: if(布尔表达式) { 语句或块1; } else 语句或块2;

其中: (1) 布尔表达式返回值为true或false。 (2) 如果为true,则执行语句或块1,执行完毕跳出if-else语句。 (3) 如果为false,则跳过语句或块1,然后执行else下的语句或块2。

【例2.7】测试if-else语句,如果x>10,则输出x的值,并提示结果正确,否则输出x= 10,提示结果不正确。程序输出结果如图2.7所示。源程序代码如下: //程序文件名称为TestIf.java public class TestIf { //声明全局变量x static int x; public static void main(String args[]) x = 12; if(x>10)

System.out.println("x = " + x + " 结果正确"); } else System.out.println("x = 10" + " 结果不正确"); change(); System.out.println("修改x的值之后"); if(x>10) {

//change方法:修改x的值 public static void change() { x = 5; }

图2.7 程序输出结果

2. switch语句 switch语句的基本格式为: switch(表达式1) { case 表达式2: 语句或块2; break; case表达式3: 语句或块3; case 表达式4: 语句或块4; default: 语句或块5; }

其中: (1) 表达式1的值必须与整型兼容。 (2)  case分支要执行的程序语句。 (3) 表达式2、3、4是可能出现的值。 (4) 不同的case分支对应着不同的语句或块序列。 (5)  break表示跳出这一分支。

【例2. 8】测试switch语句,当x=1、2、3时,分别打印1、2、3,x不为这三个值时,打印x的值。程序输出结果如图2 //程序文件名称为TestSwitch.java public class TestSwitch { public static void main(String args[]) //声明变量x int x; x = 12;

System.out.println("x=12时打印的值"); choose(x); x = 3; System.out.println("x=3时打印的值"); } //choose方法:switch语句结构 public static void choose(int x) { switch(x)

case 1: System.out.println(1); break; case 2: System.out.println(2); case 3: System.out.println(3); default: System.out.println(x); }

图2.8 程序输出结果

2.4.2 for循环语句 for循环语句实现已知次数的循环,其基本格式为: for(初始化表达式;测试表达式;步长) { 语句或块; }

其执行顺序如下: (1) 首先运行初始化表达式。 (2) 然后计算测试表达式,如果表达式为true,执行语句或块;如果表达式为false,退出for循环。 (3) 最后执行步长。

【例2.9】用for循环统计1~100(包括100)之间数的总和。程序输出结果如图2.9所示。源程序代码如下: //程序文件名称为TestFor.java public class TestFor { public static void main(String args[]) int sum = 0; for(int i = 1; i<=100; i++) sum += i; System.out.println("1到100(包括100)的数的总和为:" + sum); }

图2.9 程序输出结果

2.4.3 while循环语句 while循环语句实现受条件控制的循环,其基本格式为: while(布尔表达式) { 语句或块; } 当布尔表达式为true时,执行语句或块,否则跳出while循环。

上面for循环语句的例子改为while语句后如下所示: int sum = 0; int i = 1; while (i<=100) { sum += i; i++; } System.out.println("1到100(包括100)的数的总和为:" + sum);

2.4.4 do语句 do语句实现受条件控制的循环,其基本格式为: do { 语句或块; } while(布尔表达式)

先执行语句或块,然后再判断布尔表达式。与while语句不同,当布尔表达式一次都不为true时,while语句一开始判断就跳出循环,不执行语句或块,而在do语句中则要执行一次。上面那个例子改为do循环为: int sum = 0; int i = 1; do { sum += i; i++; } while (i<=100); System.out.println("1到100(包括100)的数的总和为:" + sum);

2.5 数 组 的 使 用 2.5.1 数组声明 数组的定义如下: (1) 首先是一个对象。 2.5 数 组 的 使 用 2.5.1 数组声明 数组的定义如下: (1) 首先是一个对象。 (2) 存放相同的数据类型,可以是原始数据类型或类类型。 (3) 所有的数组下标默认从0开始,而且访问时不可超出定义的上限,否则会产生越界错误。

数组声明时实际是创建一个引用,通过代表引用的这个名字来引用数组。数组声明格式如下: 数据类型 标识符[] 例如: int a[];//声明一个数据类型为整型的数组a pencil b[];//声明一个数据类型为pencil类的数组b

2.5.2 创建数组 由于数组是一个对象,所以可以使用关键字new来创建一个数组,例如: a = new int[10];//创建存储10个整型数据的数组a b = new pencil[20];//创建存储20个pencil类数据的数组b 数组创建时,每个元素都按它所存放数据类型的缺省值被初始化,如上面数组a的值被初始化为0,也可以进行显式初始化。在Java编程语言中,为了保证系统的安全,所有的变量在使用之前必须是初始化的,如果未初始化,编译时会提示出错。有两种初始化数组的方式,分别如下:

(1) 创建数组后,对每个元素进行赋值。 a[0]=5; a[1]=4; ... a[9] = 10; (2) 直接在声明的时候就说明其值,例如: int a[] = {4,5,1,3,4,20,2}; 说明了一个长度为7的一维数组。

【例2.10】编写程序测试数组,程序输出结果如图2.10所示。源程序代码如下: //程序文件名称为TestArray.java public class TestArray { public static void main(String args[]) //声明数组 int a[]; char b[]; //创建数组

a = new int[3]; b = new char[2]; //数组初始化 for (int i = 0; i<3; i++) { a[i] = i*3; } b[0] = 'a'; b[1] = 'b'; //快速初始化数组 int c[] = {0,1*3,2*3}; //输出结果 System.out.print("数组a\n");

for (int i = 0; i<2; i++) { System.out.print(b[i] + " "); } System.out.print("\n数组c\n"); for (int i = 0; i<3; i++) System.out.print(c[i] + " ");

图2.10 程序输出结果

习 题 1. 给出下列表达式的值。 (1) 3++4<<2^-8 (2) "abc"&123||8<<2 习 题 1. 给出下列表达式的值。 (1) 3++4<<2^-8 (2) "abc"&123||8<<2 (3) 36>>2*4&&48<<8/4+2 (4) 2*4&&0<2||4%2 2. 编写程序,统计课程编号为1001、1002、2001和3001的平均成绩并输出。学生成绩表如图2.11所示。【每个课程编号的成绩用数组存储,读取时循环操作】

图2.11 习题2.2的成绩表

3. 根据上题得出的考生平均成绩进行判断,如果在90分以上,屏幕上输出“课程编号为XXXX的考生平均成绩为优”;在80~90分之间输出“课程编号为XXXX的考生平均成绩为良”;在70~80分之间输出“课程编号为XXXX的考生平均成绩为中”,在60~70分之间输出“课程编号为XXXX的考生平均成绩为及格”;60分以下输出“课程编号为XXXX的考生平均成绩为不及格”。 4. 编写程序,用数组实现乘法小九九的存储和输出。【提示:采用多个一维数组。】

第3章类和接口 3.1 类 3.2 接口 3.3 常用数据结构及类 习 题

3.1 类 3.1.1 类的定义和声明 Java编程语言是面向对象的,处理的最小的完整单元为对象。而现实生活中具有共同特性的对象的抽象就称之为类。类由类声明和类体构成,类体又由变量和方法构成。下面给出一个例子来看一下类的构成。 【例3.1】自定义一个apple类,在主类SetApple中创建实例并调用方法,输出结果如图3.1所示。源程序代码如下:

//程序文件名为SetApple.java public class SetApple { public static void main(String[] args) apple a = new apple();//创建apple类 a.appleweight = 0.5;//实例变量赋值 System.out.println("苹果的重量为1两"); System.out.println(a.bite());//调用实例方法 a.appleweight = 5; System.out.println("苹果的重量为5两"); System.out.println(a.bite()); }

//自定义类 class apple { //属性 long applecolor;//对应苹果的颜色 double appleweight;//苹果的重量 boolean eatup;//是否吃完 //类方法 public boolean bite() if (appleweight<1) System.out.println("苹果已经吃完了!哈哈"); eatup = true; }

else { System.out.println("苹果吃不下了!:("); eatup = false; } return eatup;

图3.1 自定义类的应用

1. 类声明的基本格式 访问说明符 class 类名 extends 超类名 implements 接口名 其中: (1) 访问说明符为public或者缺省。public用来声明该类为公有类,可以被别的对象访问。声明为公有的类存储的文件名为类名。 (2) 类名:用户自定义的标识符,用来标志这个类的引用。 (3) 超类名:是指已经存在的类,可以是用户已经定义的,也可以是系统类。 (4) 接口名:即后面讲到的接口。

例如: public class HelloApplet extends Applet 访问说明符为public,类名HelloApplet,扩展类为JDK包自带的java.applet.Applet类。由于public的存在,所以文件名必须存为HelloApplet.java,同类名保持一致。

2. 类体 类体包括成员变量和方法。 (1) 成员变量:指类的一些属性定义,标志类的静态特征,它的基本格式如下: 访问说明符 数据类型 变量名 其中: ●访问说明符有public、private和protected三种: public:省略时默认为公有类型,可以由外部对象进行访问。 private:私有类型,只允许在类内部的方法中使用,若从外部访问,必须通过构造函数间接进行。 Protected:受保护类型,子类访问受到限制。 ● 数据类型包括基本类型以及用户自定义的扩展类型。

(2) 方法:是类的操作定义,标志类的动态特征,它的基本格式如下: ●访问说明符 数据类型 方法名(数据类型1 变量名1, 数据类型2 变量名2) 其中: ●访问说明符为public、private和protected,其使用方法与成员变量访问说明符的使用方法一致。 ●数据类型:包括基本数据类型和用户自定义的扩展类型。 ●数据类型为参数。

3. 创建类的实例 使用关键字 new进行创建,例如: HelloApplet hp = new HelloApplet(); 例3.1中,自定义类apple,访问标识符缺省,定义三个属性: long applecolor;//对应苹果的颜色 double appleweight;//苹果的重量 boolean eatup;//是否吃完

一个方法为: public boolean bite()//类方法{...} 公有类SetApplet中引用自定义类,首先创建类的实例: apple a = new apple(); 其次赋初值: a.appleweight = 0.5;//实例变量赋值 最后调用它的方法: System.out.println(a.bite());//调用实例方法

Java编程语言中允许用extends关键字从一个类扩展出一个新类,新类继承超类的成员变量和方法,并可以覆盖方法。 3.1.2 类的单继承性 Java编程语言中允许用extends关键字从一个类扩展出一个新类,新类继承超类的成员变量和方法,并可以覆盖方法。 【例3.2】测试类的单继承性,程序输出结果如图3.2所示。源程序代码如下: //程序文件名TestExtend.java public class TestExtend extends Employee { public static void main(String[] args) System.out.println("覆盖的方法调用:" + getSalary("王一",500));

System.out.println("继承的方法调用:" + getSalary2("王一",500)); } public static String getSalary(String name, int salary) { String str; if (salary>5000) str = "名字: " + name + " Salary: " + salary; else str = "名字: " + name + " Salary: 低于5000";

return str; } }; class Employee { public String name;//名字 public int salary;//薪水 public static String getSalary(String name, int salary) String str; str = "名字: " + name + " Salary: " + salary;

public static String getSalary2(String name, int salary) { String str; str = "名字: " + name + " Salary: " + salary; return str; } };

程序中定义了父类Employee类,它有两个方法getSalary和getSalary2,方法体的实现都是一致的,都为输出名字和薪水的值。在TextExtend主类中覆盖了getSalary方法,方法体重新定义为薪水低于5000时并不输出薪水的值而是输出“低于5000”,用于和继承的getSalary2方法进行比较。由图3.2可以看出覆盖的方法按主程序中重定义的方法调用,而继承的方法直接调用父类中的方法。

图3.2 测试单继承性程序的输出结果

3.1.3 特殊变量 类中有两个特殊变量super和this。 1. super 类声明中用关键字extends扩展了其超类之后,super用在扩展类中引用其超类中的成员变量。 【例3.3】使用super变量,输出结果如图3.3所示。源程序代码如下: //程序文件名为UseSuper.java public class UseSuper {

public static void main(String[] args) { Manager m = new Manager(); m.name = "王飞"; m.salary = 10000; m.department = "业务部"; System.out.println(m.getSalary()); } class Employee

public String name;//名字 public int salary;//薪水 //方法 public String getSalary() { String str; str = "名字: " + name + "\nSalary: " + salary; return str; } class Manager extends Employee

{ public String department;//部门 //方法 public String getSalary() //使用super变量调用超类的方法 return super.getSalary() + "\nDepartment: " + department; }

图3.3 测试super变量的输出

2. this this变量指向当前对象或实例。 str = "名字: " + name + "\nSalary: " + salary; 上例中的语句可以换成下面的语句。 str = "名字: " + this.name + "\nSalary: " + this.salary; 这两者是等同的,因为在Java编程语言中,系统自动将this关键字与当前对象的变量相关联。但有一种情况例外,就是当在某些完全分离的类中调用一个方法并将当前对象的一个引用作为参数传递时。例如: Day d = new Day(this);

3.1.4 构造函数 类中的构造函数用来初始化一个类。构造函数为公有类型,无返回值,用来从类实例中访问类时初始化此类的私有变量。 【例3.4】基于例3.3将公有变量改成私有变量之后,增加两个构造函数,访问通过外部调用构造函数实现初始化赋值,得到如图3.3所示的结果。 //程序文件名为UseConstruct.java public class UseConstruct { public static void main(String[] args)

private String name;//名字 private int salary;//薪水 //构造函数 Manager m = new Manager("王飞",10000,"业务部");//初始化赋值 System.out.println(m.getSalary()); } class Employee { private String name;//名字 private int salary;//薪水 //构造函数 public Employee(String _name, int _salary)

name = _name; salary = _salary; } public String getSalary() { String str; str = "名字: " + name + "\nSalary: " + salary; return str; class Manager extends Employee private String department;

//构造函数 public Manager(String _name, int _salary, String _department) { super(_name,_salary); department = _department; } public String getSalary() return super.getSalary() + "\nDepartment: " + department;

3.1.5 包 计算机操作系统使用文件夹或者目录来存放相关或者同类的文档,在Java编程语言中,提供了一个包的概念来组织相关的类。包在物理上就是一个文件夹,逻辑上代表一个分类概念。 包就是指一组类。例如一个名叫Company的包,可以包含一组类,如Employee(雇员)、Manager(管理者)和Department(部门)等。声明包的基本格式如下: Package 包名;

其中:Package为关键字,包名为标识符。 使用包时的注意事项如下: (1) Package语句要作为程序非注释语句的第一行语句。 (2) 包内的类名惟一。 (3) 引用包中的类时,使用import语句。import语句的基本格式为import 包名.类名,其中import为关键字,包名和类名之间用圆点(.)隔开。

【例3.5】编写程序测试包,先建立一个Company文件夹,然后建立名为Manager.java的类文件。源程序代码如下: package Company;//声明包名Company class Employee { public String name;//名字 public int salary;//薪水 public String getSalary()

String str; str = "名字: " + name + "\nSalary: " + salary; return str; } public class Manager extends Employee { public String department;//部门 public String getSalary() return super.getSalary() + "\nDepartment: " + department;

对此文件进行编译,生成类文件Manager.class。 在原目录建立源程序文件UsePackage.java。源程序代码如下: import Company.Manager;//引入包中的类 public class UsePackage { public static void main(String[] args) Manager m = new Manager(); m.name = "王飞"; m.salary = 10000; m.department = "业务部"; System.out.println(m.getSalary()); }

编译后,在命令提示符状态下运行,输出结果如图3. 4所示。从图3. 4中可以看出首先进入Company目录,编译Manager 编译后,在命令提示符状态下运行,输出结果如图3.4所示。从图3.4中可以看出首先进入Company目录,编译Manager.java文件,然后返回上层目录,编译UsePackage.java文件,最后执行UsePackage类文件,输出正确的结果。

图3.4 测试包的输出结果

3.2 接 口 Java编程语言中禁止多继承属性,但可以通过接口来帮助类扩展方法。接口中可以定义大量的常量和方法,但其中的方法只是一种声明,没有具体的实现,使用接口的类自己实现这些方法。接口与类的不同在于: (1) 没有变量的声明,但可以定义常量。 (2) 只有方法的声明,没有方法的实现。 接口声明的基本格式如下: public interface 接口名 extends 接口列表

【例3.6】测试接口,定义接口文件Product.java,定义了两个常量,声明了一个方法。接口文件如下: public interface Product { static final String MAKER = "计算机制造厂"; static final String ADDRESS = "上海"; public int getPrice(); }

使用接口的源文件代码如下: //程序文件名UseInterface.java public class UseInterface { public static void main(String[] args) Computer p = new Computer(); System.out.print(p.ADDRESS + p.MAKER); System.out.println(" 计算机的价格:" + p.getPrice()+ " 万元"); }

class Computer implements Product { public int getPrice() return 1; }

首先编译接口文件“javac Product. java”,然后编译使用这个接口的类文件“javac. UseInterface 首先编译接口文件“javac Product.java”,然后编译使用这个接口的类文件“javac.UseInterface.java”,最后执行类“java UseInterface”,输出结果如图3.5所示。 图3.5 测试接口的输出结果

3.3 常用数据结构及类 3.3.1 Vector类 Vector类似于一个数组,但与数组相比在使用上有以下两个优点。 3.3 常用数据结构及类 3.3.1 Vector类 Vector类似于一个数组,但与数组相比在使用上有以下两个优点。 (1) 使用的时候无需声明上限,随着元素的增加,Vector的长度会自动增加。 (2) Vector提供额外的方法来增加、删除元素,比数组操作高效。 Vector类有三个构造函数,分别如下: public Vector(); 该方法创建一个空的Vector。

public Vector(int initialCapacity); 该方法创建一个初始长度为initialCapacity的Vector。 public Vector(int initialCapacity, int capacityIncrement); 该方法创建一个初始长度为initialCapacity的Vector,当向量需要增长时,增加capacityIncrement个元素。

(1) Vector类中添加、删除对象的方法如下: public void add(int index, Object element) 在index位置添加对象element。 public boolean add(Object o) 在Vector的末尾添加对象o。 public Object remove(int index) 删除index位置的对象,后面的对象依次前提。

(2) Vector类中访问、修改对象的方法如下: public Object get(int index) 返回index位置对象。 public Object set(int index, Object element) 修改index位置的对象为element。

(3) 其它方法: public String toString() 将元素转换成字符串。 public int size() 返回对象的长度。

【例3.7】操作Vector对象,进行元素的添加、插入、修改和删除。程序输出结果如图3.6所示。源程序代码如下: //程序文件名为UseVector.java import java.util.Vector;//引入JDK的Vector类 public class UseVector { public static void main(String[] args)

Vector vScore = new Vector(); vScore.add(" 86"); //添加元素 vScore.add(" 98"); //添加元素 vScore.add(1, " 99"); //插入元素 //输出结果 for (int I = 0; I < vScore.size(); I++) { System.out.print(vScore.get(i) + " "); } vScore.set(1, " 77"); //修改第二个元素 vScore.remove(0); //删除第一个元素

System.out.println(″\n修改并删除之后″); for (int I = 0; I< vScore.size(); I++) { System.out.print(vScore.get(i) + " "); } System.out.println(" \n转换成字符串之后的输出\n" + vScore.toString()); };

图3.6 操作Vector对象的输出结果

3.3.2 Hashtable类 Hashtable类存储的是对象的名-值对。将对象的名和它的值相关联同时存储,并可以根据对象名来提取它的值。在Hashtable中,一个键名只能对应着一个键值,然而一个键值可以对应多个键名,键名必须是惟一的。构造函数以及常用方法如下: public Hashtable() 构建散列表。 public Hashtable(int initialCapacity) 构建长度为initialCapacity的散列表。 public int size()

返回散列表的名的个数。 public Object remove(Object key) 删除散列表中key名及其对应的value值。 public Object put(Object key,Object value) 将对象名key和对象值value存放到散列表中。 public Object get(Object key) 返回散列表key名对应的值。 public String toString() 转换成字符串。

【例3.8】操作Hashtable对象,进行添加、修改、删除等操作,输出结果如图3.7所示。源程序代码如下: //程序文件名为UseHashtable.java import java.util.Hashtable; public class UseHashtable { public static void main(String[] args) Hashtable hScore = new Hashtable(); hScore.put("张一","86");

hScore.put("李二","98"); hScore.put("海飞","99"); System.out.println("转换成字符串之后的输出:" + hScore.toString()); hScore.put("李二","77"); hScore.remove("张一"); System.out.println("修改并删除之后"); }

图3.7 操作Hashtable对象的输出结果

3.3.3 Enumeration接口 实现Enumeration接口的对象生成一系列元素,通过nextElement()方法依次读取下一个元素。只有以下两个方法: public boolean hasMoreElements() 测试是否还有元素。 public Object nextElement() 返回枚举的下一个元素。 Enumeration接口及其方法通常与Vector、Hashtable一起连用,用来枚举Vector中的项和Hashtable中的键名,例如: for (Enumeration e = v.elements() ; e.hasMoreElements() ;) System.out.println(e.nextElement());

【例3. 9】使用Enumeration接口枚举Vector中的对象和Hashtable对象中的键名,并进行输出,结果如图3 //程序文件名UseEnumeration.java import java.util.*; public class UseEnumeration { public static void main(String[] args) Vector vScore = new Vector(); vScore.add("86"); vScore.add("98");

vScore.add(1,"99"); System.out.println("Vector:" + vScore.toString()); for (Enumeration e = vScore.elements() ; e.hasMoreElements() ;) System.out.println(e.nextElement()); Hashtable hScore = new Hashtable(); hScore.put("张一","86"); hScore.put("李二","98"); hScore.put("海飞","99"); System.out.println("Hashtable:" + hScore.toString());

for (Enumeration e = hScore.keys() ; e.hasMoreElements() ;) { String str = (String)e.nextElement(); System.out.print(str + ":"); System.out.println(hScore.get(str)); }

图3.8 使用Enumeration接口枚举输出

3.3.4 Date类 Date类用来指定日期和时间,其构造函数及常用方法如下: public Date() 从当前时间构造日期时间对象。 public String toString() 转换成字符串。 public long getTime() 返回自新世纪以来的毫秒数,可以用于时间计算。

【例3.10】测试执行循环花费的时间(数量级为毫秒),具体时间情况如图3.9所示。源程序代码如下: //程序文件名为UseDate.java import java.util.Date; public class UseDate { public static void main(String[] args) Date dOld = new Date(); long lOld = dOld.getTime(); System.out.println("循环前系统时间为:" +dOld.toString());

int sum = 0; for (int i=0; i<100; i++) { sum += i; } Date dNew = new Date(); long lNew = dNew.getTime(); System.out.println("循环后系统时间为:" +dNew.toString()); System.out.println("循环花费的毫秒数为:" + (lNew - lOld));

图3.9 循环时间测试

3.3.5 String类 String类用于操作非数值型字符串,它提供了七类方法操作,分别为字符串创建、字符串长度、字符串比较、字符串检索、字符串截取、字符串运算和数据类型转换。 1. 字符串创建 public String() 构造一个空字符串。 public String(char[] value) 使用字符数组value中的字符以构造一个字符串。 public String(String original) 使用原字符串original的拷贝以构造一个新字符串。

2. 字符串长度 public int length() 返回字符串的长度。 3. 字符串比较 public boolean equals(Object anObject) 比较字符串是否与anObject代表的字符串相同(区分大小写)。 public boolean equalsIgnoreCase(String anotherString) 比较字符串是否与anotherString相同(不区分大小写)。

4. 字符串检索 public int indexOf(String str) 返回一个字符串中str第一次出现所在的位置。 public int indexOf(String str, int fromIndex) 返回从fromIndex开始字符串str出现所在的位置。

5. 字符串截取 public String substring(int beginIndex, int endIndex) 返回benginIndex到endIndex之间的字符串。 6. 字符串运算 运算符为“+”,表示连接运算。下面的行语句输出连接的字符串。 System.out.println("Hashtable:" + hScore.toString());

【例3.11】操作字符串,输出结果如图3.10所示。源程序代码如下: //程序文件名为TestString.java public class TestString { public static void main(String[] args) String str = new String("The substring begins at the specified beginIndex."); String str1 = new String("string"); String str2 = new String(); int size = str.length();//字符串长度

int flag = str.indexOf("substring"); str2 = str.substring(flag,flag + 9);//取子字符串 System.out.println("字符串" + str + "\n总长度为:" + size); if(str1.equals(str2))//判断是否相等 System.out.println("截取的字符串为:" + str1); else System.out.println("截取的字符串为:" + str2); }

图3.10 操作字符串的输出

7. 数据类型转换 各种原始数据类型与String类型之间可以通过方法相互转换。 valueOf()系列的静态方法用于从其它对象(原始数据类型对象)转换成字符串。例如: public static String valueOf(Boolean b) public static String valueOf(char c) public static String valueOf(int i) public static String valueOf(long l) public static String valueOf(float f) public static String valueOf(double d)

具体实例如下: (1) 从int转换到String。 例如: int intvar = 1; String strvar; strvar = String.valueOf (intvar); //"1"

(2) 从float转换到String。 例如: float fltvar = 9.99f; String strvar; strvar = String.valueOf(fltvar); //"9.99"

(3) 从double转换到String。 例如: double dblvar = 99999999.99; String strvar; strvar = String.valueOf (dblvar); //"9.999999999E7"

(4) 从char转换到String。 例如: char chrvar = 'a'; String strvar; strvar = String.valueOf (chrvar); //"a"

(5) 从String转换到int、float、long、double。 例如: String intstr = "10"; String fltstr = "10.1f"; String longstr = "99999999"; String dblstr = "99999999.9"; int i = Integer.parseInt(intstr); //10 float f = Float.parseFloat(fltstr); //10.1 long lo = Long.parseLong(longstr); //99999999 double d = Double.parseDouble(dblstr); //9.99999999E7

(6) 从String转换到byte、short。 例如: String str = "0"; byte b = Byte.parseByte(str); //0 short sh = Short.parseShort(str);//0

(7) 从String转换到char。 例如: String str = "abc"; char a = str.charAt(0);//返回字符a

(8) 从String转换到boolean。 例如: String str = "true"; Boolean flag = Boolean.valueOf(str);//true

3.3.6 StringBuffer类 StringBuffer类提供了一个字符串的可变序列,类似于String类,但它对存储的字符序列可以任意修改,使用起来比String类灵活得多。它常用的构造函数为: StringBuffer() 构造一个空StringBuffer对象,初始容量为16个字符。 StringBuffer(String str) 构造一个StringBuffer对象,初始内容为字符串str的拷贝。 对于StringBuffer类,除了String类中常用的像长度、字符串截取、字符串检索的方法可以使用之外,还有两个较为方便的方法系列,即append方法系列和insert方法系列。

(1) append方法系列根据参数的数据类型在StringBuffer对象的末尾直接进行数据添加。 public StringBuffer append(boolean b) public StringBuffer append(char c) public StringBuffer append(char[] str) public StringBuffer append(char[] str, int offset, int len) public StringBuffer append(double d) public StringBuffer append(float f) public StringBuffer append(int i) public StringBuffer append(long l) public StringBuffer append(Object obj) public StringBuffer append(String str) public StringBuffer append(StringBuffer sb)

(2) insert方法系列根据参数的数据类型在StringBuffer的offset位置进行数据插入。 public StringBuffer insert(int offset, boolean b) public StringBuffer insert(int offset, char c) public StringBuffer insert(int offset, char[] str) public StringBuffer insert(int index, char[] str, int offset, int len) public StringBuffer insert(int offset, double d) public StringBuffer insert(int offset, float f) public StringBuffer insert(int offset, int i) public StringBuffer insert(int offset, long l) public StringBuffer insert(int offset, Object obj) public StringBuffer insert(int offset, String str)

(3) 下面这个方法用于将stringbuffer对象的数据转换成字符串: public String toString()

【例3.12】基于例3.11进行修改,使用StringBuffer对象得到如图3.10所示的输出界面。 //程序文件名为TestString.java public class TestString { public static void main(String[] args) StringBuffer str = new StringBuffer("The substring begins at the specified beginIndex."); StringBuffer str1 = new StringBuffer("string"); String str2 = new String(); int size = str.length(); int flag = str.indexOf("substring");

str2 = str.substring(flag,flag + 9); StringBuffer strOut = new StringBuffer("字符串"); strOut.append(str); strOut.append("总长度为:"); strOut.append(size); int f = strOut.indexOf("总"); strOut.insert(f,'\n'); System.out.println(strOut.toString()); if(str1.toString().equals(str2)) System.out.println("截取的字符串为:" + str1.toString()); else System.out.println("截取的字符串为:" + str2); }

3.3.7 StringTokenizer类 StringTokenizer类是一个实现Enumeration接口的类,它使得应用程序可以将字符串分成多个记号,默认情况下以空格为分隔符,例如将字符串“this is a test”分成四个单词记号。用户也可以指定分隔符。分隔符为false,分割字符串;分隔符为true,则将分隔符自身作为分割后的字符串的一部分。其构造函数和常用方法如下: StringTokenizer(String str) 以字符串str构建StringTokenizer对象。

StringTokenizer(String str, String delim) 使用delim分隔符,以初始字符串str构建StringTokenizer对象。 int countTokens() 返回识别的总记号数。 boolean hasMoreTokens() 测试是否还有识别的记号。 boolean nextToken(String delim) 返回字符串delim分隔的下一个记号。 String nextToken() 返回下一个识别的记号。

【例3.13】使用StringTokenizer类分割字符串,字符串的分割情况如图3.11所示。源程序代码如下: import java.util.*; public class UseToken { public static void main(String[] args) String str = "数学::英语::语文::化学"; StringTokenizer st = new StringTokenizer(str,"::"); System.out.println(str + "\n课程数为:" +st.countTokens());

while (st.hasMoreTokens()) { System.out.println(st.nextToken("::")); } str = "Hello this is a test"; st = new StringTokenizer(str); System.out.println(str + "\n单词数为:" +st.countTokens()); System.out.println(st.nextToken());

图3.11 StringTokenizer分割单词的结果输出

习 题 1. 定义一个类Student,属性为学号、姓名和成绩;方法为增加记录SetRecord和得到记录GetRecord。SetRecord给出学号、姓名和成绩的赋值,GetRecord通过学号得到考生的成绩。 2. 给出上题中设计类的构造函数,要求初始化一条记录(学号、姓名、成绩)。 3. 假设一个学生所选课程为语文、数学、英语、政治、物理、化学,给出此学生所选课程的Vector列表,并访问物理存放在Vector中的位置。

4. 对于图3.12中的Shebei表,将设备编码和设备名称作为Hashtable中的键和值进行存储,然后访问此Hashtable,找到键0008和0016并输出设备编码为0008、0016的设备名称。 图3.12 习题3.4中的Shebei表

5. 编写程序,测试字符串“你好,欢迎来到Java世界”的长度,将字符串的长度转换成字符串进行输出,并对其中的“Java”四个字母进行截取,输出截取字母以及它在字符串中的位置。 6. 对于图3.12中的Shebei表,将设备编码和设备名称用 :: 进行连接,形成新的字符串,存储到Vector向量对象中,如0001::打印机、0008::书桌,然后访问Vector对象中设备编码为0010、0014的项,使用字符串操作的方法读取这两项设备编码对应的设备名称并输出。

7. 编写程序,测试1~50的阶乘所耗费的毫秒级时间。 8. 编写程序,将设备编码对应设备名称的记录添加到一个StringBuffer对象中,编码和名称之间用::隔开,每条记录之间用**隔开。 9. 编写程序,将字符串“打印机*钟表//自行车**雨伞%%收音机??电脑”进行拆分,输出每个设备的名字。

第4章 Java Applet 4.1 Applet简介 4.2 显示Applet 4.3 载入图片 4.4 载入声音 4.3 载入图片 4.4 载入声音 4.5 Applet控制浏览器环境 4.6 服务器下配置Applet文件 4.7 使用插件载入Applet 4.8 JAR文件 4.9 Applet和应用程序 习 题

4.1 Applet 简 介 4.1.1 Applet的定义 Applet是Java语言编写的,无法独立运行,但可以嵌入到网页中执行。它扩展了传统的编程结构和方法,可以通过互联网发布到任何具有Java编译环境浏览器的个体计算机上。

4.1.2 Applet的用途 用户可以静态显示Applet,像显示一幅图片或者一段文本一样;Applet也可以是一个动态交互过程,用户输入简单的数据,就会产生相应的响应。

4.1.3 Applet的编写格式 编写Applet时,首先要引入java.applet包中的类,这个类里包含三个接口和Applet的类: import java.applet.*; import java.applet.Applet; 类头定义为: public class MyApplet extends Applet; 用来声明自定义类是从Applet类扩展而来的。

类体中没有应用程序中必须具备的main方法,取而代之的是下面几个常用方法: public void init(); 初始化——在这个方法中设置一些变量的初始化,像界面布局设置等。 public void start() 启动——Applet开始执行。 public void stop() 停止——Applet停止执行。 public void destroy() 撤消——销毁Applet。

【例4.1】编写Applet,显示系统的当前时间。源程序代码如下: //程序文件名UseApplet.java import java.awt.*; import java.applet.Applet; import java.util.Date; public class UseApplet extends Applet { String strTime = new String(); public void init() }

public void start() { Date d = new Date(); strTime = d.toString(); repaint(); } public void paint(Graphics g) g.drawString("当前时间为:" + strTime,20,30); };

4.2 显 示 Applet 4.2.1 工作流程 Applet是嵌入在HTML网页中显示的。首先从服务器请求HTML网页,检测到Applet的类请求时,将所需类下载到本地,然后运行,其工作流程如图4.1所示。 图4.1 客户端显示Applet

HTML页面中引用Applet的标签为<applet></applet>,浏览器中执行Applet的步骤如下: (3) 浏览器获取该类文件。 (4) 字节码验证这个类是否合法。 (5) 如果合法就开始执行类文件。

有时可能需要载入多个类文件,直到将所有所需的类文件下载完毕为止。 为上面的UseApplet.class类写一个最简单的网页UseApplet.html: <html> <body> <applet code = "UseApplet.class" height = 200 width = 300> </applet> </body> </html>

图4.2 Applet输出时间

图4.3 网页下显示输出时间的Applet

4.2.2 参数设置 在HTML页面中嵌入Applet类文件时,可以在<applet>标签中设置属性以控制Applet类文件的外观显示,也可以传递一些用户自定义属性。嵌入的格式为: <applet attributes1> <param attributes2> ... </applet>

其中: (1) <applet></applet>标签内为Applet的信息。 (2) <param>标记在<applet></applet>之间进行设置,然后由Applet自带的方法获取。 (3) <param>标记有两个自己的属性:name和value。例如: <param name ="aaa"value = "bbb"> (4) attribute1和attribute2的属性设置如表4.1所示。

表4.1 属性设置及其描述

其中,code属性是必须的,height和width属性用来设置高度和宽度,如果都为0,那么Applet将隐藏。 对于例4.1中UseApplet.html,如果有 <applet code = "UseApplet.class" height = 200 width = 300> </applet> 那么说明网页加载的类名为UseApplet.class,类显示的高度为200像素点,宽度为300像素点。 表4.1中列出的attibute1属性为<applet>定义的属性。用户还可以根据Applet程序的需要,传递一些程序自身属性,这些属性通过attribute2中name属性给出所需参数的名,value属性给出所需参数的值。 public String getParameter(String name)

【例4. 2】基于例4. 1,在加载类的网页上设置一个用户名,使得Applet输出为“XXX,你好,当前时间为:(具体时间)”,如图4 //程序文件名UsePara.java import java.awt.*; import java.applet.Applet; import java.util.Date; public class UsePara extends Applet { String strTime = new String(); String strUser = new String(); public void init()

//得到网页的参数:用户名 strUser = getParameter("USER"); } public void start() { Date d = new Date(); strTime = d.toString(); repaint(); public void paint(Graphics g) g.setColor(Color.red); g.drawString(strUser + " 你好,当前时间为:" + strTime,20,30); };

在UseApplet.html中添加一个用户参数设置: <param name = "USER" value = 王飞> 使修改后的网页程序如下: <html> <body> <applet code = "UsePara.class" height = 200 width = 400> </applet> </body> </html>

图4.4 网页设置参数后的Applet输出

放在浏览器中查看时,显示效果如图4.5所示。 图4.5 浏览器中查看获取网页参数的Applet输出

4.2.3 生命周期 Applet的生命周期是指Applet存在的时间,即某些方法还在被调用的时间。根据Applet的四个方法init()、start()、stop()、destroy(),可以得出Applet的生命周期,如图4.6所示。 图4.6 Applet的生命周期

4.3 载 入 图 片 在java.applet包中存在一个接口AppletStub。当一个Applet创建之后,一个Applet Stub使用setStub方法附加到Applet上,这个Stub用来充当Applet和浏览器环境之间的接口。在这个接口中一个重要的方法: public URL getDocumentBase() 该方法返回的是Applet类文件所在网页的网络路径。 例如,如果一个Applet类文件嵌入在下面网页中 http://java.sun.com/products/jdk/1.2/index.html 那么返回的路径为 http://java.sun.com/products/jdk/1.2/

通过这个网络路径可以得到图片,从而由Applet类载入,载入图片的方法为: public Image getImage(URL url, String name) 其中: (1) url给出图片所在的网路路径。 (2) name给出图片的名字。 例如:url路径可以为http://java.sun.com/products/jdk/1.2/,而name可以为路径下的图片index_01.gif。

【例4.3】编写Applet,载入Applet类文件所在路径下的图片index_01.gif,然后如图4.7所示显示图片。源程序代码如下: //程序文件名UseImage.java import java.awt.*; import java.applet.Applet; import java.net.*; public class UseImage extends Applet { //定义图像对象 Image testImage; public void init()

//得到图片 testImage = getImage(getDocumentBase(),"index_01.gif"); } public void paint(Graphics g) { g.drawImage(testImage,0,0,this); 载入UseImage.class类的UseImage.html文件如下: <html> <body> <applet code = "UseImage.class" height = 200 width = 300> </applet> </body> </html>

图4.7 Applet载入图片显示

4.4 载 入 声 音 Applet类提供一个用于载入声音的方法,即 4.4 载 入 声 音 Applet类提供一个用于载入声音的方法,即 public AudioClip getAudioClip(URL url, String name) 该方法返回由url和name决定的AudioClip对象。 其中: (1) url:音频剪辑的绝对URL地址; (2) name:相对于上面的url,为音频剪辑的相对地址,通常指名字。

【例4.4】编写载入声音的Applet。源程序代码如下: //程序文件名UseAudio.java import java.awt.*; import java.applet.Applet; public class UseAudio extends Applet { public void init() } public void start() //播放Applet所在目录下的tiger.au声音文件 getAudioClip(getDocumentBase(),"tiger.au").play();

} }; 载入类的HTML文件如下: <html> <body> <applet code = "UseAudio.class" height = 200 width = 300> </applet </body> </html> 在浏览器加载或者appletviewer命令启动时可以听见老虎的叫声,但必须保证tiger.au在UseAudio.class类文件所在的目录。

4.5 Applet控制浏览器环境 java.applet包中提供一个接口AppletContext,对应着Applet的环境,主要指包含Applet的网页文档等,在这个接口内有两个重要方法: pubilc void showDocument(URL url) 浏览器下载Applet时,showDocument可以将当前Applet页面用于显示url网址的内容。 url 给出页面的绝对网络路径。 public void showDocument(URL url, String target) target可以表明url显示的位置,有四种情况,如表4.2所示。

表4.2 target 取 值 public void showStatus(String status) 字符串status显示在状态栏中,告知用户当前类文件运行中的状态。

【例4.5】编写Applet,在状态栏显示鼠标单击Applet的次数,结果如图4.8所示。源程序代码如下: //程序文件名ShowStatus.java import java.applet.*; import java.applet.Applet; import java.awt.event.*; public class ShowStatus extends Applet { int count = 0; public void init()

{ } public boolean mouseClicked(MouseEvent e) count ++; getAppletContext().showStatus("你好,你已经点击了" + count + "次了!"); return true; };

图4.8 状态栏显示单击次数信息

【例4. 6】编写Applet,在一个框架中显示不同来源的网页信息,如图4 //程序文件名ShowHtml.java import java.applet.*; import java.applet.Applet; import java.net.URL; public class ShowHtml extends Applet { public void init() }

public void start() { try //创建URL对象 URL urlName=new URL("http://www.xjtu.edu.cn"); //在左框架显示网页 getAppletContext().showDocument(urlName,"left"); urlName = new URL("http://www.sina.com.cn"); //右框架显示网页 getAppletContext().showDocument(urlName,"right"); }

catch(MalformedURLException e) { System.out.println(e.getMessage()); } 载入Applet的网页Head.html的代码如下: <html> <body> <applet code = "ShowHtml.class" height = 0 width = 0> </applet>

<h3>这是一个框架网页,上面的框架隐藏载入applet类文件,由applet控制左框架显示西安交通大学的主页,右框架显示新浪网的主页。 </body> </html> 装配的框架网页ShowHtml.html的源代码如下,可以看见其中框架名字左边的为left而右边的为right。 <html> <frameset rows="21%,*">

<frame name="guid" src="Head.html"> <frameset cols="50%,*" frameborder="YES" border="1" framespacing="1"> <frame name="left" src=""> <frame name="right" src=""> </frameset> </html>

图4.9 框架网页显示

4.6 服务器下配置Applet文件 由于Applet文件是客户端浏览器从服务器端下载的HTML网页,所以将文件配置到服务器端,由客户进行访问。 本机中使用的服务器为Tomcat 4.0,安装成功后重启动,则服务器开始运转,在浏览器的网址栏键入http://192.100.100.43:8080/index.html,如果出现如图4.10所示的Tomcat主网页,则证明服务器测试正常。

图4.10 Tomcat主页

配置自己的文件时,推荐在安装目录D:\Apache Tomcat 4 配置自己的文件时,推荐在安装目录D:\Apache Tomcat 4.0\webapps\ROOT下建立自己的文件夹,这样有利于管理。本书作者在此文件夹下建立user目录,以为载入图片的Applet为例,将UseImage.html、UseImage.class和Image_01.gif拷贝到user目录下,并在IE浏览器的地址栏键入网址:http://192.100.100.43:8080/user/UseImage.html,浏览器显示结果如图4.11所示,与前面例4.3中图4.7载入的图片效果一致,但可以看出地址栏的网址不同。

图4.11 配置到服务器端的网页显示

4.7 使用插件载入Applet Java插件(Plug-in)扩展了网络浏览器的功能,使得无论在哪个浏览器(IE浏览器或者Netscape浏览器)下,Java Applet可在Sun的Java运行环境(JRE)下运行,而不是在浏览器自带的JRE环境下运行。Java插件是Sun的JRE环境的一部分,当安装JRE时,插件自动安装。当你安装J2sdk-1.4.0_01时,JRE环境版本号也为1.4.0_01。使用插件最大的不同是将IE浏览器中网页原有的<applet>标签改成了<object>标签,在Netscape中则改成<embed>,这里只讨论IE浏览器中的使用。

J2sdk1.4提供了一个叫做HtmlConverter的工具,用于将包含普通<applet>标签的HTML文件转换成包含对象的文件。在命令行提示符键入命令HtmlConverter后按回车键,出现如图4.12所示对话框。 其中: (1) “指定文件”为要转换的文件。 (2) “模板文件”为操作系统和浏览器适用类型,操作系统有Windows和Solaris,浏览器分为IE和Netscape。本书选择适用于Windows和Solaris的IE浏览器。

(3) 在“适用于小应用程序的Java版本”栏中选中第一项“只适用于Java1. 4 (3) 在“适用于小应用程序的Java版本”栏中选中第一项“只适用于Java1.4.0_01”,这是因为作者使用的JDK安装版本为Java1.4.0_01。 将这几项进行设置之后,单击“转换”按钮,则将原有的UseImage.html文件内容转换为: <html> <body> <!--"CONVERTED_APPLET"--> <!-- HTML CONVERTER --> <OBJECT

classid="clsid:CAFEEFAC-0014-0000-0001-ABCDEFFEDCBA" WIDTH = 300 HEIGHT = 200 codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_01-win.cab#Version=1,4,0,10"> <PARAM NAME = CODE VALUE = "UseImage.class" > <PARAM NAME = ARCHIVE VALUE = "UseImage.jar" > <PARAM NAME="type" VALUE="application/x-java-applet;jpi-version=1.4.0_01">

<PARAM NAME="scriptable" VALUE="false"> </OBJECT> <!-- <APPLET CODE = "UseImage.class" ARCHIVE = "UseImage.jar" WIDTH = 300 HEIGHT = 200> </APPLET> --> <!--"END_CONVERTED_APPLET"--> </body> </html>

图4.12 HtmlConverter工具界面

其中codebase="http://java. sun 其中codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_01-win.cab#Version=1,4, 0, 10">表示如果客户端浏览器不存在此插件,可以从codebase指定的网址下载,由上述语句行可以看出HtmlConverter生成的插件文件的插件下载地址为Sun公司的网站。如果本机上放置了插件的安装程序,那么此处可以改为从本机下载,以加快下载速度。如果在网站上发布你的Applet的网页,建议使用插件方式载入Applet,可以与多种浏览器兼容。

4.8 JAR 文 件 4.8.1 操作JAR文件 在JDK的安装目录的bin子目录下有一个jar.exe文件,这就是JAR文件的操作工具,用它及一系列选项来实现对JAR文件的操作。jar命令的格式如下: jar {ctxu}[vfm0M] [jar-文件] [manifest-文件] [-C 目录] 文件名 ...

其中: (1) ctxu四者必选其一,各选项的含义如下: -c 创建新的存档; -t 列出存档内容的列表; -x 展开存档中命名的(或所有的)文件; -u 更新已存在的存档。

(2) vfm0M为可选项,各选项的含义如下: -0 只存储方式,未用ZIP压缩格式; -M 不产生所有项的清单(manifest)文件; -C 改变到指定的目录,并且包含下列文件。

(3) 清单(manifest)文件名和存档文件名都需要指定,指定的顺序依照命令行中“m”和“f”标志的顺序。 例如: ① 将两个class文件存档到一个名为“classes.jar”的存档文件中: jar cvf classes.jar Foo.class Bar.class ② 用一个存在的清单(manifest)文件“mymanifest”将foo/目录下的所有文件存档到一个名为“classes.jar”的存档文件中: jar cvfm classes.jar mymanifest -C foo/ . 对JAR文件常用的操作有三种:创建JAR文件、列出JAR文件和抽取JAR文件。

1. 创建JAR文件 jar cvf UseImage.jar UseImage.class index_01.gif 当用JAR工具创建新的档案时,自动增加一个声明文件到文档中。如果用户需要创建自己的声明文件时,则指定m选项。可以看到本目录下多了一个UseImage.jar文件。创建JAR文件的过程如图4.13所示。 图4.13 创建JAR文件

2. 列出JAR文件的内容 jar tvf UseImage.jar 执行命令后列出JAR文件中的内容,如图4.14所示。 图4.14 列出JAR文件

3. 抽取JAR文件 jar xvf UseImage.jar 抽取JAR文件是将JAR中包含的类以及相关文件逐一恢复。在E:\_Work\Java\sample目录下建立JAR文件夹,将JAR文件放入此文件夹,然后进行抽取,可以看见JAR目录下除了UseImage.class和index_01.gif,还有META-INF子目录,下面有一个文件MANIFEST.MF。图4.15给出抽取JAR文件的过程。

图4.15 抽取JAR文件

4.8.2 客户端使用JAR文件 <applet>标签中添加一个属性名字为archive,它的值为要载入的.jar文件。例如archive="UseImage.jar"。 例如,将D:\Apache Tomcat 4.0\webapps\ROOT\user\UseImage.html文件代码改为: <html> <body> <applet code = "UseImage.class" archive="UseImage.jar" height = 200 width = 300> </applet> </body> </html>

4.9 Applet和应用程序 【例4.7】修改例4.1的Applet,使得它可以从命令提示符状态下访问。 (1) 基于例4.1的UseApplet添加一个main方法如下: public static void main(String[] args) { //创建一个框架 Frame f = new Frame("时间"); //关闭窗口时退出系统 f.addWindowListener(new WindowAdapter()

public void windowClosing(WindowEvent evt) { System.exit(0); } }); //创建一个AppletApp对象 AppletApp a = new AppletApp(); //将对象载入框架 f.add("Center",a); //设置框架大小 f.setSize(300,200); //显示框架

f.show(); //初始化对象 a.init(); //启动对象 a.start(); }

(2) 修改后的源程序代码如下: //程序文件名AppletApp.java import java.awt.*; import java.awt.event.*; import java.applet.Applet; import java.util.Date; public class AppletApp extends Applet { String strTime = new String(); public void init() }

public void start() { Date d = new Date(); strTime = d.toString(); repaint(); } public void paint(Graphics g) g.drawString("当前时间为:" + strTime,20,30); public static void main(String[] args)

//创建一个框架 Frame f = new Frame("时间"); //关闭窗口时退出系统 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) System.exit(0); } }); //创建一个AppletApp对象 AppletApp a = new AppletApp();

a.init(); //将对象载入框架 f.add("Center",a); f.setSize(200,400); f.show(); a.start(); } };

(3) 编译通过并生成UseApplet. class类后,在命令提示符状态下键入“java UseApplet”,得到如图4

【例4.8】修改例4.2,在命令提示符状态下输入用户名参数,使得可以在命令提示符状态下进行访问。 (1) 从命令行状态输入用户名参数,应用程序的读取如下: if (args.length < 1) { System.out.println("缺少用户参数"); System.exit(0); } else strUser = new String(args[0]);

(2) 添加一个变量 static boolean inApplet = true; 用于控制取参数的方式,如果以应用程序调用,则从命令行取参数;如果是载入Applet,则从网页中取参数。

(3) 源程序代码如下: //程序文件名AppPara.java import java.awt.*; import java.awt.event.*; import java.applet.Applet; import java.util.Date; public class AppPara extends Applet { String strTime = new String(); static String strUser = new String(); static boolean inApplet = true; public void init()

//如果从Applet载入,从网页得到参数 if(inApplet) strUser = getParameter("USER"); } public void start() { Date d = new Date(); strTime = d.toString(); repaint(); public void paint(Graphics g)

g.setColor(Color.red); g.drawString(strUser + " 你好,当前时间为:" + strTime,20,30); } public static void main(String[] args) { inApplet = false; //如果从命令行提示符状态进入,获取参数 if (args.length < 1) System.out.println("缺少用户参数"); System.exit(0); else

strUser = new String(args[0]); //创建一个框架 Frame f = new Frame("时间"); //关闭窗口时退出系统 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) System.exit(0); } }); //创建一个AppletApp对象 AppPara a = new AppPara();

//初始化 a.init(); //将对象载入框架 f.add("Center",a); //设置框架大小 f.setSize(400,200); //显示框架 f.show(); //启动对象 a.start(); }

(4) 在命令提示符状态键入命令“java AppPara 王飞”后按回车键,弹出如图4.5所示的界面。

习 题 1. 编写Applet,载入图片的同时响起音乐。 习 题 1. 编写Applet,载入图片的同时响起音乐。 2. 将上题的Applet类、图片文件、音乐文件进行压缩并生成JAR文件,然后载入运行。

3. 编写程序,既可用作Applet,又可用作应用程序,在出现的界面中显示下面的内容,括号内表示显示的颜色。 特点:(蓝色显示) (1) 跨平台性(以下均为红色显示) (2) 面向对象 (3) 安全性 (4) 多线程 (5) 简单易用

4. 在上题的基础上,输入参数“Java语言”,使得显示内容如下所示,要求既可以从命令行输入,也可以从网页内输入。 (1) 跨平台性 (2) 面向对象 (3) 安全性 (4) 多线程 (5) 简单易用

第5章 Java图形处理 5.1 Java图形 5.2 Paint方法、Update方法和Repaint方法 5.3 Graphics类 5.4 Color类 5.5 Graphics2D类 习 题

5.1 Java图形 抽象窗口化工具(AWT)为图形用户界面编程提供API编程接口,使得Java可以提供较好的图形用户界面。AWT把图形处理分为两个层次:一是处理原始图形,这一层较原始,图形直接以点、线和面的形式画到界面上;二是提供大量组件,实现可定制的图形用户界面。本章主要讨论如何在界面上画图形及所画图形的特征。Java编程语言中的图形坐标系统不同于数学中的坐标系,屏幕左上角为(0,0),右下角为屏幕水平向右和垂直向下增长的像素数。

5.2 Paint方法、Update方法和Repaint方法 public void paint(Graphics g) 以画布为参数,在画布上执行画图方法。在Applet中,不显式地调用paint方法。 2. Repaint方法 Applet重画时系统自动调用paint方法。 3. Update方法 public void update(Graphics g) 更新容器,向Repaint发出刷新小应用程序的信号,缺省的Update方法清除Applet画图区并调用Paint方法。

5.3 Graphics类 Graphics类是所有图形上下文的抽象基类,允许应用程序在各种设备上实现组件的画图。图形对象封装了Java支持的基本渲染操作的状态信息,包括画图的组件对象、渲染区域的坐标(coordinates)、区域(clip)、颜色(color)、字体(font)、画图模式等。Graphics类提供画各种图形的方法,其中包括线、圆和椭圆、矩形和多边形、图像以及各种字体的文本等。这些方法具体如下: public abstract void clipRect(int x, int y, int width, int height) 指定的区域切分。

public abstract void drawLine(int x1, int y1, int x2, int y2) 使用当前颜色,在点(x1, y1) 和 (x2, y2) 之间画线。 public abstract void drawOval(int x, int y, int width, int height) 画椭圆。 public abstract void fillOval(int x, int y, int width, int height) 画实心椭圆。 public abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) 画x和y坐标定义的多边形。

public void drawRect(int x, int y, int width, int height) 画矩形。 画实心矩形。 public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) 使用当前颜色画圆角矩形。 public abstract void drawString(String str, int x, int y) 使用当前字体和颜色画字符串str。

public abstract void setColor(Color c) 设置图形上下文的当前颜色。 public abstract void setPaintMode() 设置画模式。 public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer) 画特定图。 public abstract void setFont(Font font) 设置特定的font字体。使用时首先得到font对象的一个实例,Font类常用构造函数为: public Font(String name, int style, int size)

通过指定的name、style和size创建字体实例。name指字体名,像“隶书”、“TimesRoman”等,字体风格为粗体、斜体,size指字号大小。 例如: Font f = new Font("TimesRoman",Font.BOLD + Font.ITALIC, 12); 创建了具有粗斜体风格的12磅的TimesRoman字体。

【例5.1】设置Graphics对象画图,显示结果如图5.1所示。源程序代码如下: //程序文件名SimpleGUI.java import java.awt.*; import java.applet.*; public class SimpleGUI extends Applet { Image samImage; public void init() samImage=getImage(getDocumentBase(),"sample.gif");

} public void paint(Graphics g) { //g.clipRect(50,50,180,180); //画线 g.drawLine(0,0,20,30); //输出字符串 g.drawString("图形显示",100,30); //设置颜色 Color c = new Color(255,200,0); g.setColor(c); //设置字体

Font f = new Font("TimesRoman",Font.BOLD+ Font.ITALIC,24); g.setFont(f); g.drawString("图形显示",180,30); g.drawLine(20,20,100,50); g.drawLine(20,20,50,100); //矩形 g.drawRect(40,40,40,40); g.fillRect(60,60,40,40); g.setColor(Color.red); //3D矩形 g.draw3DRect(80,80,40,40,true);

g.draw3DRect(100,100,40,40,false); g.fill3DRect(120,120,40,40,true); //椭圆 g.drawOval(150,150,30,40); g.fillOval(170,170,20,20); g.setColor(Color.blue); //圆角矩形 g.drawRoundRect(180,180,40,40,20,20); g.fillRoundRect(200,200,40,40,20,20);

//多边形 int xC[] = {242,260,254,297,242}; int yC[] = {240,243,290,300,270}; g.drawPolygon(xC,yC,5); //图片 g.drawImage(samImage,250,50,this); }

图5.1 简单的图形界面

将例5.1注释的程序语句 // g.clipRect(50,50,180,180); 的注释符号去掉,重新编译执行,可以看见如图5.2所示的结果。 图5.2 裁剪后的图形界面

5.4 Color类 Color类是用来封装颜色的,在上面的例子中多次用到。使用Color对象较为简单的方法是直接使用Color类提供的预定义的颜色,像红色Color.red、橙色Color.orange等;也可以使用RGB颜色模式进行定义。所谓RGB颜色模式是指使用三种基色:红、绿、蓝,通过三种颜色的调整得出其它各种颜色,这三种基色的值范围为0~255。例如Color c = new Color(255,200,0);定义橙色。表5.1给出常用颜色的RGB值以及对应的类预定义参数。

表5.1 常用颜色的RGB值以及对应的类预定义参数

Color还有一个构造函数,它构造的Color对象用于是否透明显示颜色。 public Color(int red, int green, int blue, int alpha) 其中:前三个分量即RGB颜色模式中的参数,第四个alpha分量指透明的程度。当alpha分量为255时,表示完全不透明,正常显示;当alpha分量为0时,表示完全透明,前三个分量不起作用,而介于0~255之间的值可以制造出颜色不同的层次效果。

【例5.2】测试Color对象,界面如图5.3所示。源程序代码如下: //程序文件名UseColor.java import java.awt.*; import java.applet.*; import java.awt.geom.*; public class UseColor extends Applet { public void paint(Graphics oldg) Graphics2D g = (Graphics2D)oldg;

g.setColor(Color.blue); g.fill(new Ellipse2D.Float(50,50,150,150)); g.setColor(new Color(255,0,0,0)); g.fill(new Ellipse2D.Float(50,50,140,140)); g.setColor(new Color(255,0,0,64)); g.fill(new Ellipse2D.Float(50,50,130,130)); g.setColor(new Color(255,0,0,128)); g.fill(new Ellipse2D.Float(50,50,110,110)); g.setColor(new Color(255,0,0,255)); g.fill(new Ellipse2D.Float(50,50,90,90)); g.setColor(new Color(255,200,0)); g.fill(new Ellipse2D.Float(50,50,70,70)); }

图5.3 颜色测试界面

5.5 Graphics2D类 Graphics2D类继承于Graphics类,提供几何学、坐标变换、颜色管理以及文本排列等的更高级控制。Graphics2D类是Java平台上渲染二维图形、文字、以及图片的基础类,提供较好的对绘制形状、填充形状、旋转形状、绘制文本、绘制图像以及定义颜色的支持。 在AWT编程接口中,用户通过Paint方法接收Graphics对象作为参数,若是使用Graphics2D类,就需要在Paint方法中进行强制转换。 Public void paint(Graphics old) { Graphics2D new = (Graphics2D)old; }

5.5.1 绘制形状 Graphics2D提供以下两个方法进行形状的绘制: public abstract void draw(Shape s) 根据Graphics2D的环境设置画出形状s,其中Shape接口包含的类如表5.2所示。 public abstract void fill(Shape s) 画实心形状s。

表5.2 Graphics2D绘制的图形类

其中GeneralPath是一般的几何路径,它的构造函数为: public GeneralPath() 构造一个空的对象。 常用的方法有四个,分别如下: public void lineTo(float x, float y) 从当前坐标点到(x,y)坐标点画一条直线,将此点添加到路径上。 public void moveTo(float x, float y) 移动到坐标点(x,y),在路径上添加此点。

public void quadTo(float x1, float y1, float x2, float y2) 以坐标点(x1,y1)为控制点,在当前坐标点和坐标点(x2,y2)之间插入二次曲线片断。 public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) 以(x1,y1)和(x2,y2)为控制点,在当前坐标点和(x3,y3)之间插入曲线片断。

在Draw方法中提到Graphics2D的环境设置。所谓的环境设置是指设置画图的笔画和填充属性等,设置方法分别如下: public abstract void setStroke(Stroke s) 设置笔画的粗细。其中Stroke接口中常用BasicStroke类来实现,一个较简单的构造函数为 public BasicStroke(float width) 创建实线笔画宽度为width。 public abstract void setPaint(Paint paint)

设置Graphics2D环境的填充属性。其中,paint的值可以为渐变填充类java. awt 设置Graphics2D环境的填充属性。其中,paint的值可以为渐变填充类java.awt.GradientPaint,也可以为图形填充类java.awt.TexturePaint,后者将在5.5.3节绘制图像中讲到。渐变填充类常用构造函数为 public GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2, boolean cyclic) 构建一个渐变GradientPaint对象,在起始坐标点到目标坐标点之间从颜色color1到color2渐变,cyclic为真,循环渐变。

【例5.3】演示了几何形状、笔画变换以及颜色渐变显示。其中直线的笔画宽度为10,其它笔画宽度为5,中间三个图形实现绿色到蓝色的循环渐变,后三个图形实现红色到黄色的循环渐变,如图5.4所示。 //程序文件名GUI2D.java import java.awt.*; import java.applet.*; import java.awt.geom.*; public class GUI2D extends Applet {

public void paint(Graphics oldg) { Graphics2D g = (Graphics2D)oldg; //设置笔画宽度 BasicStroke stroke = new BasicStroke(10); g.setStroke(stroke); //画线 Line2D line = new Line2D.Float(0,0,20,30); g.draw(line); line = new Line2D.Float(50,50,100,50);

line = new Line2D.Float(50,50,50,100); g.draw(line); stroke = new BasicStroke(5); g.setStroke(stroke); //设置渐变填充 GradientPaint gt = new GradientPaint(0,0,Color.green,50,30,Color.blue,true); g.setPaint((Paint)gt); //画矩形 Rectangle2D rect = new Rectangle2D.Float(80,80,40,40);

g.draw(rect); rect = new Rectangle2D.Float(100,100,40,40); g.fill(rect); //画椭圆 Ellipse2D ellipse = new Ellipse2D.Float(120,120,30,40); g.draw(ellipse); gt = new GradientPaint(0,0,Color.red,30,30,Color.yellow,true); g.setPaint((Paint)gt); ellipse = new Ellipse2D.Float(140,140,20,20); g.fill(ellipse);

//画圆角矩形 RoundRectangle2D roundRect = new RoundRectangle2D.Float(160,160,40,40,20,20); g.draw(roundRect); roundRect = new RoundRectangle2D.Float(180,180,40,40,20,20); g.fill(roundRect); //画几何图形 GeneralPath path = new GeneralPath(); path.moveTo(150,0); path.lineTo(160,50); path.curveTo(190,200,240,140,200,100); g.fill(path); }

图5.4 通过Graphics2D对象绘制形状

5.5.2 绘制文本 Graphics2D类提供一个文本布局(TextLayout)对象,用于实现各种字体或段落文本的绘制。其构造函数为: public TextLayout(String string, Font font, FontRenderContext frc) 通过字符串string和字体font构造布局。 public void draw(Graphics2D g2, float x, float y) 将这个TextLayout对象画到Graphics2D对象g2上的x,y坐标处。 public Rectangle2D getBounds() 返回TextLayout对象的区域。

【例5.4】测试绘制文本功能,如图5.5所示。源程序代码如下: //程序文件GUIText.java import java.awt.*; import java.applet.*; import java.awt.geom.*; import java.awt.font.*; public class GUIText extends Applet { public void paint(Graphics oldg)

Graphics2D g = (Graphics2D)oldg; //设置字体 Font f1 = new Font("Courier",Font.PLAIN,24); Font f2 = new Font("helvetica",Font.BOLD,24); FontRenderContext frc = g.getFontRenderContext(); String str = new String("这是一个文本布局类的实现"); String str2 = new String("扩充绘制文本的功能"); //构造文本布局对象 TextLayout layout = new TextLayout(str, f1, frc); Point2D loc = new Point2D.Float(20,50);

//绘制文本 layout.draw(g, (float)loc.getX(), (float)loc.getY()); //设置边框 Rectangle2D bounds = layout.getBounds(); bounds.setRect(bounds.getX()+loc.getX(), bounds.getY()+loc.getY(), bounds.getWidth(), bounds.getHeight()); g.draw(bounds); layout = new TextLayout(str2,f2,frc); g.setColor(Color.red); layout.draw(g,20,80); }

图5.5 Graphics2D对象绘制文本

5.5.3 绘制图像 绘制图像用到BufferedImage类,BufferedImage类是指存放图像数据的可访问的缓冲。其构造函数为: public BufferedImage(int width, int height, int imageType) 使用宽度(width)、高度(height)和imageType类型构造BufferedImage对象。 public Graphics2D createGraphics()

用图片填充椭圆的具体过程如下: (1) 创建一个Graphics2D,可以画到BufferedImage中。 例如构建一个BufferedImage对象buf。 BufferedImage buf = new BufferedImage (img.getWidth(this), img.getHeight(this),BufferedImage.TYPE_INT_ARGB); 创建一个临时Graphics2D对象: Graphics tmpG = buf.createGraphics(); 将图像画入临时缓冲: tmpG.drawImage(img,10,10,this);

(2) 用TexturePaint类进行填充: public TexturePaint(BufferedImage txtr, Rectangle2D anchor) 构造TexturePaint对象,需要一个Rectangle2D对象来存放该对象: Rectangle2D rect = new Rectangle2D.Float(0,0,h,w); TexturePaint t = new TexturePaint(buf,rect); (3) 然后设置填充模式,并进行填充: g.setPaint(t); g.fill(new Ellipse2D.Float(100,50,60,60));

【例5.5】完成图像显示,并将区域蓝色透明显示,然后进行图片填充,如图5.6所示。源程序代码如下: //程序文件名GUIImage.java import java.awt.*; import java.applet.*; import java.awt.geom.*; import java.awt.font.*; import java.awt.image.*; import java.net.*;

public class GUIImage extends Applet { public void paint(Graphics oldg) Graphics2D g = (Graphics2D)oldg; try URL imgURL = new URL(getDocumentBase(),"sample.gif"); Image img = getImage(imgURL); int h = img.getHeight(this); int w = img.getWidth(this);

//构造缓冲图像对象 BufferedImage buf = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB); //放入临时图形类 Graphics tmpG = buf.createGraphics(); tmpG.drawImage(img,10,10,this); g.drawImage(buf,10,20,this); //设置透明颜色对象 Color transBlue = new Color(0,0,255,100); g.setColor(transBlue); GeneralPath path = new GeneralPath();

path.moveTo(60,0); path.lineTo(50,100); path.curveTo(160,230,240,140,200,100); g.fill(path); transBlue = new Color(0,0,255,240); g.fill(new Ellipse2D.Float(100,100,50,50)); Rectangle2D rect = new Rectangle2D.Float(0,0,h,w); //图片填充 TexturePaint t = new TexturePaint(buf,rect);

g.setPaint(t); g.fill(new Ellipse2D.Float(100,50,60,60)); } catch(Exception e) { System.out.println("Error:" + e.getMessage());

图5.6 通过Graphics2D对象绘制图像

习 题 1. 使用Graphics类进行图形绘制,要求绘制深蓝色矩形和红色椭圆形,然后分别进行填充。 习 题 1. 使用Graphics类进行图形绘制,要求绘制深蓝色矩形和红色椭圆形,然后分别进行填充。 2. 使用Graphics2D类绘制粉红色到蓝色的渐变,填充到圆角矩形、多边形,笔画宽度为10。 3. 绘制文本“欢迎来到Java世界”,其中“欢迎来到”为蓝色显示,而“Java世界”为橙色显示,文本用矩形框起来,底色为黄色。 4. 画一个三角形区域,然后用图片进行填充。

第6章 Java用户界面技术 6.1 用户界面对象 6.2 布局 6.3 java.swing包 习 题

6.1 用户界面对象 用户界面上经常用到的组件对象有Button(按钮)、Checkbox(复选框)、Choice(组合框)、Label(标签)、List(列表)、Scrollbar(滚动条)、TextComponent(TextArea(文本区域)、TextField(文本框))和Panel(面板)。这些组件类的继承情况如下: java.lang.Object | +--java.awt.BorderLayout +--java.awt.FlowLayout +--java.awt.Component

| +--java.awt.Button +--java.awt.Canvas +--java.awt.Checkbox +--java.awt.Choice +--java.awt.Container +--java.awt.Panel +--java.awt.Label +--java.awt.List +--java.awt.Scrollbar +--java.awt.TextComponent +--java.awt.TextArea +--java.awt.TextField

6.1.1 按钮 Button类创建一个带标签的按钮对象,当按下一个按钮时,可以引发一些事件,包含一系列的动作或操作。例如单击按钮,使得界面颜色发生变化等。它的构造函数以及主要的方法如下: public Button() 构建一个没有标签的按钮。 public Button(String label) 构建一个显示为label的按钮。 public void setLabel(String label) 设置显示标签为字符串label。 public String getLabel() 获取按钮的标签,返回为字符串。

【例6.1】测试Button类的Applet。用户界面如图6.1所示。源程序代码如下: //程序文件名UseButton.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseButton extends Applet { String str1 = new String(); //声明按钮对象 Button b1; Button b2;

public void init() { //构造对象 b1 = new Button(); b2 = new Button("按钮对象2"); //添加到界面 this.add(b1); this.add(b2); } public void start()

b1.setLabel("按钮对象1"); str1 = b2.getLabel(); repaint(); } public void paint(Graphics g) { g.drawString(str1,20,30); };

启动Applet的HTML页面代码如下: <body> <applet code = "UseButton.class" height = 200 width = 300> </applet> </body> </html>

图6.1 测试Button类的界面

6.1.2 复选框和单选按钮 复选框是一个图形组件,有两个状态,即“选中”(true)和“未选中”(false)。单击复选框时可以在“选中”、“未选中”之间进行切换。在Java编程语言中,单选按钮没有单独的类,而是作为复选框的特例存在,用户通过把一组复选框放置在同一个复选框组中创建一套单选按钮。它的构造函数和其它方法如下: public Checkbox() 创建一个没有标签的复选框。 public Checkbox(String label) 创建一个标签为lable的复选框。

public Checkbox(String label, boolean state) 创建一个标签为lable的复选框,并设置初始状态。 public CheckboxGroup() 创建一个复选框组,用来放置单选按钮。 public Checkbox(String label, CheckboxGroup group, boolean state) 创建一个标签为lable的复选框,添加到group组中并设置初始状态,作为单选按钮的形式出现。

public String getLabel() 获得复选框的标签。 public void setLabel(String label) 设置标签。 public boolean getState() 返回复选框所在的状态,是选中还是未选中。 public void setState(boolean state) 设置状态,用来初始化复选框的状态。

【例6.2】测试复选框和单选按钮的用法,用户界面如图6.2所示。源程序代码如下: //程序文件名UseCheckbox.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseCheckbox extends Applet { String str1 = new String(); boolean b1 = false; boolean b2 = false;

Checkbox c1,c2,c3; Checkbox cRadio1,cRadio2; CheckboxGroup c; public void init() { c1 = new Checkbox(); c2 = new Checkbox("复选框对象2"); c3 = new Checkbox("复选框对象3",true); //构造单选按钮 c = new CheckboxGroup(); cRadio1 = new Checkbox("单选按钮1",c, false);

cRadio2 = new Checkbox("单选按钮2",c,true); //添加到界面 this.add(c1); this.add(c2); this.add(c3); this.add(cRadio1); this.add(cRadio2); } public void start() { c1.setLabel("复选框对象1");

str1 = c2.getLabel(); b1 = c3.getState(); b2 = cRadio1.getState(); repaint(); } public void paint(Graphics g) { g.drawString("获取第二个对象的标签:" + str1,40,80); g.drawString("复选框3的状态为:" + b1,40,100); g.drawString("单选按钮1的状态为:" + b2, 40,120); };

图6.2 复选框和单选按钮测试

6.1.3 组合框 组合框代表一系列选择的弹出式菜单,当前选择显示为菜单的标题。它的构造函数和其它常用方法如下: public Choice() 构建一个选择项菜单。 public void add(String item) 将item添加到选择菜单中。 public String getItem(int index) 返回选择菜单中index位置的项,注意索引是从0开始的,而项数从1开始。

public int getItemCount() 返回选择菜单总的项数。 public String getSelectedItem() 返回当前选中的项。 public int getSelectedIndex() 返回当前选中项的索引。 public void insert(String item, int index) 在index处插入字符串item。 public void remove(int position) 删除position位置的项。

public void remove(String item) public void removeAll() 删除所有的项。 public void select(int pos) 将pos处的项设为选中状态,通常用于初始化。 public void select(String str) 将str项设为选中状态,通常用于初始化。

【例6.3】测试Choice类,用户界面如图6.3所示。源程序代码如下: //程序文件名UseChoice.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseChoice extends Applet { String str1 = new String(); String str2 = new String(); int count = 0; int i1 = 0;

boolean b1 = false; Choice c1;//声明组合框对象 public void init() { //初始化组合框对象, c1 = new Choice(); c1.add("语文"); c1.add("数学"); c1.add("物理"); c1.add("化学"); c1.add("生物"); c1.select(3); this.add(c1); }

public void start() { count = c1.getItemCount(); str1 = c1.getItem(2); str2 = c1.getSelectedItem(); i1 = c1.getSelectedIndex(); repaint(); } public void paint(Graphics g) g.drawString("元素总个数为:" + count,10,80); g.drawString("第2项元素为:" + str1,10,100); g.drawString("选中项的元素为:" + str2,10,120); g.drawString("选中项的位置为:" + i1,10,140); };

图6.3 测试Choice实例的用户界面

在上例的基础上添加一个方法operate,并在start方法中调用此方法,程序段如下: public void operate() { c1.insert("英语",3); c1.remove("生物"); } public void start() operate(); count = c1.getItemCount(); ...

图6.4 修改操作后的用户界面

6.1.4 标签 标签对象就是将文本放入一个容器内的组件,显示一行只读文本。文本可以由程序修改,用户无法直接修改。它的构造函数和其它常用方法如下: public Label() 构建一个空标签。 public Label(String text) 构建一个text内容的标签,默认为左对齐。 public Label(String text, int alignment)

构建一个内容为text,以alignment方式对齐的标签,其中alignment的取值如表6.1所示。

public String getText() 获得标签文本。 public void setText(String text) 设置标签文本。 public int getAlignment() 获得标签文本对齐方式,返回为整型值。

【例6.4】测试Label类,用户界面如图6.5所示。源程序代码如下: //程序文件名UseLabel.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseLabel extends Applet { String str1 = new String(); int i1 = 0; Label l1;//声明对象 Label l2;

Label l3; public void init() { l1 = new Label(); l2 = new Label("标签对象2"); l3 = new Label("标签对象3",Label.CENTER); this.add(l1); this.add(l2); this.add(l3); } public void start()

l1.setText("标签对象1"); str1 = l2.getText(); i1 = l3.getAlignment(); repaint(); } public void paint(Graphics g) { g.drawString("获取第二个对象的文本:" + str1,40,60); g.drawString("标签对象3的对齐方式为:" + i1,40,80); };

图6.5 Label对象的界面

6.1.5 列表 List展示给用户一个滚动的文本项列表。用户可以选择其中一项或多项。它的构造函数和其它常用方法如下: public List() 构建一个新的空滚动列表。 public List(int rows) 构建一个新的rows可见行的滚动列表。 public List(int rows, boolean multipleMode) 构建一个新的rows可见行的滚动列表,并设置是否可以多项选择。multipleMode为true时,允许用户多项选择。

public void add(String item) public void add(String item, int index) 在index位置添加item项。 public String getItem(int index) 返回index位置的项。 public int getItemCount() 返回列表中项的数目。 public String[] getItems() 返回列表中的项,为一个字符串数组。

public int getSelectedIndex() 返回列表中选中项的索引。 public String getSelectedItem() 返回列表中选中的项。 public boolean isIndexSelected(int index) 判断index项是否选中。 public void remove(int position) 删除position项。 public void remove(String item) 删除item项。

public void removeAll() 删除列表中所有元素。 public void replaceItem(String newValue, int index) 将index位置的项替换为newValue。 public void select(int index) 选中index位置的项,通常用于初始化。

【例6.5】测试List类,用户界面如图6.6所示。源程序代码如下: //程序文件名UseList.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseList extends Applet { String str1 = new String(); String str2 = new String(); int i1 = 0; int i2 = 0;

List l1,l2,l3;//声明对象 public void init() { l1 = new List(); l2 = new List(5); l3 = new List(5,true); //列表添加内容 l1.add("苹果"); l1.add("香蕉"); l1.add("梨"); l2.add("语文"); l2.add("数学");

l2.add("英语"); l2.add("化学"); l3.add("钢笔"); l3.add("铅笔"); l1.select(1); l3.select(1); this.add(l1); this.add(l2); this.add(l3); } public void start() {

str1 = l1.getItem(2); i1 = l1.getItemCount(); l2.replaceItem("英语",2); str2 = l3.getSelectedItem(); repaint(); } public void paint(Graphics g) { g.drawString("第一个对象的索引为2的元素:" + str1,40,100); g.drawString("第一个对象的元素个数:" + i1,40,120); g.drawString("第三个对象选中的元素为:" + str2,40,140); };

图6.6 List对象的界面

6.1.6 滚动条 Scrollbar给用户提供一个组件,方便用户在一系列范围的值中进行选择。它的常用属性如表6.2所示。 表6.2 Scrollbar类的常用属性及缺省值

它的构造函数和其它常用方法如下: public Scrollbar() 构建一个新的垂直滚动条。 public Scrollbar(int orientation) 构建一个指定方向的滚动条。Orientation的值为HORIZONTAL(0)表示水平滚动条,值为VERTICAL(1)表示垂直滚动条。 public Scrollbar(int orientation, int value, int visible, int minimum, int maximum) 构建一个指定方向、初始值、可见性、最小值和最大值的滚动条。

public int getValue() 返回滚动条的当前值。 public int getMinimum() 返回最小值。 public int getMaximum() 返回最大值。

【例6.6】测试Scrollbar类,用户界面如图6.7所示。源程序代码如下: //程序文件名UseScrollbar.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseScrollbar extends Applet { int i1 = 0; int i2 = 0; int i3 = 0; int i4 = 0; int i5 = 0; Scrollbar s1;//声明对象

Scrollbar s2; Scrollbar s3; public void init() { s1 = new Scrollbar(); s2 = new Scrollbar(Scrollbar.HORIZONTAL); s3 = new Scrollbar(Scrollbar.VERTICAL,50,0,10,500); this.add(s1); this.add(s2); this.add(s3); } public void start()

i1 = s1.getOrientation(); i3 = s3.getValue(); i4 = s3.getMinimum(); i5 = s3.getMaximum(); repaint(); } public void paint(Graphics g) { g.drawString("第一个对象的方向:" + i1,40,80);

g.drawString("第二个对象的方向:" + i2,40,100); } };

图6.7 Scrollbar对象的测试界面

6.1.7 文本框 TextField对象用作单行文本的编辑。它的构造函数和其它常用方法如下: public TextField() 构建一个空的文本框。 public TextField(int columns) 构建一个文本框,columns给出文本框的宽度。 public TextField(String text) 构建一个文本框,用text给出初始化内容。 public TextField(String text, int columns)

构建一个文本框,text给出初始化的内容,columns给出文本框的宽度。 public void setText(String t) 将文本框的内容设置为特定文本t。 public String getText() 返回文本框的内容,返回值为字符串。 public void setEditable(boolean b) 设定文本框内容是否是用户可编辑的,b为false时,表示不可编辑,b为true时,表示可编辑。通常创建一个文本框时默认为用户可编辑的。 public int getColumns() 获取列数。

【例6.7】TextField类的测试,用户界面如图6.8所示。源程序代码如下: //程序文件名UseTextField.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseTextField extends Applet { String str1 = new String(); int i1 = 0; int i2 = 0; TextField tf1, tf2, tf3, tf4; public void init()

tf1 = new TextField(); tf2 = new TextField(20); tf3 = new TextField("文本对象3"); tf4 = new TextField("文本对象4", 30); add(tf1); add(tf2); add(tf3); add(tf4); } public void start() {

tf1.setText("文本对象1"); tf2.setText("文本对象2"); str1 = tf3.getText(); i1 = tf3.getColumns(); i2 = tf4.getColumns(); tf4.setEditable(false); repaint(); } public void paint(Graphics g) { g.drawString("第三个对象的文本内容为:" + str1,20,140); g.drawString("第三个对象的列数为:" + i1,20,160); g.drawString("第四个对象的列数为:" + i2,20,180); };

图6.8 TextField对象的界面

6.1.8 文本区域 TextArea对象用于允许多行编辑的文本区域,可以设置为可编辑的,也可以设为只读属性。 public TextArea() 构建一个空文本区域。 public TextArea(int rows, int columns) 构建一个行数为rows、列数为columns的空文本区域。 public TextArea(String text) 构建一个文本为text的文本区域。

public TextArea(String text, int rows, int columns) 构建一个文本为text、行数为rows、列数为columns的文本区域。 public TextArea(String text, int rows, int columns, int scrollbars) 构建一个文本为text、行数为rows、列数为columns的文本区域。滚动条的情况由scrollbars参数决定。Scrollbars的取值如表6.3所示。

表6.3 TextArea中的滚动条属性值

public void append(String str) public void insert(String str, int pos) 在指定位置pos插入非空字符串str。 public void replaceRange(String str, int start, int end) 将start开始位置到end结束位置的字符串替换成非空字符串str,其中start位置的字符被替换,end位置的字符仍保留。 public void setText(String t) 将文本框的内容设置为特定文本t。

public String getText() 返回文本框的内容,返回值为字符串。 public void setEditable(boolean b) 设定文本框内容是否是用户可编辑的。b为false时,表示不可编辑。b为true时,表示可编辑。通常创建一个文本框时,默认为用户可编辑的。 public int getColumns() 返回列数。 public int getRows() 返回行数。

【例6.8】测试TextArea类,用户界面如图6.9所示。源程序代码如下: //程序文件名UseTextArea.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UseTextArea extends Applet { String str1 = new String(); String str2 = new String(); int i1,i2,i3; TextArea t1,t2,t3,t4,t5;//声明对象

public void init() { t1 = new TextArea(); t2 = new TextArea(2,20); t3 = new TextArea("文本区域对象3"); t4 = new TextArea("文本区域对象4", 3, 10); t5 = new TextArea("文本区域对象5", 2, 24, TextArea.SCROLLBARS_BOTH); this.add(t1); this.add(t2); this.add(t3); this.add(t4); this.add(t5); }

public void start() { t1.setText("文本区域对象1"); t2.append("文本区域对象2"); t3.insert("\"插入3\"",4); str1 = t3.getText(); t4.replaceRange("\"替换\"",2,6); str2 = t4.getText(); i1 = t4.getRows(); i2 = t5.getColumns(); i3 = t5.getScrollbarVisibility(); t5.setEditable(false); repaint();

} public void paint(Graphics g) { g.drawString("第三个对象的文本内容为:" + str1,40,400); g.drawString("第四个对象的文本内容为:" + str2,40,420); g.drawString("第四个对象的行数为:" + i1,40,440); g.drawString("第五个对象的列数为:" + i2,40,460); g.drawString("第五个对象的滚动条情况为:" + i3,40,480); };

图6.9 TextArea对象的界面

6.2 布 局 6.2.1 流式布局器 流式布局将组件按照从左到右的顺序自然排列,是缺省的设置方式。其构造函数如下: 6.2 布 局 6.2.1 流式布局器 流式布局将组件按照从左到右的顺序自然排列,是缺省的设置方式。其构造函数如下: FlowLayout() 构建一个新的流式布局器,中央对齐,对象之间以5单元水平和垂直间隔。 FlowLayout(int align) 构建一个新的流式布局器,通过align设置对齐方式,对象之间以5单元水平和垂直间隔。 FlowLayout(int align, int hgap, int vgap) 构建一个新的流式布局器,通过align设置对齐方式,对象之间以hgap单元水平间隔并以vgap单元垂直间隔。

6.2.2 边缘布局器 边缘布局器是一个分为北(NORTH)、南(SOUTH)、东(EAST)、西(WEST)和中央(CENTER)五部分的容器。其构造函数如下: BorderLayout() 构建一个新的边缘布局,对象之间没有间隔。 BorderLayout(int hgap, int vgap) 构建一个新的边缘布局,对象之间的水平间隔为hgap,垂直间隔为vgap。

6.2.3 面板 面板是最简单的容器类,应用程序可以在面板提供的空间里放置任意组件及其它的面板。 Panel() 使用缺省的布局管理器创建一个新的面板。缺省的布局管理器为流式管理器。 Panel(LayoutManager layout) 用指定的layout布局管理器创建一个面板。 public void setLayout(LayoutManager mgr) 设置布局管理器。

【例6.9】下面是一个综合性实例,采用本章介绍的界面对象设置用户界面并进行布局管理。经过布局后的界面如图6.10所示。 //程序文件名UsePanel.java import java.awt.*; import java.applet.*; import java.applet.Applet; public class UsePanel extends Applet { Label lblName,lblNumber,lblSex,lblJob,lblText; TextField tfName,tfNumber; Checkbox chMale, chFemale;

CheckboxGroup c; TextArea taText; Choice chJob; Button btnOk,btnCancel; Panel p1,p2,p3,p4,p5,p6,p7,p8,p9; public void init() { lblName = new Label("姓名:"); lblNumber = new Label("身份证号:"); lblSex = new Label("性别"); lblJob = new Label("职业"); lblText = new Label("个性化宣言:");

tfName = new TextField(23); tfNumber = new TextField(20); taText = new TextArea(10,20); c = new CheckboxGroup(); chMale = new Checkbox("男",c,true); chFemale = new Checkbox("女",c,false); chJob = new Choice(); chJob.add("计算机业"); chJob.add("医生"); chJob.add("教师"); chJob.add("军队"); btnOk = new Button("确定"); btnCancel = new Button("取消");

p1 = new Panel(); p2 = new Panel(); p3 = new Panel(); p4 = new Panel(); p5 = new Panel(); p6 = new Panel(); p7 = new Panel(new BorderLayout()); p8 = new Panel(); p9 = new Panel(new BorderLayout()); p1.add(lblName); p1.add(tfName); p2.add(lblNumber); p2.add(tfNumber); p3.add(lblSex);

p3.add(chMale); p3.add(chFemale); p4.add(lblJob); p4.add(chJob); p5.add(p3); p5.add(p4); p6.setLayout(new BorderLayout()); p6.add(p1,BorderLayout.NORTH); p6.add(p2,BorderLayout.CENTER); p6.add(p5,BorderLayout.SOUTH); p7.add(lblText,BorderLayout.NORTH); p7.add(taText,BorderLayout.CENTER);

p8.setLayout(new FlowLayout(FlowLayout.CENTER,30,10)); p8.add(btnOk); p8.add(btnCancel); p9.add(p6,BorderLayout.NORTH); p9.add(p7,BorderLayout.CENTER); p9.add(p8,BorderLayout.SOUTH); add(p9); } };

图6.10 经过布局后的界面

6.3 java.swing包 6.3.1 基本组件 JButton为轻量级按钮组件,对应着Button组件类。 JLabel为轻量级标签组件,对应着Label组件类。 JCheckBox为轻量级复选框,对应CheckBox类。 JradioButton为轻量级单选按钮,需要添加在ButtonGroup组中。 JRadioButton jMale = new JRadioButton("男"); JRadioButton jFemale = new JRadioButton("女");

ButtonGroup group = new ButtonGroup(); group.add(jMale); group.add(jFemale); JComoBox对应Choice组件,但添加项的方法不同,使用的是addItem方法,例如: JComboBox cmbJob = new JComboBox(); cmbJob.addItem("计算机业"); cmbJob.addItem("医生"); cmbJob.addItem("教师");

cmbJob.addItem("军队"); JTextField组件为允许对单行文本进行编辑的轻量级组件,对应TextField类。 JTextArea组件用于在多行区域中显示纯文本并进行编辑。 Jpanel为轻量级面板控件,对应原来的Panel类。 JApplet类继承自Applet类。

6.3.2 JTable JTable是用于显示和编辑的两维表单元。它的构造函数和常用方法为: public JTable() 构造一个空的JTable对象。 public void setModel(TableModel dataModel) 为表对象设置数据模型dataModel,并将新数据模型进行注册。 DefaultTableModel是一个实现模型,使用一系列Vector对象实现表单元数据的填充。它的构造函数和常用方法为: public DefaultTableModel() 构造函数。

public void addColumn(Object columnName) 添加一列标题。 public void addRow(Vector rowData) 在模型对象的末尾添加一行Vector对象的数据。 构造Jtable对象的步骤如下: (1) 构造一个空JTable对象。 JTable tblInf = new JTable(); (2) 构造DefaultTableModel对象,用setModel方法将此对象绑定到JTable上。 DefaultTableModel dtm = new DefaultTableModel();

(3) 具体实现DefaultTableModel对象(addColomn方法实现列标题设置,addRow方法实现数据行添加)。 ① 初始化Vector列表,添加列标题。 Vector vCdata = new Vector(); vCdata.add("姓名"); vCdata.add("身份证号"); vCdata.add("性别"); tblInf.setModel(dtm); for (int i=0; i<vCdata.size(); i++) dtm.addColumn((String)vCdata.elementAt(i));

② 初始化Vector列表,添加行数据。 Vector vRdata = new Vector(); vRdata.add("王飞"); vRdata.add("111122197904290902"); vRdata.add("男"); dtm.addRow(vRdata); ③ JTable没有内嵌滚动功能,将之放入JScrollPane对象中并进行显示。 JScrollPane s = new JScrollPane(tblInf); getContentPane().add(s,BorderLayout.CENTER);

6.3.3 Jtree JTree控件用于显示一系列分层数据,即树状显示。它的构造函数和常用方法为: public void setModel(TreeModel newModel) 设置提供数据的TreeModel模型。TreeModel是一个接口,接口内实现的类为DefaultTreeModel类,DefaultTreeModel是一个使用TreeNodes的简单树数据模型,下面给出它的构造函数。 public DefaultTreeModel(TreeNode root)

创建根结点为root的树,其中每个结点都可以有子结点。TreeNode是一个接口,它包含一个DefaultTreeNode类,DefaultTreeNode类表达了一个树型数据结构中具有一般意义的结点。每个树结点至多有一个父结点,0个或多个子结点。其构造函数为: public DefaultMutableTreeNode(Object userObject) 创建一个无父结点、无子结点的树结点。例如 public DefaultMutableTreeNode f = new DefaultMutableTreeNode("个人信息"); public add(MutableTreeNode newChild) 将newChild结点添加到树结点的末尾,使之称为当前结点的子结点。

创建树结构的步骤如下: (1) 构建JTree对象。 JTree tree = new JTree(); (2) 构造DefaultMutableTreeModel对象,使用SetModel方法将DefaultMutableTreeModel对象绑定到Jtree对象。 tree.setModel(new DefaultTreeModel(root));

(3) 构造树。 ① 构造根结点。 DefaultMutableTreeNode root; ② 构造分支结点,添加到根结点。 DefaultMutableTreeNode NodeName, NodeNumber, NodeSex; NodeName = new DefaultMutableTreeNode("姓名"); NodeNumber = new DefaultMutableTreeNode("身份证号"); NodeSex = new DefaultMutableTreeNode("性别"); root.add(NodeName); root.add(NodeNumber); root.add(NodeSex);

③ 添加叶子结点并添加到分支结点。 leafName = new DefaultMutableTreeNode("王飞"); leafNumber = new DefaultMutableTreeNode("111122197904290902"); leafSex = new DefaultMutableTreeNode("男"); NodeName.add(leafName); NodeNumber.add(leafNumber); NodeSex.add(leafSex);

④ 使用DefaultMutableTreeModel的构造函数,将根结点作为参数构造树结构中的数据。 DefaultTreeModel dTree = new DefaultTreeModel(root); ⑤ JTree没有内嵌的滚动属性,较大的树状结构可以显示在JScrollPane上。 JScrollPane scroll = new JScrollPane(tree); getContentPane().add(scroll);

6.3.4 实例 【例6.10】 测试java.swing包提供的轻量级组件的例子,主类TestSwing扩展于JFrame类,显示结果如图6.11所示。界面分为三部分,上一部分和上例中的显示结果一一对应,中间一部分为table表记录,下面的部分为树状显示结构。源程序代码如下: //文件名为:TestSwing.java import java.awt.*; import java.util.*; import javax.swing.*; import javax.swing.tree.*; import javax.swing.table.*;

import javax.swing.ButtonGroup; public class TestSwing extends JFrame { JLabel lblName = new JLabel("姓名:"); JLabel lblNumber = new JLabel("身份证号"); JLabel lblSex = new JLabel("性别"); JLabel lblJob = new JLabel("职业"); JLabel lblText = new JLabel("个性化宣言"); JTextField tfName = new JTextField(23); JTextField tfNumber = new JTextField(20); JTextArea taText = new JTextArea(5,20); JRadioButton jMale = new JRadioButton("男");

JRadioButton jFemale = new JRadioButton("女"); ButtonGroup group = new ButtonGroup(); JComboBox cmbJob = new JComboBox(); JButton btnOk = new JButton("确定"); JButton btnDisplay = new JButton("取消"); JTable tblInf = new JTable(); DefaultTableModel dtm = new DefaultTableModel(); //布局设置 JTree tree = new JTree(); JPanel p1 = new JPanel(); JPanel p2 = new JPanel();

JPanel p3 = new JPanel(); JPanel p7 = new JPanel(new BorderLayout()); JPanel p8 = new JPanel(); JPanel p9 = new JPanel(new BorderLayout()); public TestSwing() { group.add(jMale); group.add(jFemale);

cmbJob.addItem("计算机业"); p1.add(lblName); p1.add(tfName); p2.add(lblNumber); p2.add(tfNumber); p3.add(lblSex); p3.add(jMale); p3.add(jFemale); p4.add(lblJob); p4.add(cmbJob);

p5.add(p3); p5.add(p4); p6.setLayout(new BorderLayout()); p6.add(p1,BorderLayout.NORTH); p6.add(p2,BorderLayout.CENTER); p6.add(p5,BorderLayout.SOUTH); p7.add(lblText,BorderLayout.NORTH); p7.add(taText,BorderLayout.CENTER); p8.setLayout(new FlowLayout(FlowLayout.CENTER,30,10)); p8.add(btnOk); p8.add(btnDisplay); p9.add(p6,BorderLayout.NORTH); p9.add(p7,BorderLayout.CENTER);

p9.add(p8,BorderLayout.SOUTH); //调用设置表的方法 setTable(); //调用设置树的方法 setTree(); getContentPane().add(p9,BorderLayout.NORTH); JScrollPane s = new JScrollPane(tblInf); getContentPane().add(s,BorderLayout.CENTER); getContentPane().add(tree,BorderLayout.SOUTH); } //设置表,用Vector辅助添加 public void setTable() {

Vector vCdata = new Vector(); vCdata.add("姓名"); vCdata.add("身份证号"); vCdata.add("性别"); tblInf.setModel(dtm); for (int i=0; i<vCdata.size(); i++) dtm.addColumn((String)vCdata.elementAt(i)); Vector vRdata = new Vector(); vRdata.add("王飞"); vRdata.add("111122197904290902"); vRdata.add("男"); dtm.addRow(vRdata);

} //设置树 public void setTree() { DefaultMutableTreeNode root; DefaultMutableTreeNode NodeName, NodeNumber, NodeSex; DefaultMutableTreeNode leafName, leafNumber, leafSex; root = new DefaultMutableTreeNode("个人信息"); NodeName = new DefaultMutableTreeNode("姓名"); leafName = new DefaultMutableTreeNode("王飞"); NodeNumber = new DefaultMutableTreeNode("身份证号"); leafNumber = new DefaultMutableTreeNode("111122197904290902");

NodeSex = new DefaultMutableTreeNode("性别"); leafSex = new DefaultMutableTreeNode("男"); root.add(NodeName); root.add(NodeNumber); root.add(NodeSex); NodeName.add(leafName); NodeNumber.add(leafNumber); NodeSex.add(leafSex); //设置Tree渲染的基本属性 tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );

tree.setShowsRootHandles( true ); //树不可以编辑 tree.setEditable( false ); tree.setModel(new DefaultTreeModel(root)); } public static void main(String[] args) { TestSwing ts = new TestSwing(); ts.setSize(400,450); ts.show();

图6.11 显示结果

习 题 1. 设计一个界面,如图6.12所示。其中身份证一栏有四项,分别为身份证、军人证、学生证和护照。 图6.12

2. 设计一个标签区域和三个滚动条,滚动条的取值范围均为0~255,即使得滚动条的取值对应颜色对象的红、绿、蓝三个基色的值,初始标签颜色为红色。 3. 用java.swing包中的类设计如图6.12所示的界面,然后进行详细的布局设计。 4. 图6.13中的Xuesheng表,代表了每个学生的基本信息,在Applet界面上设计此表,并依次进行记录的添加。

图6.13 Xuesheng表的记录显示

5. 用树状结构显示如图6.14所示的部分文件系统目录,要求节点的名字为文件夹的名字或者文件的名字,图片无需显示。注意ppt文件夹下含有两个文件,分别为6_10.ppt和6_10.vsd。 图6.14 部分文件系统目录

第7章 异常、事件和多线程机制 7.1 异常 7.2 事件 7.3 多线程机制 习 题

7.1 异 常 7.1.1 定义 异常是指当程序中某些地方出错时创建的一种特殊的运行时错误对象。Java创建异常对象后,就发送给Java程序,即抛出异常(throwing an exception)。程序捕捉到这个异常后,可以编写相应的异常处理代码进行处理。使用异常处理可以使得程序更加健壮,有助于调试和后期维护。

7.1.2 异常类 由继承类图可以看出,Throwable类派生了两个类:Exception类和Error类,其中Error类系统保留,而Exception类供应用程序使用,它下面又派生出几个具体的异常类,都对应着一项具体的运行错误,如图7.1所示。

图7.1 异常类图

Exception类中常用的方法为: public String toString() 返回异常的简短描述。 public String getMessage() 返回异常的详细信息描述。 异常类分为系统定义的异常和用户自定义的异常。

1. 系统定义的异常 图7.1中Exception派生的这些子类都是系统事先定义好并包含在Java类库中的,系统定义的异常对应着一些系统错误,如中断、文件没找到等错误。表7.1列举了一些常见的系统异常类。 表7.1 常见的系统异常类

2. 用户自定义异常 用户自定义异常用来处理用户应用程序中的特定逻辑的运行错误,用户自定义的异常类通常继承自Exception类,例如: public class UserDefineException extends Exception { public UserDefineException(String msg) super(msg); } };

7.1.3 异常处理 Java异常通常在调用某些方法不一定完全成功时抛出,针对抛出的异常程序需要给出相应的处理,这称为异常处理。异常处理分为三个部分:捕捉异常、程序流程的跳转和异常处理语句块。 当一个异常被抛出时,程序中有专门的语句来接收这个被抛出的异常对象,这个过程就是捕捉异常;当一个异常类的对象被捕捉或接收后,用户程序就会发生流程跳转,系统中止程序运行,跳转到异常处理语句的执行,或者直接跳出系统,回到操作系统状态下。在Java语言中,try语句用来启动Java的异常处理机制,通常是可能抛出异常的语句的调用;而catch语句进行捕捉和处理异常,有时添加finally语句块,finally中的语句是正常执行或者处理异常之后必须执行的语句。语句格式如下:

try { 语句块; } catch(异常类 异常类参数名) 异常处理语句块; finally try或者catch语句完毕后必须执行的语句(通常用于关闭文件流对象或者数据库对象等);

1. 直接抛出异常 例如: public void myMethod() { try urlName = new URL("http://www.sina.com.cn"); getAppletContext().showDocument(urlName,"right"); } catch(MalformedURLException e) System.out.println(e.getMessage());

try部分试图打开一个网址http://www. sina. com

2. 间接抛出异常 例如: public void myMethod() throws MalformedURLException { urlName = new URL("http://www.sina.com.cn"); getAppletContext().showDocument(urlName,"right"); } 在方法后面直接抛出。

3. 综合方法 例如: public void myMethod() throws MalformedURLException { try urlName = new URL("http://www.sina.com.cn"); getAppletContext().showDocument(urlName,"right"); } catch(MalformedURLException e) System.out.println(e.getMessage());

7.1.4 多异常的处理 多异常的处理使用多个catch来捕捉不同类的异常,Java中对catch块的数量没有限制。格式如下: Try { ... } catch(异常类1 标识符)

... } catch(异常类2 标识符) { finally

【例7.1】 测试多异常处理和自定义异常。源程序代码如下: //程序文件名UseMultiException.java public class UseMultiException extends Exception { static String str1; static String str2; public static void main(String[] args) int i1 = 0; int i2 = 0; str1 = new String(args[0]); str2 = new String(args[1]); String strResult = new String(); try

read(str1, str2); i1 = Integer.parseInt(str1); i2 = Integer.parseInt(str2); int result = i1/i2; strResult = String.valueOf(result); } catch(NumberFormatException e) { strResult = "错误的数字:" + e.getMessage(); catch(ArithmeticException e) strResult = "被0除错误:" + e.getMessage();

str1 = s1.trim(); str2 = s2.trim(); if (str1.equals("0")) { throw (new UserDefineException("本系统0不能为被除数")); } }; class UserDefineException extends Exception public UserDefineException(String msg) super(msg);

具体运行测试如下,结果如图7.2所示。 java UseMultiException 2gf 5 数字格式出错。 java UseMultiException 2gf 0 数字格式出错,下面的程序代码不再执行,无法抛出第二个异常。 java UseMultiException 2 0 被0除出错。 java UseMultiException 0 3 引发用户自定义错误。 java UseMultiException 9 3 9/3的答案为3。

图7.2 异常测试输出

7.2 事 件 事件用于描述程序、系统和程序使用者之间的各种活动。这些事件由系统事先定义好,当用户在图形界面上单击控件或双击鼠标时就可能引发某个事件,而用户程序中需要编制相应的代码来对这些事件做出处理。 1. 事件源 图形用户界面上每个可能产生事件的组件称为事件源。

2. 事件监听者 Java系统中注册的用于接收特殊事件的类。不同的事件对应着不同的监听者,要想事件被监听者监听并处理,则需先将事件源注册到监听者。 3. 事件处理流程 事件源触发事件并将事件作为一个参数传递给监听者,监听者实现某个接口中的抽象方法,从而实现对事件的处理。 Java的事件处理机制是一个委托事件模型,如图7.3所示。

图7.3 Java事件处理机制

事件源注册的方法如下: public void addActionListener(ActionListener l) 添加特定的动作,监听接收来自事件源的动作事件,如果l为空,不会产生任何动作。 监听者实现的接口为ActionListener接口,接口ActionListener来自包java.awt.event。 在此接口中只有一个方法: public void actionPerformed(ActionEvent e) 当事件对象e发生时,调用此方法。监听者就需要实现这个方法。 Java中常用的事件和相应的事件监听者如表7.2所示。

表7.2 常用事件及其监听者

7.2.1 动作事件(ActionEvent) ActionEvent包含一个事件,该事件为执行动作事件ACTION_PERFORMED。触发这个事件的动作为: (1) 点击按钮。 (2) 双击列表中的选项。 (3) 选择菜单项。 (4) 在文本框中输入回车。

常用方法如下: public String getActionCommand() 返回引发某个事件的命令按钮的名字,如果名字为空,那么返回标签值。 public void setActionCommand(String command) 设置引发事件的按钮的名字,默认设置为按钮的标签。

【例7.2】测试动作事件。源程序代码如下: //程序文件名UseButton.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.applet.Applet; public class UseButton extends Applet implements ActionListener { String str1 = new String(); Button b1; //声明按钮对象; Button b2; Color c; public void init()

b1 = new Button(); b2 = new Button("按钮对象2"); //添加事件监听者 b1.addActionListener(this); b2.addActionListener(this); this.add(b1); this.add(b2); } public void start() { b1.setLabel("按钮对象1"); str1 = b2.getLabel(); repaint();

public void paint(Graphics g) { g.setColor(c); g.drawString("引发事件的对象的标签:" + str1, 40,60); } //实现接口中的方法,响应动作事件 public void actionPerformed(ActionEvent e) String arg = e.getActionCommand(); if(arg == "按钮对象1") c = Color.red; str1 = "按钮对象1";

else if(arg == "按钮对象2") { c = Color.blue; str1 = "按钮对象2"; } repaint();

单击“按钮对象1”,引发对象1的单击事件,输出结果如图7.4所示;单击“按钮对象2”,引发对象2的单击事件,输出结果如图7.5所示。 图7.4 单击“按钮对象1”的输出

图7.5 单击“按钮对象2”的输出

7.2.2 文本事件(TextEvent) 文本事件即代表文本区域中文本变化的事件TEXT_VALUE_CHANGED,在文本区域中改变文本内容。 public void addTextListener(TextListener l) 添加特定的文本事件,监听者接收来自文本对象的文本事件。如果l为空,那么不会抛出任何异常,而且也不会完成任何动作。 public interface TextListener extends EventListener 用于接收文本事件的监听者接口。当对象的文本发生变化时,调用监听者对象的方法。

接口中的方法为: public void textValueChanged(TextEvent e) 当文本发生改变时调用。 public Object getSource() 发生事件的对象,从EventObject继承来的方法。

【例7.3】测试文本事件。源程序代码如下: //程序文件名UseTextEvent.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.applet.Applet; public class UseTextEvent extends Applet implements ActionListener, TextListener { TextField tOld; TextArea tNew; Panel p; public void init()

tOld = new TextField(25); tNew = new TextArea(8,25); //添加事件监听者 tOld.addActionListener(this); tOld.addTextListener(this); //设置界面 p = new Panel(new BorderLayout()); p.add(tOld,BorderLayout.NORTH); p.add(tNew,BorderLayout.SOUTH); add(p); }

//响应文本事件 public void textValueChanged(TextEvent e) { if(e.getSource() == tOld) tNew.setText(tOld.getText()); } //响应动作事件 public void actionPerformed(ActionEvent e) tNew.setText(""); };

在文本框中键入“你好,这是文本事件同步”字符串,可以看见文本区域中的字符跟随变化,如图7 在文本框中键入“你好,这是文本事件同步”字符串,可以看见文本区域中的字符跟随变化,如图7.6所示,这是响应文本事件。键入字符串后按回车键,则发生响应动作事件,将文本区域清空,如图7.7所示。 图7.6 响应文本事件

图7.7 响应动作事件

7.2.3 选择事件(ItemEvent) 选择事件中包含以事件为代表的选择项,选中状态发生变化的事件ITEM_STATE_ CHANGED。引发的动作为: (1) 改变列表类List对象选项的选中或不选中状态。 (2) 改变下拉列表类Choice对象选项的选中或不选中状态。 (3) 改变复选按钮类Checkbox对象的选中或不选中状态。

事件源对象注册的方法如下: public void addItemListener(ItemListener l) 添加特定的项监听者,接收对象的选择项发生变化的事件。 public ItemSelectable getItemSelectable() ItemEvent事件的方法,返回产生事件的事件源对象。 public interface ItemListener extends EventListener 接收选项事件的监听者接口。当选项中事件发生时,调用监听对象的itemStateChanged方法。 public void itemStateChanged(ItemEvent e) 当用户选中一项或未选中一项时,调用这个方法。

【例7.4】测试选择事件。分别对设置颜色的复选框和有三种字号10、12和14的组合框进行选择时,标签的颜色和字体发生变化。源程序代码如下: //程序文件名UseItemEvent.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.applet.Applet; public class UseItemEvent extends Applet implements ItemListener {

Checkbox cDisp; Button btnDisp; Choice cFont; public void init() { cDisp = new Checkbox("红色"); btnDisp = new Button("颜色显示"); cFont = new Choice(); cFont.add("10"); cFont.add("12"); cFont.add("14"); //添加事件 cDisp.addItemListener(this);

cFont.addItemListener(this); add(cDisp); add(cFont); add(btnDisp); } //接口事件 public void itemStateChanged(ItemEvent e) { Checkbox temp; Choice temp2; Font oldF; //复选框 if(e.getItemSelectable() instanceof Checkbox)

temp = (Checkbox)(e.getItemSelectable()); //选中为红色,否则为蓝色 if(temp.getState()) btnDisp.setBackground(Color.red); else btnDisp.setBackground(Color.blue); } //组合框 if(e.getItemSelectable() instanceof Choice) { oldF = btnDisp.getFont();

temp2 = (Choice)(e.getItemSelectable()); String s = temp2.getSelectedItem(); //设置字体 btnDisp.setFont(new Font(oldF.getName(),oldF.getStyle(),Integer.parseInt(s))); }

当选中红色复选框和组合框中的字号为14时,显示界面如图7.8所示;当未选中红色复选框和选中组合框中的字号为10时,显示界面如图7.9所示。 图7.8 选中红色和字号为14的界面

图7.9 未选中红色和字号为10的界面

7.2.4 调整事件(AdjustmentEvent) 调整事件包含一个事件,即ADJUSTMENT_VALUE_CHANGED事件,当操纵滚动条改变其滑块位置时引发动作。AjustEvent的方法如下: public Adjustable getAdjustable() 返回引发事件的对象。 public int getValue() 返回调整事件中的当前值。

public void addAdjustmentListener(AdjustmentListener l) 添加调整监听者来接收来自对象的AdjustmentEvent实例。 public interface AdjustmentListener extends EventListener 接收调整事件的监听接口,有一个方法: public void adjustmentValueChanged(AdjustmentEvent e) 可在调整改变时调用这个值。

【例7.5】测试调整事件。设置一个水平滚动条,取值为1~36,随着滑块的变化,滚动条的值将显示在文本区域中,并且字体大小也会跟随变化。源程序代码如下: //程序文件名UseAdjustmentEvent.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.applet.Applet; public class UseAdjustmentEvent extends Applet implements AdjustmentListener {

Scrollbar s; TextArea txtValue; Panel p; public void init() { s = new Scrollbar(Scrollbar.HORIZONTAL,0,1,10,36); //添加监听者 s.addAdjustmentListener(this); txtValue = new TextArea(5,25); //界面布局 p = new Panel(new BorderLayout()); p.add(s,BorderLayout.NORTH);

p.add(txtValue,BorderLayout.SOUTH); add(p); } public void start() { public void adjustmentValueChanged(AdjustmentEvent e) int value; Font oldF; if(e.getAdjustable() == s) //得到滚动条的值

value = e.getValue(); //将值写入文本区域 txtValue.setText(new Integer(value).toString()); //按照滚动条的值设置字体 oldF = txtValue.getFont(); txtValue.setFont(new Font(oldF.getName(),oldF.getStyle(),value)); }

随着滚动条的变化,可以看见文本区域中的取值以及大小都会跟随变化,取值为10和取值为35时的效果各不相同,如图7.10和图7.11所示。 图7.10 字号为10的界面

图7.11 字号为35的界面

7.2.5 鼠标事件(MouseEvent) 表明画布或界面组件中发生的鼠标事件,包含按下鼠标、释放鼠标、单击鼠标、进入部件的地理位置的鼠标事件和退出部件的地理位置的鼠标事件,以及鼠标移动事件(鼠标移动和鼠标拖动)。 鼠标使用addMouseListener方法注册,通过MouseListener接收鼠标事件;鼠标还可以使用addMouseMotionListener方法注册,通过MouseMotionListener监听者监听鼠标移动事件。

监听者中有具体的方法分别针对上述具体的鼠标事件,系统能够自动分辨鼠标事件的类型并调用相应的方法,所以只需编码实现相应的代码就可以了。 public int getButton() 返回哪个按钮发生变化。 public int getClickCount() 返回与这个事件相关的鼠标单击的次数。 public Point getPoint() 返回同源部件相对的事件发生的x、y位置。 public int getX() 返回同源部件相对的事件发生的x位置。 public int getY() 返回同源部件相对的事件发生的y位置。

【例7.6】测试按钮和画布的鼠标事件,包括单击、按下、进入和退出等。鼠标事件的演示如图7.12和7.13所示。源程序代码如下: //程序文件名UseMouseEvent.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.applet.Applet; public class UseMouseEvent extends Applet implements MouseListener, MouseMotionListener {

Button btn; public void init() { btn = new Button("演示鼠标事件"); add(btn); //给按钮添加鼠标事件和鼠标移动事件 btn.addMouseListener(this); btn.addMouseMotionListener(this); //给画布添加鼠标事件和鼠标移动事件 this.addMouseListener(this); this.addMouseMotionListener(this); }

//单击事件 public void mouseClicked(MouseEvent e) { Point p = new Point(); if(e.getSource() == btn) if(e.getClickCount() == 1) btn.setLabel("单击鼠标"); } else if(e.getClickCount() == 2) btn.setLabel("双击鼠标");

} else { if(e.getClickCount() == 1) p = e.getPoint(); showStatus(p.x + "," + p.y + "单击鼠标"); else if(e.getClickCount() == 2) showStatus(p.x + "," + p.y + "双击鼠标");

} //进入事件 public void mouseEntered(MouseEvent e) { if(e.getSource() == btn) btn.setLabel("进入Button"); else showStatus("进入Applet"); public void mouseExited(MouseEvent e) btn.setLabel("退出Button");

else showStatus("退出Applet"); } //按下事件 public void mousePressed(MouseEvent e) { if(e.getSource() == btn) btn.setLabel("按下鼠标"); showStatus("按下鼠标"); //释放事件 public void mouseReleased(MouseEvent e)

if(e.getSource() == btn) btn.setLabel("松开鼠标"); else showStatus("松开鼠标"); } //移动事件 public void mouseMoved(MouseEvent e) { btn.setLabel("移动鼠标"); showStatus("移动鼠标,新位置" + e.getX() + "," + e.getY());

//拖动事件 public void mouseDragged(MouseEvent e) { if(e.getSource() == btn) btn.setLabel("拖动鼠标"); else showStatus("拖动鼠标"); } };

图7.12 状态行提示鼠标移动位置

图7.13 提示“按下鼠标”

7.2.6 键盘事件(KeyEvent) 键盘事件有三个:键盘按键按下,按键释放,按键被敲击。常用方法如下: public char getKeyChar() 返回事件中键的字符。 public int getKeyCode() 返回整数键码。 public static String getKeyText(int keyCode) 返回描述这个键码的字符串,例如“HOME”、“F1”或者“A”等。 public interface KeyListener extends EventListener 用来接收键盘事件。使用方法addKeyListener注册。

针对键盘的三个事件接口提供相应的方法进行处理,具体方法如下: public void keyPressed(KeyEvent e) 按键时引发事件处理。 public void keyReleased(KeyEvent e) 释放键时引发事件处理。 public void keyTyped(KeyEvent e) 键入键时引发事件处理。

例如,按键处理事件如下: public void keyPressed(KeyEvent e) { char ch = e.getKeyChar(); if(ch =='Y' || ch == 'y') txt.setText ("同意"); else if ch == 'N' || ch == 'n' () txt.setText ("反对"); else txt.setText ("无效"); }

7.3 多 线 程 机 制 7.3.1 线程简介 线程(thread)就是进程中的一个执行线索。Java虚拟机允许进程中同时执行多个线程。每个线程都有一个优先级。具有较高优先级的线程先执行。   线程是操作系统分配 CPU 时间的基本实体。每一个应用程序至少有一个线程,也可以拥有多个线程。线程是程序中的代码流。多个线程可以同时运行并能共享资源。   线程与进程不同,每个进程都需要操作系统为其分配独立的地址空间,而同一进程中的各个线程是在同一块地址空间中工作。 在 Java 程序中,一些动态效果(如动画的实现、动态的字幕等)常利用多线程技术来实现。

线程存在一个生命周期,由以下方法体现:   (1) start()方法:启动一个线程。   (2) run()方法:定义该线程的动作。   (3) sleep()方法:使线程睡眠一段时间,单位为ms。   (4) suspend()方法:使线程挂起。   (5) resume()方法:恢复挂起的线程。   (6) yield()方法:把线程移到队列的尾部。   (7) stop()方法:结束线程生命周期并执行清理工作。   (8) destroy()方法:结束线程生命周期但不做清理工作。 其中最常用的是方法start()、run()、sleep()、stop()。

7.3.2 线程类和Runnable接口  1. 建立Thread类的子类 class myThread extends Thread { ... public void start()//启动线程 } public void run()//运行线程

【例7.7】多线程实例,主函数给予调用,调用情况如图7.14所示。源程序代码如下: public class MThread { public static void main(String[] args) System.out.println("Hello World!"); thread2 t1 = new thread2("线程实例1"); //创建线程实例 t1.start(); //调用 thread2 t2 = new thread2("线程实例2"); t2.start(); thread2 t3 = new thread2("线程实例3"); t3.start(); }

//自定义线程类thread2 class thread2 extends Thread { Thread thread; //定义线程实例 String str; //构造函数 public thread2(String str) this.str = str; } //启动线程 public void start() thread = new Thread(this); thread.start();

public void run() { int i = 0; while(thread != null) try //计数到5时睡眠10秒 if(i == 5) sleep(10000); } catch(Exception e)

System.out.println(e.getMessage()); } System.out.println(str); i++; };

图7.14 多线程操作界面

2. 实现接口Runnable public interface Runnable Runnable接口可以由任意试图实现线程机制的类来实现。接口包含一个run方法。 public void run() 对象实现Runnable接口时,创建一个线程,启动线程导致对象run方法的调用。 实现接口Runnable进行多线程设计的方法较为常用。下面给出一个例子。

【例7.8】编写Applet,实现Runnable接口进行简单的动画演示:三幅图片依次上移,如图7.15所示。源程序代码如下: //程序文件名为UseRunnable.java import java.awt.*; import java.applet.Applet; public class UseRunnable extends Applet implements Runnable { Thread t; Image imgs[]; int high,h1,h2,h3; public void init()

high = getHeight()/3; h1 = high; h2 = high * 2; h3 = high * 3; imgs = new Image[3]; for (int i = 0; i < 3; i ++) { imgs[i] = getImage(getDocumentBase(),"image" + (i+1) + ".gif"); } public void start()

t = new Thread(this); t.start(); } public void stop() { t = null; //实现接口的run方法,获得动画效果 public void run() while( t != null) try

Thread.sleep(100); repaint(); h1 --; //上移,到顶点时睡眠 if(h1 == 0) { Thread.sleep(3000); h2 = high; } h2 --; if(h2 == 0) h3 = high;

//上移,到顶点时睡眠 h3--; if(h3 == 0) { Thread.sleep(3000); h1 = high; } catch(InterruptedException e) System.out.println(e.getMessage());

public void paint(Graphics g) { //三幅图片依次显示 g.drawImage(imgs[0],0,h1,this); g.drawImage(imgs[1],0,h2,this); g.drawImage(imgs[2],0,h3,this); } public void update(Graphics g) paint(g); };

图7.15 动画界面

习 题 1. 自定义一个用户异常,用于数值运算时在输入的字符中检测非数字字符,并编写抛出异常时的提示“使用非法字符”,测试异常。 习 题 1. 自定义一个用户异常,用于数值运算时在输入的字符中检测非数字字符,并编写抛出异常时的提示“使用非法字符”,测试异常。 2. 为第6章习题1中图6.12所示的“提交”按钮添加动作事件,用来收集界面上输入的个人信息,并在原有界面上添加一个文本区域,将数据输出到文本区域中。 3. 为第6章习题2添加调整事件,使得三个滚动条进行滑动时,标签的颜色相应地发生变化。 4. 编写Applet,实现Runnable接口,使得界面上的一图片持续做水平运动。

第8章 输入输出技术 8.1 流式输入输出 8.2 基本输入输出流 8.3 文件处理类 8.4 对象流 习 题

8.1 流式输入输出 所有的计算机程序都必须接收输入和产生输出。针对输入、输出,Java提供了丰富的类库进行相应的处理,包括从普通的流式输入输出到复杂的文件随机访问。计算机系统使用的信息都是从输入经过计算机流向输出。这种数据流动就称为流(Stream)。输入流指数据从键盘或者文件等输入设备流向计算机;输出流指数据处理结果从计算机流向屏幕或文件等输出设备。

在Java中,通过java.io包提供的类来表示流,基本的输入输出流为InputStream和OutputStream。从这两个基本的输入输出流派生出面向特定处理的流,如缓冲区读写流、文件读写流等。Java定义的流如表8.1所示。 表8.1 Java定义的输入输出流

8.2 基本输入输出流 8.2.1 InputStream类 InputStream是抽象类,代表字节输入流的所有类的超类。这个类本身不能使用,只能通过继承它的具体类完成某些操作。它的常用方法如下: public int available() throws IOException 返回流中可用的字节数。 public void close() throws IOException 关闭流并释放与流相关的系统资源。用户使用完输入流时,调用这个方法。 public void mark(int readlimit) throws IOException 输入流中标志当前位置。

public boolean markSupported() throws IOException 测试流是否支持标志和复位。 public abstract int read() throws IOException 读取输入流中的下一个字节。 public int read(byte[] b) throws IOException 从输入流中读取字节并存储到缓冲区数组b中,返回读取的字节数,遇到文件结尾返回-1。

public int read(byte[] b, int off, int len) throws IOException 从输入流中读取len个字节并写入b中,位置从off开始。返回写的字节数。 public void reset() throws IOException 重定位到上次输入流中调用的位置。 public long skip(long n) throws IOException 跳过输入流中n个字节,返回跳过的字节数,遇到文件结尾返回-1。

8.2.2 OutputStream类 OutputSteam是抽象类,代表输出字节流的所有类的超类。 public void close() throws IOException 关闭输出流,释放与流相关的系统资源。 public void flush() throws IOException 清洗输出流,使得所有缓冲区的输出字节全部写到输出设备中。 public void write(byte[] b) throws IOException 从特定字节数组b将b数组长度个字节写入输出流。 public void write(byte[] b, int off, int len) throws IOException 从特定字节数组b将从off开始的len个字节写入输出流。 public abstract void write(int b) throws IOException 向输出流写一个特定字节。

8.2.3 系统输入输出对象 Java定义了两个流对象System.in和System.out,允许用户在自己的程序中直接使用。System.in对象允许用户从键盘读取数据,System.out对象可以产生屏幕输出。 【例8.1】使用流对象System.in和System.out,接收用户从键盘上输入的数据并将数据输出到屏幕上。测试情况如图8.1所示。源程序代码如下: //程序文件名为SystemIO.java import java.io.*; public class SystemIO {

public static void main(String[] args) { int bytes = 0; byte buf[] = new byte[255]; System.out.println("\n请输入任意文本:"); try //接收输入字符串 bytes = System.in.read(buf,0,255); System.out.println("这是你输入的文本行:"); String inStr = new String(buf,0,bytes);

//输出字符串 System.out.println(inStr); } catch(IOException e) { System.out.println(e.getMessage()); };

图8.1 例8.1的屏幕显示

8.3 文 件 处 理 类 8.3.1 FileInputStream类 8.3 文 件 处 理 类 8.3.1 FileInputStream类 FileInputStream(文件输入流)类是用来得到文件的输入字节流。大部分方法继承于InputStream类。它的构造方法如下: FileInputStream(File file) 通过打开一个到实际文件的链接,创建一个文件输入流,参数file是一个文件对象。 FileInputStream(String name) 通过打开一个到实际文件的链接,创建文件输入流,参数name为文件的实际路径。

【例8.2】使用FileInputStream对象打开源程序文件,源文件的输出如图8.2所示。源程序代码如下: //程序文件名为UseFileInputStream.java import java.io.*; public class UseFileInputStream { public static void main(String[] args) byte buf[] = new byte[2056]; try //构造文件输入流

FileInputStream fileIn = new FileInputStream("UseFileInputStream FileInputStream fileIn = new FileInputStream("UseFileInputStream.java"); //存入缓冲buf int bytes = fileIn.read(buf,0,2056); String inStr = new String(buf,0,bytes); //输出文件内容 System.out.println(inStr); } catch(IOException e) { System.out.println(e.getMessage()); };

图8.2 输出源文件内容

8.3.2 FileOutputStream类 FileOutputStream(文件输出流)类是将数据写入File或 FileDescriptor对象的输出流。它的方法大都是从OutStream继承来的,其构造方法如下: FileOutputStream(File file) 创建输出流写到特定的file对象。 FileOutputStream(File file, boolean append) 以追加的方式写入file对象。 FileOutputStream(FileDescriptor fdObj) 创建输出文件流到fdObj对象,代表一个到实际文件的链接。

FileOutputStream(String name) FileOutputStream(String name, boolean append) 是否以追加的方式写到指定的name文件。

【例8. 3】使用FileOutputStream对象,打开一个文件,写入一行文本,然后追加一行从键盘接收的字符串(如图8 //程序文件名为UseFileOutputStream.java import java.io.*; public class UseFileOutputStream { public static void main(String[] args) byte buf[] = new byte[255]; byte bufIn[] = new byte[255];

try { String str = "你好,这是已有的文本"; buf = str.getBytes(); //创建文件输出流对象 FileOutputStream fileOut = new FileOutputStream("Hello.txt"); //写入文件 fileOut.write(buf,0,buf.length); fileOut.flush(); fileOut.close();

System.out.println("\n请输入一行文本:"); //从键盘接收文本 int bytes = System.in.read(bufIn,0,255); //追加文本 fileOut = new FileOutputStream("Hello.txt",true); fileOut.write(bufIn,0,bytes); } catch(IOException e) { System.out.println(e.getMessage()); };

图8.3 程序运行和写入的文件内容

8.3.3 File类 用户接口和操作系统使用依赖于系统的路径字符串来命名文件和目录。File类就表示这些文件和目录路径,它代表一个抽象的依赖于系统的层次路径视图。File类允许用户向系统查询该文件的所有信息,也可以使用类来创建新的目录或者删除和重命名文件。 当用户需要获得有关文件的信息时,就需要创建一个File类,而当File类用于文件读写时,通常与FileInputStream流相结合。

【例8.4】创建一个临时文件,写入一行数据,然后删除。临时文件的周期说明如图8.4所示。源程序代码如下: //程序文件名为UseFile.java import java.io.*; public class UseFile { public static void main(String[] args) try

File f = new File("temp.txt"); System.out.println("创建临时文件"); FileOutputStream fout = new FileOutputStream(f); PrintStream p = new PrintStream(fout); p.println("将这句话放入临时文件"); System.out.println("写临时文件"); f.deleteOnExit(); System.out.println("删除临时文件"); } catch(IOException e) { System.out.println(e.getMessage()); };

图8.4 临时文件的周期说明

8.3.4 RandomAccessFile类 RandomAccessFile(随机访问文件)类的实例支持对随机访问文件的读/写。随机访问文件就像存储在文件系统中的巨大的字节数组,通过游标或者索引(叫做文件指示器)指向这个暗含的数组,输入操作从指示器处读取字节,然后前进指针。如果随机访问文件以可读/写模式创建,那么还支持输出操作,输出操作写到暗含数组的尾端,使得数组得以扩展。

【例8. 5】随机访问文件,将文件内容输出,并写入两个字符“O”、“K”。文件内容显示如图8 【例8.5】随机访问文件,将文件内容输出,并写入两个字符“O”、“K”。文件内容显示如图8.5的下半部分所示,若文件内容直接输出到屏幕上,则出现如图8.5上半部分所示的乱码问题,添加一个parseChinese方法后,得以修正,输出结果如图8.6所示。源程序代码如下: //程序文件名为UseRandom.java import java.io.*; public class UseRandom { public static void main(String[] args)

try { //构建随机访问文件对象 RandomAccessFile f = new RandomAccessFile("Hello.txt","rw"); //得到文件指针和长度 long flag = 0; long len = f.length(); //字符处理后输出 while(flag <len)

String s = f.readLine(); System.out.println(parseChinese(s)); flag = f.getFilePointer(); } //末尾写入字符 f.writeChar('O'); f.writeChar('K'); catch(IOException e) { System.out.println(e.getMessage());

//解决中文转换问题 public static String parseChinese(String inStr) { String s = null; byte temp[]; if (inStr == null) return new String(""); } try temp=inStr.getBytes("iso-8859-1");

s = new String(temp); } catch(UnsupportedEncodingException e) { System.out.println (e.toString()); return s; };

图8.5 文件内容及屏幕输出

图8.6 字符处理后的屏幕输出

8.4 对 象 流 8.4.1 ObjectInputStream类 8.4 对 象 流 8.4.1 ObjectInputStream类 ObjectInputStream(对象输入流)可读取使用对象输出流写入的原始数据和类型,与文件输入输出流一起可以实现对象的持久性存储。它的构造函数和一个读对象的方法如下: public ObjectInputStream(InputStream in) throws IOException 从特定的输入流中读取并创建一个对象输入流。 Public Object readObject() 从对象输入流中读取对象。

8.4.2 ObjectOutputStream类 ObjectOutputStream(对象输出流)可将Java的原始数据类型和图形写入输出流,对象可以使用对象输入流读取,使用文件可以实现对象的持久存储。它的构造函数和一个写对象方法如下: public ObjectOutputStream(OutputStream out) throws IOException 创建一个对象输出流,可以写入特定的输出流。 void writeObject(Object obj) 将对象obj写入对象输出流。

【例8. 6】将日期对象和向量对象写入文件,然后从文件中读出并输出到屏幕上,输出结果如图8 【例8.6】将日期对象和向量对象写入文件,然后从文件中读出并输出到屏幕上,输出结果如图8.7所示。要求向量对象含有三个值“语文”、“数学”和“物理”。源程序代码如下: //程序文件名为UseStream.java import java.io.*; import java.util.*; public class UseStream extends Object { public static void main(String[] args)

//构建Vector对象 Vector v = new Vector(); v.add("语文"); v.add("数学"); v.add("物理"); try { //文件处理对象 File f = new File("temp.txt"); FileOutputStream fOut = new FileOutputStream(f); ObjectOutputStream objOut = new

ObjectOutputStream(fOut); //写入日期对象 objOut.writeObject(new Date()); //写入Vector对象 objOut.writeObject(v); objOut.close(); //构建readObj的实例 readObj rObj = new readObj(); //调用方法输出 rObj.readO(); } catch(IOException e) {

System.out.println(e.getMessage()); } }; //自定义类,实现读取对象并输出 class readObj extends Object { public void readO() try //文件处理对象 File f = new File("temp.txt");

FileInputStream fIn = new FileInputStream(f); ObjectInputStream objIn = new ObjectInputStream(fIn); //读取对象输出 Object ob1 = objIn.readObject(); System.out.println(ob1); Object ob2 = objIn.readObject(); System.out.println(ob2); } catch(IOException e) {

System.out.println(e.getMessage()); } catch(ClassNotFoundException e) { };

图8.7 对象输出

习 题 1. 编写一个一位数算术表达式计算器,要求用户从命令提示符下输入表达式,如5*4,程序返回结果5*4 = 20。循环提示,直至用户输入“$”符号终止程序。【提示:程序解析表达式,得出操作数和操作符,进行运算后返回结果。】要求程序交互提示如下: 程序提示:请输入一位数表达式… 用户输入:3+2 程序返回:3+2 = 5 用户输入:$ 程序退出:退出系统

2. 编写应用程序,界面如图8.8所示。当用户在第一个文本框内填入文件的路径后单击“打开文件”按钮,文件的内容显示在下面的文本区域内,在文本区域内对文件做出修改后,单击“存储文件”按钮,则可以将文件存入原来的文件名中。 图8.8 用户界面

3. 编写应用程序,界面如图8.8所示,首先在文本框中输入一个新文件名称,单击“存储文件”按钮时,使用Hashtable对象存储以下键-值对 语文 89;数学 98;英语 90 并将此Hashtable对象写入“文件名”标识的新文件中,然后单击“打开文件”按钮,完成以下操作:打开文件,读出对象,在文本区域中顺序输出分数。

第9章 Java数据库技术 9.1 JDBC概述 9.2 使用JDBC 9.3 实例 习 题

9.1 JDBC概述 JDBC(Java Database Connection,Java数据库连接)是一种用于执行SQL语句的JavaAPI(应用程序设计接口),它由一些Java语言写的类和界面组成。JDBC提供了一种标准的应用程序设计接口,使得开发人员使用Java语言开发完整的数据库应用程序变得极为简单。通过JDBC,开发人员几乎可以将SQL语句传递给任何一种数据库,而无需为各种数据库编写单独的访问程序。JDBC可以自动将SQL语句传递给相应的数据库管理系统。

JDBC扩展了Java的功能,例如在Applet中应用JDBC,可以实现与远程数据库的连接,实现不同平台数据库之间的对话。简单地说,JDBC完成下面三个操作: (1) 与一个数据库建立连接。 Connection con = DriverManager.getConnection("jdbc:odbc:CallCenter","sa",""); (2) 向数据库发送SQL语句。 stmt = con.createStatement(); rs = stmt.executeQuery("SELECT CID,CPin from tCustomer WHERE CID='z1'");

(3) 处理数据库返回的结果。 while(rs.next()) { String theInt = rs.getString("CID"); String str = rs.getString("CPin"); ... }

9.2 使 用JDBC JDBC的接口分为两个层次:一个是面向程序开发人员的JDBC API;另外一个是底层的JDBC Driver API。JDBC API 被描述成为一组抽象的Java接口,应用程序可以对某个数据库打开连接,执行SQL语句并且处理结果。最重要的接口如下: java.sql.DriverManager:处理驱动的调入并且对产生新的数据库连接提供支持。 java.sql.Connection:代表对特定数据库的连接。 java.sql.Statement:代表一个特定的容器,以对一个特定的数据库执行SQL语句。 java.sql.ResultSet:控制对一个特定语句的行数据的存取。

其中java.sql.Statement又有两个子类型: (1) java.sql.PreparedStatement:用于执行预编译的SQL语句。 (2) java.sql.CallableStatement:用于执行对一个数据库内嵌过程的调用。 JDBC Driver API是指java.sql.Driver接口,封装了不同数据库的驱动程序(像Access、Foxpro、SQL Server等)。由于它是数据库底层处理,所以必须提供对java.sql.Connection、java.sql. Statement、java.sql.PreparedStatement和java.sql.ResultSet的实现。

如果目标DBMS提供有OUT参数的内嵌过程,那么还必须提供java.sql.CallableStatement 接口。在java.sql.Driver接口中每个数据库驱动程序必须提供一个类,使得系统可以由 java.sql.DriverManager来管理。一个比较好用的驱动程序是在ODBC之上提供对JDBC的实现,从而提供与ODBC接口的JDBC-ODBC 桥。所谓JDBC-ODBC桥,是一个JDBC驱动程序,通过将JDBC操作转换为ODBC操作来实现JDBC操作。它由sun.jdbc.odbc包实现,包含一个用来访问ODBC的本地库,对所有ODBC可用的数据库实现JDBC。

通过ODBC子协议,可以使用下面一行代码进行显示加载。 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); 加载时,ODBC驱动程序将创建自己的实例,同时在JDBC驱动程序管理器中进行注册。由于JDBC放在ODBC之后,所以实现起来简单且高效。

9.2.1 Driver Driver接口是每个驱动器类都需要完成的。JavaSQL框架允许有多个数据库驱动器,每个驱动器应该提供一个类来实现驱动器接口,而驱动器的装载通过DriverManager实例实现。 DriverManager将装载尽量多的驱动器,对每个给定的连接请求,将所有的驱动器依次连接到目标数据库上。当驱动器类装载后,Driver应该创建一个实例,然后注册到DriverManager上。

9.2.2 DriverManager DriverManager管理一系列JDBC驱动器的基本服务。应用程序可以显式加载JDBC驱动器。例如下面代码显式加载my.sql.Driver。 Class.forName("my.sql.Driver"); 显式加载JDBC-ODBC桥: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

它的较为重要的方法有: public static Connection getConnection(String url) throws SQLException public static Connection getConnection(String url, Properties info) throws SQLException public static Connection getConnection(String url, String user, String password) throws SQLException 这些方法的功能都是建立一个到给定数据库url的连接。DriverManager试图从注册的JDBC驱动器序列中选择合适的驱动器,返回到url的连接。

(1) url为数据库url,格式为 jdbc:subprotocol:subname。 其中: (1) url为数据库url,格式为 jdbc:subprotocol:subname。 (2) info以“标记/数值对”作为连接参数,至少应该包括user和password属性对。 (3) user指数据库用户(连接以什么身份建立)。 (4) password 是用户的密码。 例如:使用JDBC-ODBC桥建立到ODBC配置的数据库CallCenter的连接,访问CallCenter数据库的用户名为sa,密码无。语句行如下: Connection con = DriverManager.getConnection("jdbc:odbc:CallCenter","sa","");

9.2.3 Connection 一个Connection(连接)就是一个与特定数据库的会话。在连接的上下文环境中才可以执行SQL语句和返回结果。Connection对象的数据库可以提供描述它的表、SQL语法和存储过程等的信息。它较为重要的方法有: public Statement createStatement() throws SQLException 创建一个Statement对象,用于发送SQL语句到数据库。没有参数的SQL语句通常使用Statement对象执行。如果希望多次执行,使用PreparedStatement更为高效。

public PreparedStatement prepareStatement(String sql) throws SQLException 创建一个PreparedStatement对象,发送参数化SQL语句sql到数据库。 SQL语句可以预先编译并存储到PreparedStatement语句中。这个对象可以用来高效地多次执行语句。 其中:参数sql是包含多个“?”参数的SQL语句,“?”表示输入参数由用户进行设置。

例如: 创建Statement对象语句如下: stmt = con.createStatement(); 创建PreparedStatement对象语句如下: pstmt = con.prepareStatement("UPDATE Xuesheng SET 班级 = ? WHERE 班级 = ?");

9.2.4 Statement Statement对象用于执行一个静态的SQL语句并返回它产生的结果。在缺省情况下,任一时刻每个Statement对象只产生一个ResultSet集。对数据库希望有不同操作得到结果集时,需要创建不同的Statement对象。它的较为重要的方法有: public ResultSet executeQuery(String sql) throws SQLException 执行给定的sql语句,返回一个ResultSet对象。

public int executeUpdate(String sql) throws SQLException 执行给定的sql语句,可以是插入(INSERT)、更新(UPDATE)或者删除(DELETE)等,也可以是一个空语句,执行DDL语句。返回值是操作的记录个数。 public ResultSet getResultSet() throws SQLException 以ResultSet对象格式返回当前结果集,每个结果集只调用一次。 例如: 从表tCustomer中返回CID为z1的记录的CID(客户ID)和CPin(密码)列,语句行为: rs = stmt.executeQuery("SELECT CID,CPin from tCustomer WHERE CID= 'z1' ");

9.2.5 PreparedStatement PreparedStatement代表预编译的SQL语句的对象。一个SQL语句预编译后存储到PreparedStatement对象中,这个对象用来多次执行语句。PreparedStatement继承于Statement,扩展了Statement的用途,提高了Statement的执行效率。它与Statement对象有两点不同: (1) 同一个对象可以多次使用。 (2) 它的SQL语句可以带输入(IN)参数。

PreparedStatement在程序语句中的输入参数使用占位符“ PreparedStatement在程序语句中的输入参数使用占位符“?”来实现。必须使用类提供的设置方法设置语句中占位符的具体值,才能执行语句。如下面的程序段,根据ID的取值更新EMPLOYEES表中SALARY字段的取值,将第一个占位符代表的参数设置为10000.00,将第二个占位符代表的参数设置为111,语句执行的结果是EMPLOYEES表中ID为111的记录的SALARY取值为10000.00。

类PreparedStatement提供的常用方法如下: PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES SET SALARY = ? WHERE ID = ?"); pstmt.setBigDecimal(1, 10000.00) pstmt.setInt(2, 111); pstmt.executeUpdate(); 类PreparedStatement提供的常用方法如下: public boolean execute() throws SQLException 执行PreparedStatement对象中的任一类型的SQL语句。如果返回true,则调用getResultSet方法取得ResultSet集;如果返回false,则调用getUpdateCount方法获得更新数。

public ResultSet executeQuery() throws SQLException public int executeUpdate() throws SQLException 执行对象中的SQL语句。如果是一些更新操作,如插入(INSERT)、修改(UPDATE)和删除(DELETE )等,则返回操作的个数。

常用的设置方法为: public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException 在第parameterIndex位置设置BigDecimal型x。 public void setBoolean(int parameterIndex, boolean x) throws SQLException 在第parameterIndex位置设置布尔型x。 public void setByte(int parameterIndex, byte x) throws SQLException 在第parameterIndex位置设置字节型x。

public void setBytes(int parameterIndex, byte[] x) throws SQLException 在第parameterIndex位置设置字节数组型x。 public void setDouble(int parameterIndex, double x) throws SQLException 在第parameterIndex位置设置双精度型x。 public void setFloat(int parameterIndex, float x) throws SQLException 在第parameterIndex位置设置单精度型x。 public void setInt(int parameterIndex, int x) throws SQLException 在第parameterIndex位置设置整型x。

public void setLong(int parameterIndex, long x) throws SQLException 在第parameterIndex位置设置长整型x。 public void setNull(int parameterIndex, int sqlType) throws SQLException 在第parameterIndex位置设置为空x。 public void setObject(int parameterIndex, Object x) throws SQLException 在第parameterIndex位置设置对象x。 public void setShort(int parameterIndex, short x) throws SQLException 在第parameterIndex位置设置短整型x。

void setString(int parameterIndex, String x) throws SQLException 在第parameterIndex位置设置字符串型x。 public void setTime(int parameterIndex, Time x) throws SQLException 在第parameterIndex位置设置时间型x。 public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException 在第parameterIndex位置设置时间戳型x。

9.2.6 ResultSet Result对象是指一张数据表,代表数据库结果集,通常是通过执行查询数据库的语句而产生的。ResultSet对象持有一个游标,该游标指向当前数据行。初始化时游标定位到第一行之前。Next方法将游标移动到下一行,当对象行完时,返回错误。通常使用循环来完成每行的遍历。 public boolean next() throws SQLException 读行,返回true;行数完,则返回false。

例如,下面程序段完成对结果集的操作,对所有记录进行遍历并输出其中的字段CID和CPin的值。 while(rs.next()) { String theInt = rs.getString("CID"); String str = rs.getString("CPin"); System.out.println("CID: "+theInt+" CPin: "+str); }

结果集有一些方法用于对返回结果的具体字段进行读取,包括以字段编号为参数和以字段名称为参数的读取,其中以字段编号为参数的读取速度快一些,而以字段名称为参数的读取对用户来说更加方便。 所谓字段编号是指当前结果集中的第一个列字段编号为1,然后依次加1对剩余列进行编号;而字段名称是指列标题的名字。Get字段的类型同上述Set字段的类型一致。下面是以字段编号为参数的读取方法:

public BigDecimal getBigDecimal(int columnIndex) throws SQLException public boolean getBoolean(int columnIndex) throws SQLException public byte getByte(int columnIndex) throws SQLException public byte[] getBytes(int columnIndex) throws SQLException public double getDouble(int columnIndex) throws SQLException public float getFloat(int columnIndex) throws SQLException public Int getInt(int columnIndex) throws SQLException public Long getLong(int columnIndex) throws SQLException

public Object getObject(int columnIndex) throws SQLException public short getShort(int columnIndex) throws SQLException public String getString(int columnIndex) throws SQLException public java.sql.Time getTime(int parameterIndex, Time x) throws SQLException public java.sql.TimeStamp getTimestamp(int columnIndex) throws SQLException

以字段名称为参数的读取方法如下: public BigDecimal getBigDecimal(String columnName) throws SQLException public boolean getBoolean(String columnName) throws SQLException public byte getByte(String columnName) throws SQLException public byte[] getBytes(String columnName) throws SQLException public double getDouble(String columnName) throws SQLException public float getFloat(String columnName) throws SQLException public Int getInt(String columnName) throws SQLException

public Long getLong(String columnName) throws SQLException public Object getObject(String columnName) throws SQLException public short getShort(String columnName) throws SQLException public String getString(String columnName) throws SQLException public java.sql.Time getTime(String columnName) throws SQLException public java.sql.TimeStamp getTimestamp(String columnName) throws SQLException

9.3 实 例 【例9.1】给出一个完整的实例,包括建立所需用户数据库,配置ODBC数据源,编写访问数据库的程序,查看运行结果。其中访问数据库程序输出班级为“025”的记录,并将“025”修改为“计算机”。

9.3.1 建立用户数据库 建立FoxPro数据库,数据库名为Student.mdb,其中一个表为Xuesheng.dbf,记录如图9.1所示。 图9.1 表Xuesheng.dbf中的记录

9.3.2 配置ODBC数据源 在Windows 2000下配置ODBC数据源,首先找到程序→管理工具→数据源(ODBC),(在Windows 98下,可在控制面板中找到ODBC),调出“ODBC数据源管理器”,如图9.2所示。 图9.2 ODBC数据源管理器

点击“添加”按钮,出现 “创建新数据源” 窗口,如图9.3所示。 图9.3 为数据源选择驱动程序

在图9.3中选择“Microsoft Visual FoxPro Driver”选项,然后点击“完成”按钮,进入“ODBC Visual FoxPro Setup”窗口,如图9.4所示。

(1) “Data Source Name (数据源名)”:ODBC提供给应用程序的数据库名字。在图9.4中填为STU。 (2) “Description (描述)”:用来说明数据库的文字信息,可根据自己的需要填写。在图9.4中填为“用于JAVA程序的数据库测试”。 (3) “Path (路径)”是ODBC映射数据库的具体路径,可以直接填写,也可以单击“Browse”(浏览)按钮选择一数据库。这里选择的路径为E:\_Work\Java\STUDENT.DBC。 (4) 点击“OK (确定) ”按钮,可以看见“ODBC数据源管理器”窗口多出一个STU数据源。

9.3.3 数据库访问的步骤 编写数据库访问程序的步骤如下: (1) 引入java.sql的包。 import java.sql.*; (2) 声明变量。 Statement stmt; PreparedStatement pstmt; ResultSet rs;

(3) 加载驱动程序。 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); (4) 连接数据库。 String urlName = "jdbc:odbc:STU"; Connection con = DriverManager.getConnection(urlName,"","");

(5) 执行查询操作。 rs = stmt.executeQuery("SELECT 学号,姓名,班级 from Xuesheng WHERE 班级='025'"); ... pstmt = con.prepareStatement("UPDATE Xuesheng SET 班级 = ? WHERE 班级 = ?"); pstmt.setString(1,"计算机"); pstmt.setString(2,"025"); pstmt.executeUpdate(); (6) 关闭数据库。 con.close();

9.3.4 源程序代码 源程序代码如下: //程序文件名UseJDBC.java import java.sql.*; public class UseJDBC { public static void main(String args[]) try

Statement stmt; PreparedStatement pstmt; ResultSet rs; //加载JDBC-ODBC桥 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); String urlName = "jdbc:odbc:STU"; //连接STU数据库 Connection con = DriverManager.getConnection(urlName,"",""); //执行sql查询 stmt = con.createStatement();

rs = stmt.executeQuery("SELECT 学号,姓名,班级 from Xuesheng WHERE 班级='025'"); System.out.println("显示所有返回结果:"); //遍历结果集 while(rs.next()) { //得到记录值,输出 String strNumber = rs.getString("学号"); String strName = rs.getString("姓名"); String strClass = rs.getString(3);

System.out.println("学号: "+ strNumber +" 姓名: "+strName +" 班级: "+strClass); } //更新班级值 pstmt = con.prepareStatement("UPDATE Xuesheng SET 班级 = ? WHERE 班级 = ?"); pstmt.setString(1,"计算机"); pstmt.setString(2,"025"); pstmt.executeUpdate(); //关闭连接

con.close(); } catch(Exception e) { e.printStackTrace();

9.3.5 运行结果 在图9.5中,输出“班级”为“025”的记录中的“学号”、“姓名”和“班级”字段。图9.6为执行更新操作后的表记录。与图9.1相比,可以看到程序操作以前为“025”的“班级”的列值已经更新为“计算机”。 图9.5 界面输出

图9.6 程序访问后的Xuesheng表

习 题 1. 创建一个新的Access数据库或者其它类型的数据库,为之建立一个如图9.1所示的表stu。对数据库进行ODBC数据源配置,将它的名字配置成STUDENT。 2. 编写程序,访问习题1配置的STUDENT库中的表stu,输出所有女生的学号、姓名和班级,然后存到文件stu_female中。 3. 编写程序,访问习题1配置的STUDENT库中的表stu,将学号以“02037”开头的学生的班级更新为“英语班”。 4. 编写Applet,实现到习题1配置的STUDENT库中的表stu的访问,界面上使用文本区域按行输出所有男生的记录。

第10章 Java安全技术 10.1 简介 10.2 安全限制和许可 10.3 安全策略 (Policy) 10.4 辅助工具 10.1 简介 10.2 安全限制和许可 10.3 安全策略 (Policy) 10.4 辅助工具 10.5 签名及发布的例子 习 题

10.1 简 介 Java是网络上使用的编程语言,安全性是非常重要的,特别是Java平台的安全以及Java技术部署带来的安全问题,尤其值得认真考虑。Java中的安全包括两个方面: (1) 提供安全且易于构建的Java平台,能够以安全模式运行Java实现的应用程序。 (2) 提供用于编程语言的安全工具和服务,实现较广泛的安全。

Java平台提供的原始安全模型称为沙箱模型(JDK1 Java平台提供的原始安全模型称为沙箱模型(JDK1.0),该模型提供较窄环境——沙箱来运行没有得到信任的代码。在沙箱模型中允许得到信任的本地代码访问重要资源,而没有经过信任的远程代码只能访问沙箱内的很少部分资源。在JDK1.1中引入签名Applet的概念,如果签名的密钥由接收Applet的客户端认为是可信任的,那么这个经过正确数字签名的Applet就可以当作可信任本地代码访问重要资源。这里签名Applet和它的签名以JAR格式传送。随着发展,在原来沙箱模型的基础上引入新的安全体系,形成Java 2平台安全模型如图10.1所示。从图10.1中可以看出,不管本地还是远程,签名还是未签名的代码都统一到类加载器处,咨询安全策略,然后决定代码能够访问的资源。Java 2安全平台模型较之以前有了很大的改进,其主要特点如下:

(1) 细粒度的访问控制。 (2) 易于配置的安全策略。 (3) 易于扩展的访问控制结构。 (4) 安全检查扩展到所有Java程序,包括应用程序和Applet。

图10.1 Java 2平台安全模型

图10.1中的多个沙箱模型可以看作有固定边界的保护域。所谓保护域是指一个对象集合,这些对象可以由安全策略中定义的一条规则直接访问。保护域分为系统域和应用程序域,受保护的资源,像文件系统、网络设施以及屏幕和键盘,只允许系统域进行访问,而应用程序域可以通过授权许可访问受保护资源。类加载器将本地或远程代码(Applet)载入的同时,策略文件给出域的划分和不同代码对不同域访问权限的许可,载入的类就根据域划分和权限许可来访问相应的域资源。图10.2为运行中类到域再到许可的映射。

图10.2 类到域再到许可的映射

10.2 安全限制和许可 本地的代码类访问系统资源时通常不会受到太大的限制,所以本章主要讨论从服务器下载到客户端的远程代码Applet访问客户端资源的情况。 Applet访问客户端资源时,由于Java内嵌的平台安全性机制受到较大的限制,通常表现在无法读写客户端的文件,无法采集客户端音频。例如,当Applet实现的是客户端和服务器端进行语音聊天时,客户端采集音频就会受到限制,还有无法启动客户端的Socket进行传输等。下面看一个文件访问受到安全限制的例子。

【例10. 1】 编写一个用来读取客户端文件的Applet,客户端的文件路径及文件名为E:\a 【例10.1】 编写一个用来读取客户端文件的Applet,客户端的文件路径及文件名为E:\a.txt,文件内容为“你好,这是客户端的测试文件!”,如图10.3右部分所示。将读出的文件内容显示在文本区域内,如果访问出错,异常信息也显示在文本区域内。 //程序文件名:AppletSecurity.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.io.*; public class AppletSecurity extends Applet {

TextField fileNameField; TextArea fileArea; public void init() { Label lblName=new Label("文件名:"); Label lblContext = new Label("文件内容:"); fileNameField=new TextField(35); fileNameField.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ loadFile(fileNameField.getText()); }}); fileArea=new TextArea(10,35);

add(lblName); add(fileNameField); add(lblContext); add(fileArea); } public void loadFile(String fileName) { try BufferedReader reader=new BufferedReader(new FileReader(fileName)); String context = new String();

while((context = reader.readLine())!=null) { fileArea.append(context + "\n"); } reader.close(); catch(IOException ie) fileArea.append("IO错误:" + ie.getMessage());

catch(SecurityException se) { fileArea.append("安全访问错误:" + se.getMessage()); }

程序编写后,编译生成相应的类,将类嵌入HTML文件,从本地服务器加载,在载入Applet的界面上输入文件名E:\a 程序编写后,编译生成相应的类,将类嵌入HTML文件,从本地服务器加载,在载入Applet的界面上输入文件名E:\a.txt后按回车键,在界面的文本区域内并没有显示相应a.txt的内容,只是提示“access denied (java.io.FilePermission E:\a.txt read)”,表示访问拒绝,如图10.3左部分所示。 图10.3 Applet访问文件出错显示和客户端文件内容

Java安全平台中受到的种种安全限制,在Java中都提供了一一对应的许可,例如对于读、写文件的限制,Java提供了java. io Java安全平台中受到的种种安全限制,在Java中都提供了一一对应的许可,例如对于读、写文件的限制,Java提供了java.io.FilePermission来许可对客户端文件的读、写等操作。下面看一下这些许可类。 许可类代表对系统资源的访问权限。Java.security.permission类是抽象类,划分为多个子类来代表特定的访问。而不同的许可类属于不同的包,如FilePermission类属于java.io包,而SocketPermission类属于java.net包。目前Java系统内嵌的主要的许可类如表10.1所示。

表10.1 Java内嵌的许可类

建立这些类的对象就可以产生许可。例如,下面的代码用来产生许可——读取/tmp目录下名为Hello的文件: filePerm = new java.io.FilePermission("/tmp/Hello","read");

10.3 安 全 策 略 (Policy) 1. keystore条目 keystore用来存放密钥对和相关数字证书。数字证书像X.509证书链用来鉴别相应的公有密钥。keytool工具用来创建和管理keystore。Policy配置文件中指定keystore,用来查找grant条目中签名者的公有密钥。如果存在指明签名的grant条目,那么必须存在相应的keystore。Keystore条目的格式为: keystore "url","type"

其中: (1) keystore是保留字,表示keystore条目。 (2) url指kestore的URL地址。 (3) type指keystore的类型,用于定义keystore信息的存储和数据格式,以及保护keystore中的私有密钥和keystore完整性算法。通常情况下缺省类型为“JKS”。

2. grant条目 policy对象中含有0到多条grant条目,指明远程代码访问特定资源的相关许可。grant条目的格式如下: grant signedBy "name" codeBase "url" { Permission permission-class-name "target-name"," action-name "; }

其中: (1) 每个grant条目为由name签名且来源于codeBase的类的访问提供一系列许可permission-class-name。 (2) grant为保留字,表示一条授权。 (3) signedBy为保留字,指明签名者。 (4) name为数字签名的作者名。 (5) codeBase为保留字,指明代码来源。 (6) url为指定代码的来源路径。

(7) Permission为保留期,指明许可名字及许可操作。 (8) permission-class-name指许可类名。 (9) target-name为受保护资源的名字,如文件目录。 (10) action-name 为对受保护资源进行操作的权限。

例如,下面为两条具体的grant条目,第一条表示允许lihua签名的网址http://192. 100. 100 grant signedBy "lihua" codeBase "http://192.100.100.43:8080/"{ Permission java.io.FilePermission "tmp/*","read,wirte"; }; grant codeBase "file:${java.home}/lib/ext/*" { permission java.security.AllPermission;

10.4 辅 助 工 具 10.4.1 密钥和证书管理工具 keytool为密钥和证书管理工具。它使得用户可以管理他们自己的公共密钥和私有密钥对以及相关的证书,用于在数字签名中进行数据完整性验证和身份验证。 keytool将密钥对和证书存放在keystore中,keystore通常以文件的形式存在。创建keystore时需要为它设置密码,还要为其中的私有密钥设置密码。命令提示符状态下键入不带参数的命令keytool,可以看见它的用法,如图10.4所示。

图10.4 keytool的用法

例如,在命令行提示符状态下键入如下命令行,生成UseImage 例如,在命令行提示符状态下键入如下命令行,生成UseImage.keystore文件,密钥和keystore的密码均为xueliang,显示结果如图10.5所示。 keytool -genkey -alias UseImage -keypass xueliang -keystore UseImage.keystore -storepass xueliang 其中: (1) -genkey:选项,表示生成新的密钥。 (2) -alias:别名选项,表示紧跟的参数为别名具体值:UseImage。

(3) -keypass:密钥的密码选项,表示紧跟的参数为密钥的密码具体值:xueliang。 (4) -keystore:选项,表示紧跟的参数为生成的keysotre文件名称:UseImage.keystore。 (5) -storepass:keystore密码的选项,表示紧跟的参数为keystore文件的密码具体值:xueliang。

图10.5 生成文件UseImage.keystore

而要将公共密钥导入证书,则需要键入如下代码,生成UseImage.cer证书文件,结果如图10.6所示。 keytool -export -alias UseImage -file UseImage.cer -keystore UseImage.keystore -storepass xueliang 图10.6 生成证书文件UseImage.cer

10.4.2 签名和校验工具 jarsigner用来对JAR文件进行数字签名和校验,这个过程基于keytool生成的keystore。在命令提示符状态下键入jarsigner后按回车键,可以看见用法及选项说明,如图10.7所示。 图10.7 jarsigner工具的用法及选项

常用的签名格式为: jarsigner -keystore keysotre-file -storepass keystore-password jar-file alias 其中: (1) keystore-file为keytool生成的keystore文件。 (2) keystore-password为keystore的密码。 (3) jar-file文件为档案文件而且只能为档案文件。 例如: jar cvf UseImage.jar UseImage.class index_01.gif;//生成JAR文件 jarsigner -keystore UseImage.keystore -storepass xueliang UseImage.jar UseImage//进行签名

10.4.3 PolicyTool Policytool是图形用户界面工具,可帮助用户指定、生成、编辑一个安全策略。命令提示符状态下键入PolicyTool后按回车键,就可调出此图形界面。根据界面指示,可以实现一个policy文件的各种操作。下面来看一下系统默认的policy文件,将D:\j2sdk1.4.0_01\jre\lib\security目录下的java.policy文件拷贝到E:\_Work\Java\sample目录下,在命令行状态运行命令policytool,弹出“规则工具”对话框。单击“文件”菜单中的“打开”项,选择java.policy文件并打开,如图10.8所示。从图10.8中可以看出允许两个CodeBase来源的远程代码。

图10.8 使用policytool工具打开java.policy文件

选中第一个规则项目“CodeBase "file:${java. home}/lib/ext/ 选中第一个规则项目“CodeBase "file:${java.home}/lib/ext/*"”,单击“编辑规则项目”按钮,弹出一个“规则项目”的窗口,如图10.9所示,可以看见签名项(SignedBy:)为空,而权限一栏中为 permission java.security.AllPermission; 表示允许这个规则项目下的所有远程代码对系统的所有资源进行访问。

图10.9 全部授权的权限列表

选中第二个规则项目“CodeBase <ALL>”,单击“编辑规则项目”按钮,弹出如图10

图10.10 所有远程代码访问的默认权限列表

这个默认的java.policy文件对应的源文件如下: // Standard extensions get all permissions by default grant codeBase "file:${java.home}/lib/ext/*" { permission java.security.AllPermission; }; // default permissions granted to all domains

// Allows any thread to stop itself using the java.lang.Thread.stop() grant { // Allows any thread to stop itself using the java.lang.Thread.stop() // method that takes no argument. // Note that this permission is granted by default only to remain // backwards compatible. // It is strongly recommended that you either remove this permission // from this policy file or further restrict it to code sources // that you specify, because Thread.stop() is potentially unsafe.

// See "http://java.sun.com/notes" for more information. permission java.lang.RuntimePermission "stopThread"; // allows anyone to listen on un-privileged ports permission java.net.SocketPermission "localhost:1024-", "listen"; // "standard" properies that can be read by anyone permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.vendor", "read"; permission java.util.PropertyPermission "java.vendor.url", "read";

permission java.util.PropertyPermission "java.class.version", "read"; permission java.util.PropertyPermission "os.name", "read"; permission java.util.PropertyPermission "os.version", "read"; permission java.util.PropertyPermission "os.arch", "read"; permission java.util.PropertyPermission "file.separator", "read"; permission java.util.PropertyPermission "path.separator", "read"; permission java.util.PropertyPermission "line.separator", "read";

permission java. util. PropertyPermission "java. specification permission java.util.PropertyPermission "java.specification.version", "read"; permission java.util.PropertyPermission "java.specification.vendor", "read"; permission java.util.PropertyPermission "java.specification.name", "read"; permission java.util.PropertyPermission "java.vm.specification.version", "read"; permission java.util.PropertyPermission "java.vm.specification.vendor", "read"; permission java.util.PropertyPermission "java.vm.specification.name", "read"; permission java.util.PropertyPermission "java.vm.version", "read"; permission java.util.PropertyPermission "java.vm.vendor", "read"; permission java.util.PropertyPermission "java.vm.name", "read"; };

10.5 签名及发布的例子 10.5.1 步骤 在命令提示符状态下进入路径D:\Apache Tomcat 4.0\webapps\ROOT\user,所有操作都在服务器路径下操作,也可以在普通路径下操作,完成后将一系列文件配置到服务器端。本书作者使用前者,在user目录下建立AppletSecurity.java文件,源代码即为例10.1,编译后生成三个类:AppletSecurity.class、AppletSecurity$1.class和AppletSecurity$2.class。 (1) 将生成的类文件打包成文档文件。 jar cvf a.jar *.class

(2) 为刚才创建的文件创建keystore。 keytool -genkey -alias a -keypass xueliang -keystore a.keystore -storepass xueliang (3) 使用刚才生成的钥匙来对jar文件进行签名。 jarsigner -keystore a.keystore -storepass xueliang a.jar a (4) 将公共钥匙导入到一个cer文件中。 keytool -export -alias a -file a.cer -keystore a.keystore -storepass xueliang

(5) 网页转换及修改。 嵌入类的index.html写成: <html> <body> <APPLET CODE = "AppletSecurity.class" ARCHIVE = "a.jar" WIDTH = 400 HEIGHT = 300> </APPLET> </body> </html>

使用htmlConverter工具,将index.html转换成使用插件的文件。源文件如下: <body> <!--"CONVERTED_APPLET"--> <!-- HTML CONVERTER --> <OBJECT classid="clsid:CAFEEFAC-0014-0000-0001-ABCDEFFEDCBA" WIDTH = 300 HEIGHT = 200 codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_01-win.cab#Version=1,4,0,10"> <PARAM NAME = CODE VALUE = "AppletSecurity.class" > <PARAM NAME = ARCHIVE VALUE = "a.jar" >

<PARAM NAME="type" VALUE="application/x-java-applet;jpi-version=1.4.0_01"> <PARAM NAME="scriptable" VALUE="false"> </OBJECT> <!-- <APPLET CODE = "AppletSecurity.class" ARCHIVE = "a.jar" WIDTH = 300 HEIGHT = 200> </APPLET> --> <!--"END_CONVERTED_APPLET"--> </body> </html>

10.5.2 结果 通过IE访问index.html,在类加载过程中,弹出一个“Java安全警告”窗口,如图10.11所示。 图10.11 证书的安全警告窗口

单击“授予该会话”按钮,则出现Applet界面,如图10. 12所示,在文件名栏敲入E:\a. txt,然后按回车键以显示文本内容,如图10

图10.13 显示的文本内容

习 题 1. 编写Applet,在客户端建立一个文件,测试Applet的安全限制和许可访问类,要求文件路径为“D:\Hello.txt”,并存入数据“你好,欢迎来到Java世界”。编写的界面如图10.14所示,文件名中写入路径,文件内容中写入数据,单击“存储”按钮,将数据存入客户端文件,单击“打开”按钮,又可以显示内容,将可能出现的异常输出到文本区域内。 2. 使用PolicyTool工具建立一个new.policy文件,要求对网址http://192.100.100.43::79端口下所有访问进行许可授权。 3. 为习题1的Applet加入数字签名,并进行访问。

图10.14 用户界面

第11章 Java网络技术(一) 11.1 TCP Sockets基础 11.2 UDP Sockets基础 11.3 网页显示控件 习 题

11.1 TCP Sockets基础 Sockets是一个编程抽象概念,它是网络上与另一个应用程序通信连接的句柄。Sockets编程将用户代码与TCP/IP协议堆栈的底层实现隔离开,允许用户灵活地实现自己的编程。 基于TCP协议的TCP Sockets需要四个方面的数据: (1) 本地系统的IP地址。 (2) 本地应用程序正在使用的TCP端口号。 (3) 通信的远程系统的IP地址。 (4) 通信的远程系统响应的TCP端口号。

TCP Sockets的应用模型通常是客户/服务器模型,一个服务器在一个特定端口等待远程计算机请求资源,给予响应。客户程序绑定到这个端口,建立一个可靠连接来用于通信。 面向连接的操作使用TCP协议,在这个模式下的Socket必须在发送数据之前与目的地的Socket取得一个连接。连接建立后,Socket就可以使用一个流接口:打开—读—写—关闭。所有发送的信息都会在另一端以同样的顺序被接收。面向连接的操作比无连接的操作效率低,但是数据的安全性更高。由于Socket使用是双方的,所以在客户端和服务器端的使用稍有不同。

(1) 客户端请求Socket的步骤如下: ① 建立客户端Socket连接; ② 得到Socket的读和写的流; ③ 利用流; ④ 关闭流; ⑤ 关闭Socket。 使用一个服务器端的Socket请求比使用一个客户端Socket请求要麻烦一些。服务器并不是主动地建立连接。相反地,服务器被动地监听客户端的连接请示,然后给它们服务。

(2) 服务器端Socket要完成以下的基本的步骤: ② 使用accept()方法取得新的连接; ③ 建立输入和输出流; ④ 在已有的协议上产生会话; ⑤ 关闭客户端流和Socket; ⑥ 回到②或者转到⑦; ⑦ 关闭服务器Socket。

图11.1 面向连接的Socket协作流程图

例如,HTTP请求通过服务器的80端口实现,服务器端类似于Socket监听,端口号为80。如果客户端希望通过应用程序获得网页,只需采用面向连接的Socket,请求80端口,获取网页数据。首先打开一个连接,请求主机为服务器网址,请求端口号为80,然后发送命令请求“GET / HTTP/1.0\r\n\r\n”,得到返回的网页。

11.1.1 InetAddress类 InetAddress对象表示一个Internet主机地址。这个主机地址可以通过名字和IP地址来标识。名字即主机名,例如本机名字为snowing,为字符串类型;IP地址为192.100.100.43,为四字节的数字,书写形式为a.b.c.d。InetAddress类的几个常用方法如下: public static InetAddress getByName(String host) throws UnknownHostException 通过名字可以得到主机的IP地址。

public String getHostAddress() 返回IP地址的字符串格式。 public String getHostName() 返回IP地址的主机名。 public static InetAddress getLocalHost() throws UnknownHostException 以InetAddress类封装的格式返回本机地址。 public String toString() 转换成字符串格式。

【例11.1】InetAddress对象应用的测试。获取本机地址并转换成字符串输出,输出本地主机名和IP地址以及通过局域网内计算机的名字得到它的IP地址。程序源代码如下: //程序文件名FindHost.java import java.net.*; public class FindHost { public static void main(String[] args) try

InetAddress h = InetAddress.getLocalHost(); System.out.println("toString():" + h.toString()); System.out.println("getHostName():" +h.getHostName()); System.out.println("getHostAddress():"+h.getHostAddress()); h = InetAddress.getByName("engine"); System.out.println(h.getHostName() +":" + h.getHostAddress()); } catch(UnknownHostException e) { System.out.println(e.getMessage());

编译后生成FindHost. class文件,运行后输出结果界面如图11. 2所示。由图11 编译后生成FindHost.class文件,运行后输出结果界面如图11.2所示。由图11.2可以看出本地主机名为snowing,IP地址192.100.100.43;engine为局域网内另一台计算机的名字,可以得到它的IP地址为192.100.100.186。

图11.2 程序结果输出界面

11.1.2 Socket类 Socket类用来实现客户端的Socket。常用的构造方法有以下三种: public Socket() 创建一个无连接的Socket。 public Socket(InetAddress address, int port) 创建流式Socket,将它连接到InetdAdress类指明的主机和port端口上。 public Socket(String host, int port) 创建流式Socket并将它连接到特定host的特定port端口上。

【例11.2】 建立客户端程序,访问网址http://www.xjtu.edu.cn,返回网址的首页写入文件xjtu.html。 1. 程序建立的步骤 (1) 建立到http://www.xjtu.edu.cn且端口为80的Socket连接。 Socket clientSocket = new Socket("www.xjtu.edu.cn", 80);

(2) 初始化字节流。连接建立后的数据传输通过数据输入输出流实现,写文件又通过文件输入输出流来实现。各种流对象的初始化如下: DataOutputStream outbound = new DataOutputStream(clientSocket.getOutputStream()); DataInputStream inbound = new DataInputStream(clientSocket.getInputStream()); InputStreamReader inS = new InputStreamReader(inbound); File f = new File("xjtu.html"); FileOutputStream fOut = new FileOutputStream(f); PrintStream p = new PrintStream(fOut);

(3) 发送请求。 outbound.writeBytes("GET / HTTP/1.0\r\n\r\n"); (4) 返回数据后,循环写入文件。 int c; while((c = inS.read()) != -1) p.print((char)c);

(5) 关闭流。 inS.close(); outbound.close(); inbound.close(); clientSocket.close();

2. 程序源文件 //程序文件名ReadClient.java import java.io.*; import java.net.*; public class ReadClient { public static void main(String args[]) try

//初始化Socket对象 Socket clientSocket = new Socket("www.xjtu.edu.cn", 80); System.out.println("Client1: " + clientSocket); //初始化流对象 DataOutputStream outbound = new DataOutputStream( clientSocket.getOutputStream() ); DataInputStream inbound = new DataInputStream( clientSocket.getInputStream() ); InputStreamReader inS = new InputStreamReader(inbound); File f = new File("xjtu.html");

FileOutputStream fOut = new FileOutputStream(f); PrintStream p = new PrintStream(fOut); outbound.writeBytes("GET / HTTP/1.0\r\n\r\n"); //写入文件 int c; while((c = inS.read()) != -1) p.print((char)c); //关闭流 inS.close(); outbound.close(); inbound.close(); clientSocket.close();

} catch (UnknownHostException uhe) { System.out.println("UnknownHostException: " + uhe); catch (IOException ioe) System.err.println("IOException: " + ioe);

3. 输出结果 图11.3为程序输出的xjtu.html,而图11.4为网址http://www.xjtu.edu.cn的首页,可以看出程序输出的网页图形丢失,这是因为此处的数据输入输出流只实现文本的读取,而并未考虑图形的处理。

图11.3 程序输出的xjtu.html

图11.4 网址http://www.xjtu.edu.cn的首页

11.1.3 ServerSocket类 ServerSocket类实现服务器Socket,服务器Socket等待从网络到达的请求,基于这些请求完成操作,然后返回相应的结果给请求者。ServerSocket类有一个重要的方法: public Socket accept() 用户监听并接收一个连接。 【例11.3】编写TCP通信程序,服务器端发送字符串到客户端。

1. 服务器端程序建立的步骤 (1) 创建服务器Socket(端口82,限制5个连接)和接收客户连接的Socket。 serverSocket = new ServerSocket(82, 5); Socket clientSocket = new Socket(); (2) 等待客户端连接。 clientSocket = serverSocket.accept();

(3) 初始化输入输出流。 DataInputStream inbound = new DataInputStream( client.getInputStream()); DataOutputStream outbound = new DataOutputStream( client.getOutputStream());

(4) 当接收到“hello”字符串时,输出字符串“http://www.xjtu.edu.cn/”。 StringBuffer buffer = new StringBuffer("\"http://www.xjtu.edu.cn/\"\r\n"); String inputLine; while ((inputLine = inbound.readLine()) != null) { if ( inputLine.equals("hello") ) outbound.writeBytes(buffer.toString()); break; }

(5) 关闭流以及Socket。 outbound.close(); inbound.close(); clientSocket.close();

2. 服务器端程序源文件 //程序文件名SimpleWebServer.java import java.io.*; import java.net.*; public class SimpleWebServer { public static void main(String args[]) { ServerSocket serverSocket = null; Socket clientSocket = null; int connects = 0; try

//创建服务器Socket,端口82,限制5个连接 serverSocket = new ServerSocket(82, 5); while (connects < 5) { //等待连接 clientSocket = serverSocket.accept(); //操作连接 ServiceClient(clientSocket); connects++; } serverSocket.close();

catch (IOException ioe) { System.out.println("Error in SimpleWebServer: " + ioe); } public static void ServiceClient(Socket client) throws IOException DataInputStream inbound = null; DataOutputStream outbound = null; try //获取输入输出流 inbound = new DataInputStream( client.getInputStream());

outbound = new DataOutputStream( client.getOutputStream()); StringBuffer buffer = new StringBuffer("\"http://www.xjtu.edu.cn/\"\r\n"); String inputLine; while ((inputLine = inbound.readLine()) != null) { //判断输入为hello时输出字符串 if ( inputLine.equals("hello") ) outbound.writeBytes(buffer.toString()); break;

} finally { //打印清除连接,关闭流及Socket System.out.println("Cleaning up connection: " + client); outbound.close(); inbound.close(); client.close();

3. 客户端程序 //程序文件名为ReadClient.java import java.io.*; import java.net.*; public class ReadClient { public static void main(String args[]) try //与服务器建立连接

Socket clientSocket = new Socket("192.100.100.43",82); System.out.println("Client1: " + clientSocket); //初始化流对象 DataOutputStream outbound = new DataOutputStream( clientSocket.getOutputStream() ); DataInputStream inbound = new DataInputStream( clientSocket.getInputStream() ); InputStreamReader inS = new InputStreamReader(inbound); //发送数据 outbound.writeBytes("hello\r\n");

System.out.println("hello"); outbound.flush(); int c; while((c = inS.read()) != -1) { System.out.print((char)c); } catch (UnknownHostException uhe) System.out.println("UnknownHostException: " + uhe);

catch (IOException ioe) { System.err.println("IOException: " + ioe); } finally //关闭流及clientSocket inS.close(); outbound.close(); inbound.close(); clientSocket.close();

4. 通信结果输出 通信结果如图11.5所示。上面的部分为服务器端的输出,清除每次连接的Socket,下面的部分为客户端显示,首先打印客户端Socket情况,然后打印发送的hello数据,最后输出从服务器端返回的字符串。

图11.5 基于TCP的Socket通信结果输出界面

11.2 UDP Sockets 基 础 数据报Socket是包发送服务的接收和发送点,它接收和发送的每个包都是单独访问和路由的。从一个机器发送到另一个机器的包可能有不同的路由,而且可能以任意顺序到达。数据报Socket构造函数有三个,分别如下: public DatagramSocket() 构建数据报Socket,绑定到本地主机上的任意端口。 public DatagramSocket(int port) 构建一个数据报Socket,将它绑定到指定的port端口。

public DatagramSocket(int port, InetAddress laddr) 构建一个数据报Socket,绑定到指定的laddr本地地址和port端口。 数据报可以通过connect方法建立连接,也可以直接使用send方法发送数据,使用receive方法接收数据,方法如下: public void connect(InetAddress address, int port) 将建立的数据报Socket连接到远程地址address的port端口。 public void receive(DatagramPacket p) 接收数据报包p。 public void send(DatagramPacket p) 发送数据报包p。

11.2.1 DatagramPacket类 从TCP的例子程序可以看出TCP Socket发送的数据是流式数据,而UDP Socket发送的数据是分块的数据包,这就用到DatagramPacket类。数据报Socket传输数据前首先构造数据报包,然后传输这个数据报包。数据报包的构造函数如下: DatagramPacket(byte[] buf, int length) 构建数据报包,接收length长度的包并放入buf。 DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构建数据报包,发送length长度的包到address指定的主机的port端口。

【例11.4】客户端循环发送数据“Client:Hello”到服务器端,服务器端截取字符串Hello输出,并将源字符串返回给客户端。 1. 程序建立的步骤 (1) 服务器端程序建立的步骤。 ●建立数据报Socket。 DatagramSocket mysock = new DatagramSocket(1719); ● 建立数据报包,准备接收。 byte[] buf = new byte[1024]; DatagramPacket p = new DatagramPacket(buf,buf.length); ●接收或者发送。 mysock.receive(p); ... mysock.send(p);

(2) 客户端程序建立的步骤。 ● 建立数据报Socket。 DatagramSocket mysock = new DatagramSocket(); ● 建立数据报包,准备接收。 DatagramPacket p = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),1719); ● 接收或者发送。 mysock.receive(p); ... mysock.send(p);

2. 服务器端源程序 //程序文件名为DatagramServer.java import java.net.*; public class DatagramServer extends Object { public static String ReadRAS(String str) int indexstart = str.indexOf(':'); int indexend = str.indexOf("o"); String Msg = "Server:" +

str.substring(indexstart+1,indexend+1); return Msg; } public static void main(String[] args) { try DatagramSocket mysock = new DatagramSocket(1719); byte[] buf = new byte[1024]; DatagramPacket p = new DatagramPacket(buf,buf.length); while(true)

mysock.receive(p); System.out.println("RECEIVE:"+ new String(p.getData()).trim()); System.out.println(ReadRAS(new String(p.getData()).toString())); mysock.send(p); System.out.println("SEND:"+ new String(p.getData()).trim()); } catch(Exception e) { System.out.println(e.getMessage());

3. 客户端源程序 //程序文件名为DatagramClient.java import java.net.*; import java.io.*; public class DatagramClient extends Object { public static void main(String[] args) try

DatagramSocket mysock = new DatagramSocket(); byte[] buf = new byte[1024]; String sendData = new String("Client:Hello"); buf = sendData.getBytes(); DatagramPacket p = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),1719); while(true) { mysock.send(p); System.out.println("SEND:"+ new String(p.getData()).trim());

mysock.receive(p); System.out.println("GET:"+ new String(p.getData()).trim()); } catch(Exception e) { System.out.println(e.getMessage());

4. 通信结果显示 如图11.6所示,上面的一部分为客户端,SEND标志发送数据“Client:Hello”,下面部分为服务器端,可以看见RECEIVE标志的接收到的字符串为客户端发送的数据,然后服务器端将接收到的数据进行截取,显示Hello字符串,前面加了Server标志,最后将原数据返回,在客户端又显示GET标志的接收到的字符串。

图11.6 数据报Socket通信结果输出界面

11.2.2 音频采集、播放实例 【例11.5】下面给出一个音频采集与播放实例,使用UDP Socket和多线程技术实现局域网内部的声音采集与传输。实例采用对等访问模式,使用时只需输入Java UDPTalk “对方服务器IP地址”,然后单击“通话”按钮即可,如图11.7所示。本例是Applet和应用程序共用的,如果通过Applet进行访问,需要获取SERVER_NAME参数,或者在程序内写成定值。另外,还需为此Applet进行数字签名,否则无权在客户端的机器上进行音频采集。

//程序文件名为TalkSelf.java import java.io.*; import java.applet.*; import java.applet.Applet; import java.awt.*; import java.awt.event.*; import javax.sound.sampled.*; import java.net.*; public class TalkSelf extends Applet implements Runnable, ActionListener {

private static String SERVER_NAME;// = "192.100.100.42"; private static int SRTPPort = 1734; private static int MAXBUFFER = 8192; final int bufSize = 16384; private static boolean PlayButton = false;//是否播放按钮 private static boolean keepRunning = true;//控制循环 private boolean bRun = false;//控制播放 private Thread TalkThread; private static PlaybackAudio PlayAudio; //播放音频实例 private static CaptureAudio CapAudio;//采集音频实例 Button PlayBtn; //通话按钮 Button CaptureBtn;//停止按钮

//初始化界面 public void init() { PlayBtn = new Button("通话"); PlayBtn.addActionListener(this); add(PlayBtn); CaptureBtn = new Button("停止"); CaptureBtn.addActionListener(this); add(CaptureBtn); }

//启动线程 public void start() { if(TalkThread == null) TalkThread = new Thread(this); TalkThread.start(); } //响应动作事件 public void actionPerformed(ActionEvent evt) String btnCaption = evt.getActionCommand(); if(btnCaption.equals("通话"))

PlayButton = true; } if(btnCaption.equals("停止")) { PlayButton = false; bRun = false; //实现run函数 public void run() while(keepRunning) if(PlayButton && !bRun)

{ try bRun = true; CapAudio = new CaptureAudio(SERVER_NAME, SRTPPort); CapAudio.start(); PlayAudio = new PlaybackAudio(SRTPPort); PlayAudio.start(); } catch(Exception e)

System.out.println(e.getMessage()); } //音频播放类 class PlaybackAudio extends Thread implements Runnable { SourceDataLine line; Thread thread; DatagramSocket s; //数据报Socket DatagramPacket p; //数据报包 int RTPPort; //构造函数:取得接收端口

PlaybackAudio(int RTPPort) { this.RTPPort = RTPPort; } public void start() thread = new Thread(this); thread.setName("PlaybackAudio"); thread.start(); public void run() AudioFormat format =new

AudioFormat(8000,16,2,true,true);//设置音频格式 DataLine.Info info = new DataLine.Info(SourceDataLine.class,format);//设置数据线 try { line = (SourceDataLine)AudioSystem.getLine(info);//获得源数据线信息 line.open(format, bufSize);//打开源数据线 } catch (LineUnavailableException ex) return;

byte[] data = new byte[MAXBUFFER]; int numBytesRead = 0; line.start();//启动源数据线 try { s = new DatagramSocket(RTPPort); } catch (IOException e) return; while (thread != null)

try { p = new DatagramPacket(data, data.length); s.receive(p);//接收包 numBytesRead = p.getLength(); line.write(p.getData(), 0, numBytesRead);//写入源数据线 } catch(IOException e) break;

s.close();//关闭数据报Socket if (thread != null) { line.drain(); } line.stop(); line.close(); line = null; //音频采集类 class CaptureAudio extends Thread implements Runnable

TargetDataLine line; Thread thread; DatagramSocket s; DatagramPacket p; String SERVER_NAME; int RTPPort; //构造函数:取得远程服务器名和端口,用于发送数据 CaptureAudio(String SERVER_NAME, int RTPPort) { this.SERVER_NAME = SERVER_NAME; this.RTPPort = RTPPort; } public void start()

thread = new Thread(this); thread.setName("CaptureAudio"); thread.start(); } public void run() { AudioFormat format =new AudioFormat(8000,16,2,true,true);//AudioFormat(float sampleRate, int sampleSizeInBits, int channels, boolean signed, boolean bigEndian) DataLine.Info info = new DataLine.Info(TargetDataLine.class,format);

try { line = (TargetDataLine)AudioSystem.getLine(info);//设置目标数据线信息 line.open(format,line.getBufferSize()); //打开目标数据线 } catch (Exception ex) return; byte[] data = new byte[MAXBUFFER]; int numBytesRead=0; line.start(); //启动目标数据线

{ //构造数据报Socket s = new DatagramSocket(); } catch (Exception ex) return; while (thread != null) try numBytesRead = line.read(data, 0, MAXBUFFER);//读取采集数据

p = new DatagramPacket(data, numBytesRead, InetAddress.getByName(SERVER_NAME), RTPPort); s.send(p);//发送数据 } catch (Exception ex) { break; s.close(); line.stop(); line.close(); line = null;

//主函数 public static void main(String args[]) { SERVER_NAME = new String(args[0]); TalkSelf t = new TalkSelf(); Frame f = new Frame(); f.addWindowListener(new WindowAdapter() public void windowClosing(WindowEvent evt) System.exit(0); } });

t.init(); f.setSize(300,300); f.add("Center", t); f.show(); t.start(); }

图11.7 简单的通话界面

11.3 网页显示控件 11.3.1 JEditorPane JEditorPane类是一个文本组件,通过实现相应的EditorKit接口可以编辑一些特殊格式的内容。它的内容类型有三种: (1) text/plain:使用默认的DefaultEditorKit的扩展,处理普通文本。 (2) txt/html:使用javax.swing.text.html.HTMLEditorKit提供HTML3.2的帮助,用以处理HTML文本,针对HTML中存在的一些链接,进行点击时可以激活超链接事件。 (3) txt/rtf:使用javax.swing.text.rtf.RTFEditorKit实现对RTF格式文本的处理。

这里侧重于处理HTML文本的情况,JEditorPane类的构造函数和处理HTML常用到的方法如下: public JEditorPane() 构建一个空JeditorPane。 public JEditorPane(String url) 构建一个基于URL声明的字符串的JEditorPane。 public JEditorPane(URL initialPage) 构建一个基于特定URL对象的JeditorPane。 public void addHyperlinkListener(HyperlinkListener listener) 添加一个超链接监听者,通知任何变化,例如选中一个链接并进入时。

public void setPage(String url) 字符串url 表示页面的URL地址。 public void setPage(URL page) 设置显示的URL对象代表的page页面。 public void setText(String t) 设置指定t内容。 public void setEditable(boolean b) b为true时,可编辑;b为false时,不可编辑,但只有当b为false时,才可以响应超链接事件。

11.3.2 HyperlinkListener和HyperlinkEvent HyperlinkListener是超链接事件监听者接口,继承于EventListener接口,只有一个事件:超链接更新,当点击链接或者进入链接时引发。这个事件的方法为: public void hyperlinkUpdate(HyperlinkEvent e) 当更新一个超链接时调用此方法。 HyperlinkEvent是超链接事件,用于通知有关超文本链接的变化,它有两个常用方法,一个方法用来获得事件的类型,另一个方法是获得超链接指向的URL网址。 public HyperlinkEvent.EventType getEventType()

表11.1 超链接事件的三种类型 public URL getURL() 返回超链接指向的URL地址。

【例11.6】使用JEditorPane显示网页,当单击其中的链接时能够给予响应。 1. 分析 (1) 创建JEditorPane对象。 JEditorPane jep = new JEditorPane(); (2) 设置可编辑属性为false。 jep.setEditable(false); (3) 添加超链接事件监听器。 jep.addHyperlinkListener(new LinkFollower(jep));

public void hyperlinkUpdate(HyperlinkEvent evt) { (4) 显示网页。 jep.setPage(str); (5) 链接更新事件的处理。 public void hyperlinkUpdate(HyperlinkEvent evt) { if(evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) jep.setPage(evt.getURL()); }

2. 源程序 //程序文件名为DisplayHtml.java import java.io.*; import java.net.*; import javax.swing.*; import javax.swing.event.*; public class DisplayHtml { //在新窗口显示网页 public static void showNetPage(String str)

JEditorPane jep = new JEditorPane(); jep.setEditable(false); jep.addHyperlinkListener(new LinkFollower(jep)); try { jep.setPage(str); } catch(IOException e) jep.setContentType("text/html"); jep.setText("<html>Could not load "+ str + " </html>");

JScrollPane jscrollp = new JScrollPane(jep); JFrame f = new JFrame("西安交通大学主页"); f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); f.setContentPane(jscrollp); f.setSize(600, 400); f.show(); } public static void main(String[] args) { String getURL = "http://www.xjtu.edu.cn"; showNetPage(getURL);

//类:新开的网页窗口响应链接点击事件 class LinkFollower implements HyperlinkListener { private JEditorPane jep; public LinkFollower(JEditorPane jep) this.jep = jep; } //超链接更新事件的处理 public void hyperlinkUpdate(HyperlinkEvent evt) //判断是否是激活事件 if(evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED)

try { //显示新的网址 jep.setPage(evt.getURL()); } catch(Exception e) System.out.println(e.getMessage());

3. 结果分析 图11.8 JEditorPane显示的网页

图11.9 响应链接后进入的网页

习 题 1. 建立TCP Socket读取http://www.263.net网址的首页,将之存入文件h2.html并在IE中显示此文件。 习 题 1. 建立TCP Socket读取http://www.263.net网址的首页,将之存入文件h2.html并在IE中显示此文件。 2. 建立TCP Socket进行通信,服务器端向客户端传送日期数据。 3. 使用UDP Socket进行通信,服务器和客户相互传送日期数据。 4. 仔细学习本章音频操作的实例,修改其中的一些数据,如缓冲值或音频流初始化参数,看是否会影响到音频的质量。 5. 使用java.swing包中的高级控件显示http://www.263.net网址的首页。

第12章 Java网络技术(二) 12.1 URL类 12.2 URLEncoder类 12.3 URLDecoder类 12.4 URLConnection类 12.5 HttpURLConnection类 12.6 新IO包的Socket应用 习 题

12.1 URL 类 URL类代表统一资源定位符(Uniform Resource Locator),指向WWW上的资源,资源可以是一个文件或者一个目录,也可以是一个更加复杂的对象,如对数据库的查询或者搜索引擎的查询。通常,一个URL可以被拆分成几个部分,例如: http://192.100.100.43:8080/web/index.html?search="Java " 其中: (1) http用来描述使用的协议为超文本链接协议。

(2) 192.100.100.43是信息存放的主机IP地址(也可以是主机名,如www.sohu.com.cn)。 (3) 端口号为8080,默认都为80端口。 (4) web/index.html为HTTP服务器上文件的虚拟路径。 (5) ?search="Java"为网页上表单使用GET方法进行查询的附加部分,?表示后面跟的是提交的表单的名-值对,格式为“名=值”。search为查询词的名字,Java为查询词的值,即用户要查询的值,提交搜索引擎时可以有0个到多个这样的查询名-值对。

URL类用来定位WWW上的资源,从而进行处理,如读取网页等。它的构造函数以及一系列常用的方法如下: public URL(String spec) 从指定的字符串spec创建一个URL对象。 public URL(String protocol, String host, int port, String file) 从指定的protocol协议、host主机、port端口号和file文件名创建一个URL对象。 public URL(String protocol, String host, String file) 从指定的protocol协议、主机名host和文件名file创建一个URL对象。

public Object getContent() 得到URL的内容。 public String getContentType() 返回网页内容类型,普通网页为“text/html”。 public String getFile() 得到URL文件名。 public String getHost() 得到URL的主机名。 public String getPath() 得到URL的路径部分。 public int getPort() 得到URL的端口号。

public String getProtocol() 得到URL的协议名。 public String getQuery() 得到URL的查询部分。 public URLConnection openConnection() 返回一个URLConnection对象,代表到URL远程对象的连接。 public InputStream openStream() 打开到URL的连接,返回读取连接的输入流。 public String toExternalForm() 构建代表URL对象的字符串。 public String toString() 转换成字符串,覆盖来自对象的toString()方法。

12.2 URLEncoder类 URLEncoder类是一个工具类,用于HTML表单的编码。类只包含一个静态方法,这个方法将字符串转换成application/x-www-form-urlencoded MIME格式。该方法如下: public static String encode(String s, String enc) throws UnsupportedEncodingException 使用指定的enc编码格式将字符串s转换为application/x-www-form-urlencoded格式,得以在网上传输。

其中: (1) s为要转换的字符串。 (2) enc是字符编码格式名称,包括US-ASCII、ISO-8859-1、UTF-8等。

【例12.1】将查询表单提交的网址和相应的两个查询“名-值对”使用“UTF-8”编码格式进行编码。 1. 分析 (1) 在程序中定义了一个QueryString类,实现多个名-值对的编码和连接。其中一对名-值对编码如下: query = URLEncoder.encode(name.toString(),"UTF-8") + "=" + URLEncoder.encode(value.toString(),"UTF-8");

(2) 名-值对之间用符号&连接。 if(!query.trim().equals("")) query += "&"; (3) 主函数中对QueryString类的引用。 QueryString q = new QueryString("cdtype","GB"); q.add("word","Java");

2. 源程序 //程序文件名UseEncode.java import java.net.*; import java.io.*; public class UseEncode { public static void main(String[] args) String fullURL = "http://bingle.pku.edu.cn/scripts/ftp_search.exe?"; //新建QueryString对象,调用方法

QueryString q = new QueryString("cdtype","GB"); q.add("word","Java"); fullURL += q.toString(); //打印编码后的字符串 System.out.println("编码后的字符串:" + fullURL); } //类:处理请求串,编码成网页识别格式 class QueryString {

private String query; //构造函数,初始名值对的编码 public QueryString(Object name, Object value) { try query = URLEncoder.encode(name.toString(),"UTF-8") + "=" + URLEncoder.encode(value.toString(),"UTF-8"); } catch(UnsupportedEncodingException e) System.err.println(e);

//构造函数 public QueryString() { query = ""; } //添加名值对,之间用符号&进行连接 public synchronized void add(Object name, Object value) if(!query.trim().equals("")) query += "&"; try query += URLEncoder.encode(name.toString(),"UTF-8") + "=" + URLEncoder.encode(value.toString(),"UTF-8");

} catch(UnsupportedEncodingException e) { System.err.println(e); //返回编码后的字符串 public String toString() return query; //清空 public void clear() query = "";

3. 结果输出 图12.1为搜索引擎提交的查询字符串编码的结果。可以看见提交网址的?后跟了两个名值对,cdtype=GB 和word=Java,它们之间用符号&隔开。 图12.1 编码后字符串输出结果

12.3 URLDecoder类 URLDecoder类是URLEncoder类的解码类。它的构造函数和解码方法如下: public URLEncoder() public static String decode(String s, String enc) 使用编码格式enc对application/x-www-form-urlencoded字符串s进行解码。

12.4 URLConnection类 抽象类URLConnection代表应用程序和URL之间通信连接的类的超类。类的实例可以用来读取和写入URL代表的资源。URLConnection类实现的两个方法如表12.1所示。 表12.1 URLConnection类实现的两个方法

上述两个方法的实现步骤如下: (1) 调用openConnection方法建立连接对象。 (2) 操作建立参数和普通请求参数。 (3) 调用connect方法建立到远程对象的实际连接。 (4) 远程对象可用,头文件和远程对象的内容可以访问。

【例12. 2】编写程序,访问天网主页(http://e. pku. edu. cn)并查询Java一词,将查询结果存入result 【例12.2】编写程序,访问天网主页(http://e.pku.edu.cn)并查询Java一词,将查询结果存入result.html文件。 1. 分析 (1) 建立URL对象。 u = new URL(fullURL); (2) 打开到URL对象的连接。 conn = u.openConnection(); (3) 获取输入流。 theData =conn.getInputStream(); (4) 输出到文件。 p.println(line);

2. 源程序 //程序文件名Search.java import java.net.*; import java.io.*; public class Search { public static void main(String args[]) String fullURL = "http://bingle.pku.edu.cn/scripts/ftp_search.exe?"; URLConnection conn = null;

OutputStream theControl =null; InputStream theData =null; URL u=null; //建立URL对象,参数为请求地址+编码后的请求串 try { fullURL += URLEncoder.encode("cdtype","UTF-8") + "=" + URLEncoder.encode("GB","UTF-8"); fullURL += "&" + URLEncoder.encode("word","UTF-8") + "=" + URLEncoder.encode("Java","UTF-8"); u = new URL(fullURL); }

catch(UnsupportedEncodingException e) { System.err.println(e); } catch(MalformedURLException e) { System.err.println("网页错误:"+fullURL+""+e); System.exit(1); //打开连接,读入网页流数据 try { conn = u.openConnection(); theData =conn.getInputStream(); //得到网页类型:text/html

String contentType=conn.getContentType(); //建立文件对象和读取的流对象 File f = new File("result.html"); FileOutputStream fOut = new FileOutputStream(f); PrintWriter p = new PrintWriter(fOut); if(contentType.toLowerCase().startsWith("text")) { BufferedReader in = new BufferedReader(new InputStreamReader(theData)); String line; while ((line = in.readLine())!=null)

//输出到文件 p.println(line); } else { System.out.println("程序只处理文本响应"); System.out.println("得到的类型为:"+contentType); catch(IOException e) { System.err.println(e); System.exit(2);

3. 结果分析 程序运行结果如图12.2所示,左部分为浏览器中天网检索的结果,右部分为程序检索结果result.html,由于没有考虑图片的读取,可以看出程序输出缺少所有的图片,但是文本字符和检索结果都是一致的。

图12.2 程序检索结果和天网检索结果的对比

12.5 HttpURLConnection类 HttpURLConnection类继承URLConnection类,专门支持HTTP协议相关的特征。每个HttpURLConnection实例完成一个单一的请求,其构造函数如下: protected HttpURLConnection(URL u) 构建一个到URL对象u代表的网络资源的HttpURLConnection连接。 public int getResponseCode() throws IOException

返回HTTP状态码,如: HTTP/1.0 200 OK HTTP/1.0 401 Unauthorized HttpURLConnection类中有一系列的常量值对应着这些状态码。例如HTTP_OK对应着上面列出的第一个状态码,而HTTP_UNAUTHORIZED对应着第二个状态码。

【例12.3】返回www.sohu.com.cn网址的首页,并与从浏览器获得的网页进行比较。 //程序文件名为UseHttp.java import java.io.*; import java.net.*; public class UseHttp { public static void main(String[] args) String urlstring = "http://www.sohu.com.cn"; String httpresp = new String(); String status = new String("good"); try

//构造URL对象 URL currenturl = new URL(urlstring); urlstring = currenturl.toString(); //判断是否是HTTP协议 if(!currenturl.getProtocol().equals("http")) { status = currenturl.getProtocol() + " protocol"; } else //打开连接 URLConnection conn = currenturl.openConnection(); //建立HttpURLConnection对象

HttpURLConnection httpconn = (HttpURLConnection)conn; //判断是否正确返回 if(httpconn.getResponseCode() == HttpURLConnection.HTTP_OK) { if(httpconn.getContentType().equals("text/html")) //构造文件读写流对象,写入文件 InputStreamReader isr = new InputStreamReader(conn.getInputStream()); File f = new File("sohu.html"); FileOutputStream fOut = new FileOutputStream(f); PrintWriter p = new PrintWriter(fOut);

int c; while((c = isr.read()) != -1) { p.write(c); } isr.close(); httpconn.disconnect(); else status = "Not text/html"; status = "bad";

catch(Exception e) { status = e.toString(); System.out.println("Exception:" + e.getMessage()); } if(status.equals("good")) System.out.println(urlstring); else System.out.println(status); System.out.println("Bad URL = " + urlstring);

由图12. 3可以看出,左边的页面是网址首页,右边的网页是程序生成的sohu 由图12.3可以看出,左边的页面是网址首页,右边的网页是程序生成的sohu.html页面。比较可得,两个页面是一致的。可以看出随着类的依次封装,可应用领域变窄(从Socket到URL,再到针对HTTP协议的URL),但相对专门的应用而言,用户使用起来更为简单。

图12.3 程序输出页面和网址首页的比较

12.6 新IO包的Socket应用 在J2sdk1.4中提供了一个新I/O包(java.nio),通过引入四个基本抽象类,开发网络Socket连接的非阻塞应用。对于第11章Java编程中的多个Socket连接,用线程实现时会遇到一些问题,如操作系统限制、死锁、线程安全违反等。而新I/O提供的特性可以很好地解决这些问题。引入的四个基本抽象类如下: Buffer:包含读写的数据线性流。 Charset:将Unicode字符与字节流之间进行相互转换。 Channels:可以是Socket、文件或管道,代表双边通信的管道。 Selectors:多元异步I/O操作,应用于单个线程或多个线程。

12.6.1 Buffers Buffer是一个线性、有序的数据集,提供了一种在内存容器中保存一系列原始数据的机制。Buffer进行特定的封装之后只允许读写一种数据类型,例如char、int或double等。共有七种读写不同数据类型的Buffer,如表12.2所示。 表12.2 读写不同数据类型的Buffer

Buffer中涉及到的四个基本概念是capacity(容量)、position(位置)、limit(限制)和mark(标记)。 ● capacity——Buffer的大小(>=size); ● position——在Buffer中目前读写的位置(<=limit); ● limit——第一个不应该被读取元素的位置的index(索引号)(<=capacity); ● mark—— 用mark方法设置的可设位置,mark方法可以使用reset来重置position(<=position,>=0)。

【例12.4】下面给出一段缓冲操作的代码,依次演示capacity、position和limit的值,代码段为: ByteBuffer buf = ByteBuffer.allocateDirect(8); buf.put( (byte)0xca ); buf.putShort( (short)0xfeba ); buf.put( (byte)0xbe ); buf.flip();

代码段分析如下: ByteBuffer buf = ByteBuffer.allocateDirect(8); 分配8字节的字节缓冲,position=0、capacity=8、limit=8,如图12.4(a)所示。 buf.put( (byte)0xca ); buf.putShort( (short)0xfeba ); buf.put( (byte)0xbe ); 依次放入三个数据,两个字节数据和一个短整型数据,其中短整型数据占两个字节,position=4、capacity=8,如图12.4(b)所示。 buf.flip();

上面这行语句是填充数据之后、写入通道之前需要调用的方法,将position重定位到0,将limit定位到数据结束位置,准备好序列写入通道。position=0、capacity=8、limit=4,如图12.4(c)所示。

图12.4 缓冲的变化过程显示

12.6.2 Charset Charset(字符集)定义了Unicode和字节之间的映射。字符集根据IANA标准定义,Java中字符集由java.nio.charset.Charset实例代表,使用Charset.forName()得到合适实例。Charset.availableCharsets()给出支持字符集名的映射和它们的Charset实例。JDK1.4包括8个字符集:US-ASCII、ISO-8859-1、ISO-8859-15、UTF-8、UTF-16等。Charset构造CharsetEncoder和CharsetDecoder,使得有序字符和字节之间可以进行转换,输出时使用encoder,对输入请求使用decoder。以下代码段是对字符buffer进行编码的情况。

Charset charset = Charset.forName("UTF-8");//ISO-8859-1US-ASCII ByteBuffer buffer = charset.newEncoder().encode(chars); ByteBuffer directBuffer = ByteBuffer.allocateDirect(buffer.limit()); directBuffer.put(buffer);

12.6.3 Channels 1. ServerSocketChannel Java.nio.channels.ServerSocketChannel与java.net.ServerSocket一样,用于创建一个监听线程来接收到来的连接,只是既不能读也不能写。ServerSocketChannel.socket()提供对底层ServerSocket的访问,因此可以设置Socket选项。这个通道可以使用ServerSocketChannel.open()工厂方法创建。ServerSocketChannelaccept()为新建立连接的客户返回java.nio.channel.SocketChannel。如果ServerSocketChannel进入阻塞模式,accept()不会返回,直到一个有连接请求到达;而在非阻塞模式,不管Socket是否为空,总是立刻返回。

2. SocketChannel Java.nio.channels.SocketChannel在应用程序中封装了java.net.Socket并添加非阻塞模式和状态机。SocketChannel可以通过两种方法创建:SocketChannel.open()创建一个新的、无连接的SocketChannel;通过ServerSocketChannel.accept()返回的Socket,实际上有一个开放和连接的SocketChannel附加在它上面。

3. FileChannel 文件通道允许使用操作系统的文件缓冲直接进行文件传输。文件通道可以将文件区域映射到内存。 MappedByteBuffer是一个专门用于直接缓冲的ByteBuffer,这个类用字节缓冲来映射一个文件。想要映射一个文件到MappedByteBuffer,必须先取得这个文件的通道(channel)。通道是某种已建立的连接,如管道(Pipe)、套接口(Socket)或文件(File)等能够完成I/O操作的对象。

如果是文件通道,你可以通过FileInputStream(文件输入流)、FileOutputStream(文件输出流)或RandomAccessFile(随机存取文件)的getChannel方法来获得一个通道。一旦你取得这个通道,就可以通过它的map方法指明映射模式,来把你想映射的那一部分文件映射到缓冲中去。文件通道可以使用FileChannel.MapMode的任一个常数打开:只读(READ_ONLY)、私有/写时拷贝(PRIVATE)或读写(READ_WRITE)。

【例12.5】使用文件通道打开一个文件并输出文件内容。 1. 分析 (1) 通过文件输入流对象得到通道。 FileInputStream input = new FileInputStream(filename); FileChannel channel = input.getChannel(); (2) 映射到字节缓冲。 int fileLength = (int)channel.size(); MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength);

(3) 字符集进行解码。 Charset charset = Charset.forName("ISO-8859-1");// CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(buffer); (4) 输出文件内容。 System.out.println(charBuffer);

2. 源程序 //程序文件名为UseFchannel.java import java.io.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; class UseFchannel { public static void main(String[] args) try

String filename = "f.txt"; FileInputStream input = new FileInputStream(filename); FileChannel channel = input.getChannel(); int fileLength = (int)channel.size(); MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength); Charset charset = Charset.forName("ISO-8859-1");// CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(buffer); System.out.println(charBuffer);

} catch(Exception e) { System.out.println("Error:" + e.getMessage());

3. 结果分析 图12.5上一部分显示打开的文件内容,下一部分显示程序输出的内容,可以看出两者是一致的。 图12.5 源文件内容与程序输出的内容对比

12.6.4 Selectors 非阻塞I/O围绕为多元选择通道准备的Selectors(选择器)对象构建。选择器对象保持一系列选择键,这些键在应用中可由某个事件激活。选择器本身管理键,编程人员使用键的状态管理回调来完成客户请求。 构造函数如下: protected Selector() 初始化一个实例。 选择器可以通过调用自带的open方法进行创建: Selector s = Selector.open();

下面的两个方法用来返回选择键值的个数和键值集合。 public abstract int select() throws IOException 返回键值的个数。 public abstract Set selectedKeys() 返回选择器选中的键值集合。 对于一个Socket通道,只有将它本身发生的事件(如监听、连接、读、写等)在选择器上进行注册,才可以被选择器识别,从而进行处理,这个注册就用到SelectionKey类。SelectionKey类包括四个注册值,将通道支持的事件注册到相应的选择器上。它还提供四个判断键值状态的方法,从而进行相应的事件处理,如表12.3所示。

表12.3 SelectionKey类的注册值及其方法

例如,接收新连接的通道应该注册为: ServerSocketChannel ch = ServerSocketChannel.open(); SelectionKey acceptKey = ch.register(s,SelectionKey.OP_ACCEPT); 一个读取和写入数据的通道应该注册为: SelectionKey readWriteKey = ch.register(s,SelectionKey.OP_READ|SelectionKey.OP_WRITE); 当用户发送一个请求时,选择器通过返回键进行工作。 //循环实现 While((keysAdded = s.select())>0) {

//返回键值集合 set readyKeys = s.selectedKeys(); //使用Iterator枚举 Iterator I = readKeys.iterator(); While(i.hasNext()) { SelectionKey sk = (SelectionKey)i.next(); ...接收连接处理请求; }

具体的处理可以根据键值的状态进行,上面的“接收连接处理请求”就可以为: i.remove(); if ( key.isAcceptable() ) { ... } else if ( key.isWritable() )

12.6.5 阻塞模式 在阻塞模式下,服务器端和客户端的程序基本相同,惟一不同的是客户端为Connect方法,服务器端为Accept方法。 新I/O包处理Socket的读写操作时使用java.net包中的InetSocketAddress类指定链接地址,并用前面介绍的SocketChannel类来完成实际的读写操作。InetSocketAddress类提供一个用来绑定、连接或者返回数值的远程对象。它常用的构造函数为: InetSocketAddress(String hostname, int port) 以名字hostname和端口号port创建socket地址。

客户端使用SocketChannel建立连接的步骤如下: (1) 首先获得InetSocketAddress的对象。 String host = "www.sohu.com.cn"; InetSocketAddress socketAddress = new InetSocketAddress(host, 80); (2) 打开一个到InetSocketAddress代表的主机的连接。 使用SocketChannel取代以前从Socket对象的输入流来读取、向Socket的输出流写入的所有操作: SocketChannel channel = SocketChannel.open(); channel.connect(socketAddress);

(3) 发送一个HTTP请求,发送请求之前进行编码。 Charset charset = Charset.forName("ISO-8859-1"); CharsetEncoder encoder = charset.newEncoder(); String request = " GET / HTTP/1.0\r\n\r\n "; channel.write(encoder.encode(CharBuffer.wrap(request)));

(4) 从通道中读取服务器的响应。 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); while ((channel.read(buffer)) != -1) { buffer.flip(); decoder.decode(buffer, charBuffer, false); charBuffer.flip(); System.out.println(charBuffer); buffer.clear(); charBuffer.clear(); }

【例12. 6】下面这个程序通过一个HTTP请求来读取站点www. xjtu. edu 【例12.6】下面这个程序通过一个HTTP请求来读取站点www.xjtu.edu.cn的首页,直接输出到屏幕上,可以看见是网页的文本文件。源程序代码如下: //程序文件名为ReadURL.java import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*;

public class ReadURL { public static void main(String args[]) SocketChannel channel = null; String host = "www.xjtu.edu.cn"; try //建立连接 InetSocketAddress socketAddress = new InetSocketAddress(host, 80);

Charset charset = Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); CharsetEncoder encoder = charset.newEncoder(); //分配缓冲 ByteBuffer buffer = ByteBuffer.allocateDirect(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); //连接 channel = SocketChannel.open(); channel.connect(socketAddress); //发送请求 String request = "GET / HTTP/1.0\r\n\r\n"; channel.write(encoder.encode(CharBuffer.wrap(request))); while ((channel.read(buffer)) != -1) {

buffer.flip(); //解码 decoder.decode(buffer, charBuffer, false); //输出 charBuffer.flip(); System.out.println(charBuffer); buffer.clear(); charBuffer.clear(); } } catch (UnknownHostException e) { System.err.println(e); } catch (IOException e) {

} finally { if (channel != null) { try { channel.close(); } catch (IOException ignored) {} }

12.6.6 非阻塞模式 1. 非阻塞客户端 (1) 创建可选择通道,配置成非阻塞模式并进行连接。 String host = "192.100.100.43"; InetSocketAddress socketAddress = new InetSocketAddress(host, 80); channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(socketAddress); 这时得到的通道channel是一个可选择通道(SelectableChannel),通过一个Selector来工作。将通道注册到一个Selector,同时声明一些事件,当事件发生时通道会通过回调执行。

(2) 创建Selector实例。 Selector selector = Selector.open(); (3) 通道向选择器的注册。对于SocketChannel类来说,有效的操作事件是OP_CONNECT、OP_READ 和 OP_WRITE,因此可以进行如下注册: channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ);

(4) 处理通道事件。当有事件在通道上发生时,Selector进行通知,然后使用一个while (selector (4) 处理通道事件。当有事件在通道上发生时,Selector进行通知,然后使用一个while (selector.select() > 0)循环进行处理。发出请求后得到响应的代码都放入这一段,当作读写事件进行处理。 (5) 加入异常处理代码。加入必要的try{...}catch(Exception e){...}语句,并在finally代码段中加入关闭通道的代码。

【例12.7】下面是完整的例子程序,结果是得到本机tomcat主页并显示在标准输出上。用户也可以将它输出到文件中,以方便与原网页进行比较。源程序代码如下: //程序文件名为NBReadURL.java import java.io.*; import java.net.*; import java.nio.*; import java.util.*; import java.nio.channels.*; import java.nio.charset.*;

public class NBReadURL { static Selector sele; public static void main(String args[]) String host = "192.100.100.43"; SocketChannel channel = null; try

//初始化 InetSocketAddress sockAddr = new InetSocketAddress(host, 8080); Charset charset = Charset.forName("ISO-8859-1"); CharsetDecoder decoder = charset.newDecoder(); CharsetEncoder encoder = charset.newEncoder(); //分配缓冲 ByteBuffer buf = ByteBuffer.allocateDirect(1024); CharBuffer cbuf = CharBuffer.allocate(1024); //连接 channel = SocketChannel.open(); channel.configureBlocking(false); channel.connect(sockAddr);

//打开选择器 sele = Selector.open(); channel.register(sele, SelectionKey.OP_CONNECT|SelectionKey.OP_READ); //Wait for something of interest to happen while(sele.select(500)>0) { //得到对象 Set readyKeys = sele.selectedKeys(); Iterator readyItor = readyKeys.iterator();

//遍历对象集 while(readyItor.hasNext()) { //得到键 SelectionKey k = (SelectionKey)readyItor.next(); //Remove current entry readyItor.remove(); //得到通道 SocketChannel keyChannel = (SocketChannel)k.channel(); if(k.isConnectable())

//完成连接 if(keyChannel.isConnectionPending()) { keyChannel.finishConnect(); } //发送请求 String request = "GET /index.html \r\n\r\n"; keyChannel.write(encoder.encode(CharBuffer.wrap(request))); else if(k.isReadable())

//读取数据 keyChannel.read(buf); buf.flip(); //解码 decoder.decode(buf, cbuf, false); //显示 cbuf.flip(); System.out.print(cbuf); //清除缓冲 buf.clear(); cbuf.clear(); } else {

System.err.println("Ooops"); } catch(UnknownHostException e) { System.err.println(e); catch(IOException e) finally if(channel != null)

{ try channel.close(); } catch(IOException ignored) System.out.println();

2. 非阻塞服务器 使用新I/O包实现的非阻塞Web服务器,不必为每个连接都提供一个线程就可以实现Web服务器的功能。非阻塞服务器程序编写的步骤如下: (1) 创建接收通道,配置非阻塞模式并绑定到InetSocketAddress对象。 ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); InetSocketAddress isa = new InetSocketAddress(port); channel.socket().bind(isa);

(2) 创建Selector实例。 Selector selector = Selector.open(); (3) 注册事件。服务器端需要注册的是OP_ACCEPT键值: channel.register(selector, SelectionKey.OP_ACCEPT); (4) 处理通道事件。当有事件在通道上发生时,Selector进行通知,然后使用一个while (selector.select() > 0)循环进行处理。发出请求得到响应的代码都放入这一段,当作读写事件进行处理。

(5) 添加异常处理代码。加入必要的try{. }catch(Exception e){ (5) 添加异常处理代码。加入必要的try{...}catch(Exception e){...}语句,并在finally代码段中加入关闭通道的代码。 可以看出非阻塞模式下服务器和客户端编程的步骤都是一致的,只是有些步骤下编写的代码有所不同。

【例12.8】给出一个基本的单线程的服务器,对每一个响应都发回一段文字信息来表示时间。源程序代码如下: //程序文件名为Server.java import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; import java.util.*; public class Server {

private static int port = 4321; public static void main(String args[]) throws Exception { Selector selector = Selector.open(); ServerSocketChannel channel = ServerSocketChannel.open(); channel.configureBlocking(false); InetSocketAddress isa = new InetSocketAddress(port); channel.socket().bind(isa); //注册 channel.register(selector, SelectionKey.OP_ACCEPT); while (selector.select() > 0)

Set readyKeys = selector.selectedKeys(); Iterator readyItor = readyKeys.iterator(); while (readyItor.hasNext()) { SelectionKey key = (SelectionKey)readyItor.next(); readyItor.remove(); ServerSocketChannel keyChannel = (ServerSocketChannel)key.channel(); ServerSocket serverSocket = keyChannel.socket(); Socket socket = serverSocket.accept(); //返回时间

PrintWriter out = new PrintWriter(socket.getOutputStream(), true); Date now = new Date(); out.println("Hello, now time is: " + now); out.close(); }//End of While }//End of main }

为了服务器与客户端能够进行通信,对例12.7的客户端程序进行修改,将InetSocketAddress对象sockAddr中的端口号改成4321,使得客户端请求的端口号与此服务器监听的端口号一致,即将 InetSocketAddress sockAddr = new InetSocketAddress(host, 8080); 改为 InetSocketAddress sockAddr = new InetSocketAddress(host, 4321); 然后重新编译,生成类文件。 启动两个命令提示符,其中一个为运行命令“java Server”,另一个为键入命令“java NBReadURL”,可以看见如图12.6的通信结果。客户端显示服务器发送的一段时间信息。如果希望在服务器端显示内容,还需修改服务器程序,注册服务器通道的读、写事件。

图12.6 非阻塞模式下的通信输出

习 题 1. 给出字符串,熟悉URLEncoder和URLDecoder类的规范。 习 题 1. 给出字符串,熟悉URLEncoder和URLDecoder类的规范。 2. 从天网主页http://e.pku.edu.cn检索包含“网络”一词的文件。 3. 使用HttpURLConnection对象打开网页并输出到文件中。 4. 使用文件通道将例12.6的程序输出改成输出到文件中。 5. 编写非阻塞模式下的Socket通信。

第13章 Servlet技术 13.1 Servlet概述 13.2 Servlet生命周期 13.3 使用Servlet 13.4 Applet与Servlet通信 习 题

13.1 Servlet 概 述 Servlet是用Java编写的且协议和平台都独立的服务器端的组件。与客户端组件Applet相对应。Servlet扩展了面向请求/响应的服务器的模块,使用平台专用的API进行服务器端的编程。Servlet为服务器和基于Web的客户之间的通信提供了一条更为简单的途径。它的特殊用途包括: (1) 允许用户之间的合作。一个Servlet可以同时并发处理大量的请求,而且可以同步请求,因此使Servlets能够支持像在线会议这样的系统。Servlets能够并发地服务多个客户。

(2) 转发请求。Servlets能够转发请求到其它的服务器和Servlets,因此Servlets能够被用来在多个镜像同一个内容的服务器之间来平衡负载,在多个服务器上根据任务类型或者组织边界分割单一的逻辑服务。

13.2 Servlet生命周期 图13.1 Servlet的生命周期

1. 初始化Servlet 当服务器载入一个Servlet时,服务器运行Servlet的init方法。初始化在客户请求被处理和Servlet被销毁之前完成。

2. Servlet_Client交互 初始化成功后,HTTP Servlet调用Service方法处理客户请求,Service方法将每个请求分配到处理这个请求的方法,从而支持标准的HTTP客户请求。HttpServlet类中的方法处理客户请求时使用以下两个参数: (1) HttpServletRequest对象:封装了从客户来的数据,主要提供了访问初始请求数据的方法和字段;访问客户数据时使用getParameter方法得到一个已命名参数的值。 (2) HttpServletResponse对象:封装了对客户的响应。使用getWriter方法返回文本数据给客户(可以以HTML网页的形式表现出来)。

Service方法支配的HTTP请求如表13.1所示。

通常,编写的Servlet应该重载处理它支持的HTTP交互的方法。如果出错,这些方法返回一个BAD_REQUEST(400)错误。当Servlet收到OPTIONS请求时,HttpServlet的Service方法调用doOptions方法。默认的doOptions的实现自动地决定了支持何种HTTP选项和返回信息。HTTP Servlets通常能够并发地服务多个客户。如果Servlet中的这个方法对于客户访问共享资源是可行的,那么你可以通过创建在某一时刻只能处理一个客户请求的Servlet来处理并发。

3. 销毁Servlet Servlet一直运行直到服务器销毁它们,比如在系统管理员的要求下。当一个服务器销毁一个Servlet时,服务器运行Servlet的Destroy()方法。方法只运行一次,服务器将不再运行Servlet,直到服务器重新载入和重新初始化Servlet。

13.3 使用Servlet 13.3.1 编写Servlet 【例13.1】 在客户端填写“用户注册信息”网页,并将此网页提交到后台服务器端Servlet,服务器端Servlet程序给予响应,并以网页的形式按行输出用户提交的基本信息。 1. 客户端 客户端是一个“用户注册信息”的HTML网页,如图13.2所示。用户输入个人信息,点击“确定”按钮,将表单数据提交到服务器,然后等待服务器的响应。Index.html源文件代码如下:

<!-- 文件名:index.html --> <html> <head> <title>用户注册信息收集 </title> </head> <body> <h2> <center>用户注册信息 </center>

<hr> <center><h4> <form action= "http://192.100.100.43:8080/examples/ Servlet/user.UserServlet" method= "POST"> 姓名:<INPUT type="text" name="name" size="23" maxlength="30"> <br> 身份证号:<INPUT type="text" name="number" size="20" maxlength="30"> 性别 <INPUT type="radio" name = "sex" value=男 checked>男 <INPUT type="radio" name = "sex" value=女>女

职业 <select name="job"> <option value =计算机业>计算机业 <option value =医生>医生 <option value =教师>教师 <option value =军队>军队 </select> <br> 个性化宣言

<textarea rows=7 cols=27 name="ta" > <br> <INPUT type="submit" value="确定"> <INPUT type="reset" value="清空"> </form></center> </body> </html>

图13.2 “用户注册信息”网页

在网页index.html中要注意表单的书写,表单的action属性对应服务器端的Servlet,本例中取值为http://192.100.100.43:8080/examples/Servlet/user.UserServlet;method属性是访问方法,本例中为POST方法。表13.2是表单中的元素标签和命名,可以看到除去“确定”和“清空”,其它的元素标签在第三栏都有一个对应的名字,Servlet通过这些名字获得用户在界面上输入的值,而用户单击“确定”按钮时,表单内容就提交到action属性指定的Servlet。

表13.2 表单元素标签及命名

2. 服务器端 服务器端Servlet收集用户界面输入的数据(见图13.3),然后按行返回这些内容,结果如图13.4所示。注意传输过程中中文字符可能会有出错情况,因此再添加一个转换字段,使得Servlet能够正确打印输出。 //程序文件名:UserServlet.java package user; import java.io.*; import javax.Servlet.*; import javax.Servlet.http.*; public class UserServlet extends HttpServlet {

String name,number,sex,job,ta; public void init() throws ServletException { super.init(); name = new String(); number = new String(); sex = new String(); job = new String(); ta = new String(); } //解决中文转换问题 public String parseChinese(String inStr)

String s = null; byte temp[]; if (inStr == null) { //System.out.println("Warn:Chinese null founded!"); return new String(""); } try temp=inStr.getBytes("iso-8859-1"); s = new String(temp); catch(UnsupportedEncodingException e)

System.out.println (e.toString()); } return s; public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { //获取用户界面输入的值 name = req.getParameter("name"); number = req.getParameter("number"); sex = req.getParameter("sex"); job = req.getParameter("job");

ta = req.getParameter("ta"); //进行输出 res.setContentType("text/html; charset=GB2312"); PrintWriter out = res.getWriter(); out.println("<HTML><HEAD></HEAD><BODY><center>"); out.println("注册信息返回结果<hr>"); out.println(" 姓名:" + parseChinese(name)); out.println("<br> 身份证号:" + number); out.println("<br> 性别:" + parseChinese(sex) + " 职业:" + parseChinese(job)); out.println("<br>个性化宣言:" + parseChinese(ta) + "<center></body></html>"); }

图13.3 用户输入注册信息

图13.4 Servlet返回信息

13.3.2 编译、配置Servlet 安装的Java包是没有带Servlet的JAR文件,所以将D:\Apache Tomcat 4.0\common\lib目录下的Servlet.jar配置到路径包的安装路径下库的扩展目录中,编译时会自动连接库,如本书配置到的目录为D:\j2sdk1.4.0_01\jre\lib\ext。在命令行提示符下键入命令javac UserServlet.java编译文件,生成类UserServlet.class。

Servlet是服务器端组件,所以必须配置到服务器端。对于Tomcat 4. 0服务器,将index Servlet是服务器端组件,所以必须配置到服务器端。对于Tomcat 4.0服务器,将index.html配置到物理路径D:\Apache Tomcat 4.0\webapps\ROOT\user目录下,对应的网络路径就是http://192.100.100.43:8080/user/index.html;将UserServlet配置到物理路径下的D:\Apache Tomcat 4.0\webapps\examples\WEB-INF\classes\user目录下,对应的网络地址就是http://192.100.100.43:8080/examples/Servlet/user.UserServlet。这些配置信息由index.html中的Action属性标明。

如果希望能够配置到根目录下,则在开始->程序->Apache Tomcat 4 如果希望能够配置到根目录下,则在开始->程序->Apache Tomcat 4.0->Configuration中单击EditServer Configuration,然后找到行: <!-- Tomcat Root Context --> <!-- <Context path="" docBase="ROOT" debug="0"/> - -> 删除第二个<!--和-->,将以上语句变成: <Context path="" docBase="ROOT" debug="0" reloadable="true"/>

将机器重启动,使得配置文件生效,并在D:\Apache Tomcat 4 将机器重启动,使得配置文件生效,并在D:\Apache Tomcat 4.0\webapps\ROOT\WEB-INF路径下建立classes目录,然后将UserServlet.java源文件中的语句行package user;去掉,重新编译成.class类文件并放入此目录,则action属性对应的网络地址为 http://192.100.100.43:8080/Servlet/ UserServlet

13.4 Applet与Servlet通信 Applet与Servlet的通信过程的基本原理相当于HTML网页的POST请求。首先两者之间建立一个连接,使用URLConnection类对象打开连接后,Applet将请求发送给Servlet,Servlet处理请求并返回处理结果。注意,发送请求数据时一定用URLEncoder类的Encode方法进行格式编码,在Servlet端还需用URLDecoder类的Decode方法进行格式解码。 在HTTP协议中POST请求是以参数名=参数值的方式自动进行URL编码后传送的,编程中要手工实现,例如名-值对

qry = SELECT number,code,score from chengji WHERE code='3001' 进行URL编码如下: String qry = URLEncoder.encode("qry","UTF-8") + "=" + URLEncoder.encode(qryString,"UTF-8"); 建立连接时,注意将DbServlet配置到路径D:\Apache Tomcat 4.0\webapps\ROOT\WEB-INF\classes下。

String str = "http://192.100.100.43:8080/Servlet/DbServlet"; URL urlName = new URL(str); 打开连接。 URLConnection uc = urlName.openConnection(); 设置参数。 uc.setDoOutput(true); uc.setDoInput(true); uc.setUseCaches(false);

uc.setRequestProperty("Content-type","application/x-www-form-urlencoded"); 得到数据流发送格式转换后的POST请求 。 DataOutputStream dos = new DataOutputStream(uc.getOutputStream()); dos.writeBytes(qry); 在DbServlet中接收数据并进行解码。 String qry = req.getParameter("qry"); qry = URLDecoder.decode(qry,"UTF-8");

【例13. 2】编写Applet和Servlet交互的程序,使得用户在Applet界面(见图13 【例13.2】编写Applet和Servlet交互的程序,使得用户在Applet界面(见图13.5)上输入数据库查询语句,单击“查询”按钮后,后台Servlet接收请求,对后台数据库进行查询,并将查询结果返回到Applet界面的文本区域内。 图13.5 Applet用户界面

图13.6 Applet和Servlet交互原理图

13.4.1 Servlet文件 首先书写查询数据库的Servlet文件,编译通过后配置到上面提到的路径。 //程序文件名DbServlet.java import javax.Servlet.*; import javax.Servlet.http.*; import java.util.*; import java.sql.*; import java.io.*; import java.net.*;

public class DbServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException PrintWriter out = res.getWriter(); res.setContentType("text/html;charset=GB2312"); //得到Applet请求参数,解码后输出 String qry = req.getParameter("qry"); qry = URLDecoder.decode(qry,"UTF-8"); out.println(qry); Connection dbCon = null; try

{ //同数据库建立连接 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); String dbURL = "jdbc:odbc:STU"; dbCon = DriverManager.getConnection(dbURL,"",""); PreparedStatement p = dbCon.prepareStatement(qry); ResultSet rs = p.executeQuery(); //输出查询结果 while(rs.next()) out.print(rs.getString(1)); out.print(rs.getString(2) + " "); out.println(rs.getInt(3)); }

} catch(Exception e) { out.println("读写数据库出错:" + e.getMessage()); finally try dbCon.close(); out.close();

{ out.println("关闭数据库连接出错:" + e.getMessage()); } };

13.4.2 Applet文件 编写与Servlet通信的Applet文件。 //程序文件名DbApplet.java import java.awt.*; import java.applet.*; import java.awt.event.*; import java.io.*; import java.net.*; public class DbApplet extends Applet implements ActionListener { TextField tfQuery;

TextArea taResults; Button btnExecute; URL chatURL; public void init() { Panel pa = new Panel(); pa.setLayout(new FlowLayout(FlowLayout.LEFT)); pa.add(new Label("查询串:")); tfQuery = new TextField("SELECT number,code,score from chengji WHERE code='3001'",50);

pa.add(tfQuery); btnExecute = new Button("查询"); btnExecute.addActionListener(this); pa.add(btnExecute); add("North",pa); taResults = new TextArea(30,60); add("Center",taResults); chatURL = getCodeBase(); } public void actionPerformed(ActionEvent evt) {

String lbl = evt.getActionCommand(); if(lbl.equals("查询")) { String qryString = tfQuery.getText(); try //查询串编码 String qry = URLEncoder.encode("qry","UTF-8") + "=" + URLEncoder.encode(qryString,"UTF-8"); //打开到DbServlet的连接 String str = "http://192.100.100.43:8080/Servlet/DbServlet";

URL urlName = new URL(str); URLConnection uc = urlName.openConnection(); uc.setDoOutput(true); uc.setDoInput(true); uc.setUseCaches(false); uc.setRequestProperty("Content-type","application/x-www-form-urlencoded"); //获得输出流 DataOutputStream dos = new DataOutputStream(uc.getOutputStream());

//发送编码的查询串 dos.writeBytes(qry); dos.close(); //获取结果,输出 InputStreamReader in = new InputStreamReader(uc.getInputStream()); int chr = in.read(); while(chr != -1) { taResults.append(String.valueOf((char)chr)); chr = in.read(); }

in.close(); } catch(MalformedURLException e) { taResults.setText(e.toString()); catch(IOException e)

13.4.3 HTML文件 最后编写HTML文件,用户最好将它转换成使用插件的文件,使用插件的文件具有较好的应用性。 <head> </head> <body> <center> <applet code = "DbApplet.class" width = "600" height = "600" name = "DbApplet"> </applet> </center> </body> </html>

13.4.4 结果显示 本例中用到的表为STU配置下的Chengji表,部分记录如图13.7所示。查询串分别为: SELECT number,code,score from chengji WHERE code= ' 2001' SELECT number,code,score from chengji WHERE code= ' 1002' 而查询结果如图13.8所示。对照Chengji表,得出查询结果完全正确。

图13.7 STU库中的Chengji表

图13.8 查询结果的输出

习 题 1. 编写Servlet程序,进行数据库访问,将数据库结果以表格的形式显示在HTML网页上。 习 题 1. 编写Servlet程序,进行数据库访问,将数据库结果以表格的形式显示在HTML网页上。 2. 编写Applet和Servlet的交互程序,Applet请求打开一个文本文件,Servlet接收文件的路径和名称,返回文本文件的内容。在Applet界面上的文本框内输入文件路径以及名称,单击“请求”按钮,得到Servlet的响应,返回的内容输出到Applet界面上的文本区域内。设计具体界面时可参考图13.9。

图13.9 用户界面

第14章 Java读写XML技术 14.1 XML简介 14.2 SAX接口解析XML 14.3 DOM接口解析XML 习 题

14.1 XML 简 介 14.1.1 XML定义 XML是SGML一个简化而严格的子集,特别是为Web应用设计的,具有可扩展性、结构性和可检验性。 ●可扩展性指用户可以根据需要自定义新的标识以及属性名,更好地从语义上修饰数据。 ●结构性指XML文件结构可以嵌套,也可以复杂到任意程度。 ●可校验性指XML文件文件可以包括一个语法描述,应用程序可以通过语法描述对此文件进行结构检验。

14.1.2 XML分类 图14.1 XML相关标准的体系结构

XML相关标准主要分为三类,分别是元语言标准、基础标准和应用标准。 1. 元语言标准 用来描述标准的元语言,即XML标准。

2. 基础标准 为XML进一步实用化制定的标准,共分为五类:外围标准、核心标准、操作标准、样式与链接标准、内容描述标准。 (1) 外围标准指Internet网络上统一应用的标准: ● HTTP协议采用请求/应答方式,客户端向服务器提交请求方式、URI、协议版本、客户端信息等,服务器向客户端返回状态信息、实体信息以及实体内容等。 ● URI/URL指资源定位符,用来在网络上实现快速资源定位。 ● Unicode指Internet网上统一传输数据的标准编码。

(2) 核心标准是XML核心的标准。 (3) 操作标准为XML文档的处理提供有效的方法与规则,DOM是与平台无关的,提供一个编程接口。Schema是对DOM的补充,提供一种更为严格的描述XML文档的结构、属性、数据类型等的方法。

(4) 样式与链接标准。 ●CSS是XML文档显示的样式标准。 ● XSL标准可将XML文档形成树状结构,采用元素节点匹配的方式进行转换,因而该标准提供转换和显示的标准。 ● XSLT标准是从XSL中分离出来的,是XML文档的转换标准,可以将XML文档转换为HTML文档并进行显示处理。 (5) 内容描述标准。 RDF(Resourse Description Format)采用XML语法格式处理元数据的应用,是为描述图像文档和它们之间的相互关系定义的一个简单数据模型,为进行资源描述定义了资源描述的规则。

3. 应用标准 XML标准是Internet时代的ASCII标准,主要针对具体的领域进行应用,如cXML是指电子商务XML应用标准、voiceXML指语音XML等。

14.1.3 XML文档的书写 图14.2 XML文档举例

14.1.4 XML文档的解析 图14.3 XML文档的处理过程

14.2 SAX接口解析XML 14.2.1 解析的步骤 (1) 创建SAX解析工厂的实例。 14.2.1 解析的步骤 (1) 创建SAX解析工厂的实例。 SAXParserFactory spf = SAXParserFactory.newInstance(); (2) 创建一个SAX解析器。 SAXParser sp = spf.newSAXParser(); (3) 得到SAX的处理器(处理器由用户自己编写实现)。 SAXHandler handler = new SAXHandler(); (4) 使用用户创建的处理器,解析器解析文件。 sp.parse(new InputSource(reader), handler);

14.2.2 相关类 在J2sdk1.4中的SAX版本为2.0,它提供DefaultHandler(org.xml.sax.helpers.DefaultHandler)接口,通过这个接口实现自己的解析器。接口中需要实现的解析函数为: public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException 读取XML数据的节点元素开始时触发,需要实现这个方法进行标记元素的名字的操作。

public void endElement(String uri, String localName, String qName) throws SAXException 处理节点元素终止时触发,可以添加代码来将节点数据进行存储。 public void characters(char[] ch, int start, intlength) throws SAXException 处理节点之间的数据,可以添加代码来读取节点间的数据值。

【例14.1】编写一个SAX处理器,对14.1节中的person.xml进行解析,输出XML文件节点的标签和节点的值。 分析:类SAXHandler是一个处理类,实现这个DefaultHandler接口时覆盖了上述三个方法,将读取的节点标签和节点的值存入到Hashtable对象中。类中提供了一个方法,返回读取的名-值对的Hashtable对象。输出结果如图14.4所示。源程序代码如下: //程序文件名为Parse.java import java.io.*; import java.util.Hashtable; import org.w3c.dom.*;

import org.xml.sax.*; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.helpers.*; public class Parse { public static void main(String[] args) try

File file = new File("person.xml"); FileReader reader = new FileReader(file); //创建解析工厂实例 SAXParserFactory spf = SAXParserFactory.newInstance(); //创建解析器 SAXParser sp = spf.newSAXParser(); //创建处理类实例 SAXHandler handler = new SAXHandler(); //解析 sp.parse(new InputSource(reader), handler); Hashtable hashTable = handler.getTable(); //输出数据

System.out.println("教师信息表"); System.out.println("姓名:"+ (String)hashTable.get(new String("name"))); System.out.println("学院:" + (String)hashTable.get(new String("college"))); System.out.println("电话:" + (String)hashTable.get(new String("telephone"))); System.out.println("职称:" + (String)hashTable.get(new String("title"))); } catch(Exception e) { System.out.println(e.getMessage()); };

//自定义处理类 class SAXHandler extends DefaultHandler { private Hashtable table = new Hashtable(); private String currentElement = null; private String currentValue = null; public Hashtable getTable() return table; } //覆盖startElement方法,取出节点标签 public void startElement(String uri,String localName,String qName,Attributes attributes)

currentElement = qName; } //覆盖characters方法,取出节点值 public void characters(char[] ch, int start, int length) throws SAXException { currentValue = new String(ch, start, length); //覆盖endElement方法,放入Hashtable

public void endElement(String uri,String localName,String qName) throws SAXException { if (currentElement.equals(qName)) table.put(currentElement, currentValue); } };

图14.4 解析person.xml文件后输出结果

14.3 DOM接口解析XML 14.3.1 解析的步骤 1. 从DOM接口写XML的步骤 14.3.1 解析的步骤 1. 从DOM接口写XML的步骤 (1) 创建DocumentBuilderFactory的一个实例; (2) 创建DocumentBuilder的一个新实例; (3) 构建一个DOM对象; (4) 创建ROOTELEMENT对象; (5) 创建单个ELEMENT节点; (6) ELEMENT创建节点的值; (7) 将ELEMENT挂接到ROOT上; (8) 写入XML文件。

2. 从DOM接口读XML的文件步骤 (1) 创建DocumentBuilderFactory的一个实例; (2) 创建DocumentBuilder的一个新实例; (3) 根据已有的XML文件构建一个DOM对象; (4) 得到ROOTELEMENT对象; (5) 得到单个ELEMENT节点; (6) 得到ELEMENT创建节点的值。

14.3.2 相关类 1. DocumentBuilderFactory类 public abstract class DocumentBuilderFactory extends Object 定义工厂API,使得应用程序得到解析器从XML文档中产生的DOM对象树。 public static DocumentBuilderFactory newInstance() throws FactoryConfigurationError 得到DocumentBuilderFactory的一个实例,这个实例某个时刻只能用于一个线程。 public abstract DocumentBuilder newDocumentBuilder() throws ParserConfigurationException 使用当前配置的方法创建一个新的DocumentBuilder的实例。

2. DocumentBuilder类 public abstract class DocumentBuilder extends Object 定义从XML文档得到DOM文档的API。 public abstract Document newDocument() 得到DOM文档对象的一个新实例,用来构建DOM树。 public Document parse(File f) throws SAXException, IOException 解析给定的XML文档f,返回DOM文档对象。

3. Document类 public interface Document extends Node Document接口代表整个XML文档。 public Element createElement(String tagName) throws DOMException 创建指定的类型的元素tagName。 public Text createTextNode(String data) 使用给定的data字符串创建节点元素的值。

4. Element类 public interface Element extends Node Element接口代表XML文档中的一个元素。 public NodeList getElementsByTagName(String name) 返回给定的name元素下面的所有节点列表,以前向遍历的方式给出。

5. NodeList类 NodeList接口给出一个节点集合,有以下两个方法: public int getLength() 返回节点的个数。 public Node item(int index) 返回第index个节点。

6. Node类 Node接口是整个文档对象模型中最主要的数据类型,它表示文档树中单一的节点。 Node getFirstChild() 返回节点的第一个孩子节点。 String getNodeValue() 返回叶子节点的值。

14.3.3 实例 【例14.2】在用户界面(见图14.5)上输入个人信息后,单击“确定”按钮,程序收集用户信息,存成文档user.xml并显示如下。单击“显示”按钮,读取user.xml文件,将节点标签和对应的节点值显示在文本区域内。 <?xml version="1.0"encoding="gb2312"?> -<UserDate> <Name>王飞</Name> <Number>111122197904290902</Number> <Sex>男</Sex> <Job>军队</Job> <Text>海纳百川,有容乃大;壁立千仞,无欲则刚</Text> </UserData>

1. 程序源文件 //程序文件名UsePanel.java import java.awt.*; import java.awt.event.*; import java.applet.*; import java.applet.Applet; import java.io.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.*;

public class UsePanel extends Applet implements ActionListener { Label lblName,lblNumber,lblSex,lblJob,lblText; TextField tfName,tfNumber; Checkbox chMale, chFemale; CheckboxGroup c; TextArea taText; Choice chJob; Button btnOk,btnDisplay; Panel p1,p2,p3,p4,p5,p6,p7,p8,p9; String strName,strNumber,strSex,strJob,strText; public void init()

//初始化界面对象 lblName = new Label("姓名:"); lblNumber = new Label("身份证号:"); lblSex = new Label("性别"); lblJob = new Label("职业"); lblText = new Label("个性化宣言:"); tfName = new TextField(23); tfNumber = new TextField(20); taText = new TextArea(10,20); c = new CheckboxGroup(); chMale = new Checkbox("男",c,true); chFemale = new Checkbox("女",c,false); chJob = new Choice();

chJob.add("计算机业"); chJob.add("医生"); chJob.add("教师"); chJob.add("军队"); btnOk = new Button("确定"); btnDisplay = new Button("显示"); p1 = new Panel(); p2 = new Panel(); p3 = new Panel(); p4 = new Panel(); p5 = new Panel(); p6 = new Panel();

p7 = new Panel(new BorderLayout()); //设置界面 p1.add(lblName); p1.add(tfName); p2.add(lblNumber); p2.add(tfNumber); p3.add(lblSex); p3.add(chMale); p3.add(chFemale);

p4.add(lblJob); p4.add(chJob); p5.add(p3); p5.add(p4); p6.setLayout(new BorderLayout()); p6.add(p1,BorderLayout.NORTH); p6.add(p2,BorderLayout.CENTER); p6.add(p5,BorderLayout.SOUTH); p7.add(lblText,BorderLayout.NORTH); p7.add(taText,BorderLayout.CENTER);

p8.setLayout(new FlowLayout(FlowLayout.CENTER,30,10)); p8.add(btnOk); p8.add(btnDisplay); p9.add(p6,BorderLayout.NORTH); p9.add(p7,BorderLayout.CENTER); p9.add(p8,BorderLayout.SOUTH); add(p9); //添加监听事件 btnOk.addActionListener(this); btnDisplay.addActionListener(this);

btnDisplay.setEnabled(false); strName = new String(); strNumber = new String(); strSex = new String(); strJob = new String(); strText = new String(); } public void actionPerformed(ActionEvent evt) { String arg = evt.getActionCommand(); //收集用户信息并写入XML文件 if(arg.equals("确定"))

strName = tfName.getText().trim(); strNumber = tfNumber.getText().trim(); if(chMale.getState()) strSex = "男"; else strSex = "女"; strJob = chJob.getSelectedItem(); strText = taText.getText().trim(); try { //创建新的文档对象 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.newDocument(); //创建元素 Element root = doc.createElement("UserData"); Element eName = doc.createElement("Name"); Element eNumber = doc.createElement("Number"); Element eSex = doc.createElement("Sex"); Element eJob = doc.createElement("Job"); Element eText = doc.createElement("Text"); //添加节点

root.appendChild(eName); root.appendChild(eNumber); root.appendChild(eSex); root.appendChild(eJob); root.appendChild(eText); //添加值 eName.appendChild(doc.createTextNode("\n "+ strName +" \n")); eNumber.appendChild(doc.createTextNode("\n " + strNumber + " \n")); eSex.appendChild(doc.createTextNode("\n "+ strSex +" \n")); eJob.appendChild(doc.createTextNode("\n "+ strJob +" \n")); eText.appendChild(doc.createTextNode("\n "+ strText +" \n"));

//创建文件对象 File f = new File("user.xml"); FileOutputStream fOut = new FileOutputStream(f); //初始化xml文件 fOut.write("<?xml version=\"1.0\" encoding=\"gb2312\" ?>\n".getBytes()); //写入文件 fOut.write(root.toString().getBytes()); fOut.flush(); fOut.close(); btnDisplay.setEnabled(true); } catch(Exception e) {

System.out.println(e.getMessage()); } //读取XML文件并输出到文本区域 else if(arg.equals("显示")) { try //得到XML文档对象 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse("user.xml"); Element root = doc.getDocumentElement(); //获取叶子节点值

strName = root.getElementsByTagName("Name").item(0) .getFirstChild().getNodeValue().trim(); strNumber =root.getElementsByTagName("Number").item(0). getFirstChild(). getNodeValue().trim(); strSex = root.getElementsByTagName("Sex").item(0). strJob = root.getElementsByTagName("Job").item(0). strText = root.getElementsByTagName("Text").item(0). //输出到文本区域

taText.setText(""); taText.append("姓名:" + strName + "\n身份证号:" +strNumber + "\n性别:" + strSex + "\n职业:" + strJob + "\n个性化宣言:\n" + strText); } catch(Exception e) { System.out.println(e.getMessage()); public static void main(String args[])

Frame f = new Frame("收集用户界面"); //关闭窗口退出程序 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) System.exit(0); } }); //定义类实例 UsePanel p = new UsePanel(); p.init(); f.add("Center",p); f.setSize(600,450); f.show(); };

2. 结果分析 图14.5显示用户输入的信息,初始化时“显示”按钮变灰,无法使用。单击“确定”按钮时,用户输入的信息存入user.xml文件,“显示”按钮可以使用,此时单击“显示”按钮,将得到如图14.6所示的输出结果。文本区域内是从user.xml文件中读取的节点标签和相应的节点值。

图14.5 信息输入

图14.6 单击“显示”按钮后读出XML文件内容

习 题 1. 编写test.xml文件,要求在浏览器中的显示如下: 习 题 1. 编写test.xml文件,要求在浏览器中的显示如下: <?xml version="1.0"encoding="gb2312"?> -<person> <name>王月</name> <college>交通大学</college> <telephone>2673457</telephone> <title>教授</title> </person>

2. 通过SAX接口将本章person.xml文件用表的格式显示在图形用户界面上,要求文件中的节点标签分别为表的列名称,相应节点的值为一条记录。 3. 根据习题1设计图形用户界面,输入各项内容后,用DOM接口存入my.xml文件。 4. 用DOM接口访问例14.1的person.xml文件,并用树形结构显示在图形用户界面上,要求文件中的节点名称为树结点名称,文件中的值为树的叶子结点。 5. 编写Servlet,读取例14.2的user.xml文件,将内容输出到HTML页面上,显示结果如第13章的图13.4所示。