第七章 异常处理
异常处理的基本概念
l 异常处理机制的作用
– 在编程中,错误总是难免会发生的
– 关键在于发生错误之后,能否捕获错误,如何捕获错误,程序能否从错误中恢复
– 异常处理的目的,不是要避免异常,而是在异常发生时,设法使损失降低到最小
l 错误与异常
– 错误:编译程序时发现的问题,编译时会出现错误或警告。当运行程序时,错误已经不存在
– 异常:编译时没有发现,只有在运行程序的时候,在某种特定的情况下,程序执行出现错误,这时会发生异常
– 例1,算术异常:
l 语句:“y = a / b + c;”,y, a, b, c都是整型变量;
l 如果b = 0,则程序停止运行,抛出算术异常:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptDemo1.main(ExceptDemo1.java:3)
– 例2,数组下标越界异常:
l 数组myArray只能容纳10个元素
l 如果访问第11个元素:“i = 10; myArray[i] = 3;”
l 则程序停止运行,抛出数组下标越界异常:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
at ExceptDemo1.main(ExceptDemo1.java:6)
l 异常的特点
– 编译时不能发现,有些非常隐蔽,很难发现
– 一旦出现异常,程序就无法继续执行下去
l 异常处理的目的
– 在发生异常时,处理好一些相关善后事宜,例如保存文档、删除错误的数据库记录等
– 如果异常仅仅是在局部发生,可以仅仅停止执行这条命令,而让整个程序继续正常运行
异常的捕获
l 通过try ... catch块捕获异常
try {
. . . // 可能会发生异常的程序块
}
catch (Type1 id1) {
. . . // 处理类型1的异常
}
catch (Type2 id2) {
. . . // 处理类型2的异常
}
l 通过try ... catch块捕获异常
– try将一块可能发生异常的代码包起来
– 执行这段代码时,一旦出现异常,就跳出try块,而进入后面的catch部分
– 逐一比较异常类型是否与catch中的异常类型相符,如果符合,则进入catch块内的异常处理程序
– 最后跳出整个try-catch块
l 通过try ... catch块捕获异常
– 在比较异常类型的过程中,如果有一个catch块的异常类型与之相符,Java就会停止继续比较,直接进入这个catch块的异常处理程序
– 在处理完之后,不会继续比较其它catch块,也不会继续执行异常发生点后面的程序,而是跳出整个try-catch块,执行后面的语句
– 如果程序正常运行,则不执行所有catch块中的程序内容
– 异常的类型是通过异常类来描述的
– 异常类是一种特殊的类,平时并不出现
– 在发生异常时,Java会自动产生一个异常对象,并把它“抛出”try块之外
– 抛出的try对象与各个catch块进行匹配
– 匹配原则:如果抛出的异常对象属于catch块里的异常类,或者属于该异常类的子类,则认为异常对象与catch块匹配
– 异常与捕获对象不匹配的解决办法
l 第1种办法:增加一个catch块,捕获算术异常
l 第2种方法:捕获更加通用的运行期异常
catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界。"); }
catch (RuntimeException e) {
System.out.println("程序出现运行期异常。"); }
l RuntimeException包括运行时各种常见的异常,因此大部分异常都能在这里捕获并进行处理。
l 运行期异常应当放在最后,否则会覆盖了其它的特定异常处理
l finally块
– 表示无论是否出现异常,都应当执行的内容
– 完整的try ... catch ... finally块结构:
try {
. . . // 可能会发生异常的程序块 }
catch (Type1 id1) {
. . . // 处理类型1的异常 }
catch (Type2 id2) {
. . . // 处理类型2的异常 }
finally {
. . . // 不管是否发生异常,最后都应处理的程序块 }
l finally块的功能
– 与其它编程语言不同,Java中会自动回收无用对象,因此在finally块中不需要回收已分配的内存
– 主要的工作是关闭已打开的文件,关闭数据库连接,清除在屏幕上显示的内容等
– 注意:程序正常运行时也会执行finally块中的内容,因此finally块中的程序既要满足正常执行的需要,也要满足出现异常时的需要
l 自定义异常
– 第1步:建立自己的异常类
l 所有的异常类都继承自Exception类
l 创建一个异常类,只要继承Exception类,再给出两个构造函数即可
class MyException extends Exception {
public MyException() {}
public MyException(String msg) {
super(msg); }
}
l 第2步:抛出并捕获自定义异常
– Java系统不会自动抛出自定义异常,因为Java不知道什么时候需要抛出它,因此我们只能手动抛出异常
– 利用throw关键字产生一个新的异常对象,随后抛出该异常,正常的程序执行中断
throw new MyException();
– 用throw产生异常对象时,可给出异常原因
throw new MyException("自定义异常,异常原因为……");
l 第2步:抛出并捕获自定义异常
– 用try ... catch块捕获自定义异常
try {
. . . // 其中包括抛出自定义异常的语句}
catch (MyException e) {
String errStr;
errStr = e.getMessage();
System.out.println(errStr); }
– catch块捕获类型相配的自定义异常,利用getMessage函数获得描述字符串,并显示在屏幕上
l 运行期异常的特点
– Java默认自动抛出所有的运行期异常
– 如果有try ... catch块,则运行期异常被try ... catch块捕获
– 如果没有try ... catch块,运行期异常被抛出至主程序外,由Java系统自动处理
– 非运行期异常类必须要手动用try ... catch块捕获,或用throws关键字抛出
标准Java异常
l 异常类的继承关系
– Throwable类是所有错误和异常的父类
l 只有由它继承来的类才能被Java系统或throw抛出
l 只有由它继承来的类才能被catch子句捕获
– Throwable的直接子类:Error和Exception
– Error类包括了严重错误,一般不应当捕获它
– Exception类是所有异常类的父类,如果在catch结构中放入Exception类,那么它可以捕获到所有的异常
– Exception类的直接子类:
l ClassNotFoundException:没有找到类
l CloneNotSupportedException:不支持复制
l IllegalAccessException:没有权限访问
l InstantiationException:不能创建对象
l InterruptedException:线程受到打扰
l NoSuchFieldException:没有该成员变量
l NoSuchMethodException:没有该成员函数
l RuntimeException:运行期异常
l Exception类的直接子类
– 大部分子类在正常情况下都很少出现
– 仅适用于用特殊的方法,躲开了编译器的语法检查的情况
– 最后一个子类:RuntimeException(运行期异常)类非常重要
– 它包括了几乎所有程序运行时可能会遇到,而编译时无法检查到的错误
– 主要的运行期异常
l ArithmeticException:算术异常
l ArrayStoreException:数组存储类型错误
l ClassCastException:变量类型设置错误
l IllegalArgumentException:函数的参数错误
l IndexOutOfBoundsException:数组下标越界
l NegativeArraySizeException:数组长度为负值
l NullPointerException:使用空指针变量
l SecurityException:违反安全要求
l UnsupportedOperationException:操作不支持
第八章 多线程
如何创建一个多线程程序
l 线程的概念
– 一个线程就是一段连续执行的程序
– 连续执行的含义:
l 程序从起始点开始运行
l 每次执行一条语句
l 语句可能有条件判断、循环、函数,但每次只有一条语句在执行
l 程序最后在终点退出
l 线程与可执行程序的区别
– 线程不是一个完整的可执行程序,它不能自动开始
– 线程在程序中运行,由程序来启动一个线程
– 我们前面学到的程序,其核心都是一个线程
– 程序的作用是给线程加上一段创建并启动线程的代码
l 多线程的概念
– 如果一个程序中同时启动了两个或更多个线程,则称为多线程程序
– 有些应用程序需要做到“一心二用”,一个在前台随时听候用户调遣,另一个在后台完成用户交待的工作
– 利用多线程程序就可以做到这一点,只需要让程序启动两个线程,一个在前台接受用户指令,另一个在后台进行具体工作
l 多线程程序的应用
– 应用非常广泛,常用程序几乎都是多线程
– 多线程的典型例子:IE浏览器
l 利用浏览器浏览网页时,受速度限制,网页不可能一下子就全部显示出来
l 对于单线程程序,我们只能耐心地等待网页全部显示出来,然后才能继续控制浏览器
l 多线程程序:线程一,下载并显示网页
l 线程二,在前台响应用户的输入,如果用户想翻动页面,线程二会立即做出响应
l 多线程程序的设计步骤
l (1) 建立自己的线程类
– 线程类继承自Thread类
– 必须覆盖Thread类的run(运行)函数
– 当主程序运行该线程时,实际上就是运行run函数中的代码
– run函数运行完毕以后,线程自动结束
– run函数的作用相当于主程序的main函数
l (2) 在主程序中创建线程对象
– 如下面的例子:
RabbitThread rabbit = new RabbitThread("兔子");
TortoiseThread tortoise = new TortoiseThread("乌龟");
– 构造函数中的参数表示线程名称
– 线程的名称可以随便给出
l (3) 启动线程
– 通过调用线程对象的start函数实现
– 下面的代码启动了两个线程
rabbit.start();
tortoise.start();
l (4) 结束线程
– 当线程类的run函数执行完后,线程会自动结束,无须人工干预
多线程程序的设计要点
l 创建线程的方法
– 方法1:产生一个继承Thread类的线程类
– 方法2:让一个普通类实现Runnable接口
– 两种方法的比较:
l 方法2比方法1要复杂一些
l 如果你的类需要继承其他类(比如后面将要介绍的图形界面类JFrame,小程序类Applet),那么这个类就不能再继承线程类
l 此时就必须通过实现Runnable接口来实现
l 实现Runnable接口的具体做法
– (1)让普通的类实现Runnable接口
class RabbitThread implements Runnable { ... }
– (2) 实现Runnable接口的run函数
public void run() { ... }
– (3) 创建一个普通类的对象
RabbitThread rabbit = new RabbitThread();
l 实现Runnable接口的具体做法
– (4)创建一个线程(Thread)对象,在线程对象构造函数的参数中需要给出普通类的对象,以及线程名称
Thread thread1 = new Thread(rabbit, "兔子");
– (5)调用线程对象的start函数,线程对象会启动线程,并且在该线程中运行普通类的run函数
thread1.start();
l 启动、暂停、停止线程
– 线程的启动:通过线程类(或者其子类)的start函数来实现:
thread1.start();
– 启动线程之后,会自动在线程中运行run函数,run函数相当于普通程序的main函数
– 线程的暂停:包括休眠与等待,二者的不同点在于休眠的线程会在设定的时间后自动醒来,而等待的线程必须被其它线程叫醒
l 启动、暂停、停止线程
– 线程的休眠:利用Thead类的sleep静态函数休眠,函数的参数为休眠时间(毫秒)
Thead.sleep(1500);
– 线程的等待:调用wait函数会使线程处于等待状态,处于等待状态的线程需要其它线程发送notify或notifyAll消息来激活它
– wait/notify常用于线程间的同步
l 启动、暂停、停止线程
– 线程的中止:一个线程不需要特别的代码来中止它,当run函数中的代码都执行完了以后,线程就自然中止了
– 有些线程需要设计成反复循环型,只有人工干预才能中止它
– 例如编写一个在后台运转的时钟程序,每秒钟在屏幕上显示当前时间,我们希望时钟线程持续运转,直到手动停止它为止
l 启动、暂停、停止线程
– 对于这种情况,我们可以在类中加入一个判断变量,当变量值为true时,线程就反复运转,如果变量值为false,则停止线程
class RabbitThread implements Runnable {
public boolean isRunning;
public void run() {
isRunning = true;
while (isRunning) {
. . . // 执行代码
} } }
l 启动、暂停、停止线程
– 在线程内,这段代码形成一个死循环,无法自动跳出循环
– 在多线程程序中,其它线程也可以访问isRunning变量
– 如果其它线程改变了这个变量的值,本线程就会很快停止
– 这种方法从线程外控制了一个线程的结束,在多线程程序设计中,这是一种常用技巧
l 线程的执行方式
– 多个线程间是同时运行的,但PC机一般只有一个CPU,如何同时运行多个线程呢?
– 解决方法:将CPU时间分成很多小的时间片
– 每个时间片内只有一个线程运行,一旦时间用完了,该线程就暂停,将CPU让给其它线程使用
– 目前主流操作系统都是多任务操作系统,它可以同时执行多个应用程序
l 线程的执行方式
– 虽然程序是间断执行的,但CPU执行速度很快,每个时间片又很短,因此用户感觉不到间断,似乎所有的程序都在同时执行
– 当调用Thread.start()函数启动一个线程的时候,并不意味着这个线程会立刻执行,只是将线程放到等候执行的队列中,与其它数十个应用程序的上百个线程一起等候执行
– 线程的调度:
l 操作系统通过复杂的调度程序对CPU进行调度
l 调度的目的是让上百个线程都能有条不紊地进行处理
l 避免出现执行混乱
l 避免出现某个线程长久得不到处理的情况
– 线程的优先级:
l 线程具有优先级
l 高优先级的线程比低优先级的线程更早执行
l 还可以打断低优先级线程的运行,抢先执行
l 线程的优先级
– 在Java中,线程的优先级通过线程对象的setPriority函数设置
// 将线程设为最低优先级
thread1.setPriority(Thread.MIN_PRIORITY);
// 将线程设为最高优先级
thread1.setPriority(Thread.NORM_PRIORITY);
// 设为普通优先级(缺省值)
thread1.setPriority(Thread.MAX_PRIORITY);
// 设优先级为3
thread1.setPriority(3);
l 线程优先级——演示
– 龟兔赛跑例子,将兔子线程的优先级设为最低,乌龟线程的优先级设为最高:
rabbit.setPriority(Thread.MIN_PRIORITY);
tortoise.setPriority(Thread.MAX_PRIORITY);
– 为了充分演示优先级的作用,将两个线程类的休眠时间设为0:
sleep(0);
– 程序运行结果
虽然兔子线程先启动,但乌龟线程具有更高的优先级,因此后者立即抢断了前者的执行,直到乌龟线程全部执行完后,才轮到兔子线程的执行
– 如果将休眠时间增加1毫秒:
sleep(1);
线程间的同步
l 线程间的同步概念
– 线程之间的执行顺序是随机的,我们不能设定某个线程先执行、某个线程后执行
– 有时,需要对两个线程进行同步,例如保证在线程写数据之后再调另一线程读数据
– 在Java中,线程间的同步是通过wait/notify函数来实现的
l 线程间同步的方法
– 设置标志位hasData检查是否有数据
– 若无数据,则调用wait()函数让线程暂停
– 另一个线程写入数据之后,利用notify函数叫醒前一个线程
– 前一线程被叫醒后,再次检查标志位,如果有数据,则读入数据,清空标志位,然后再次进入暂停状态
第九章 IO流库
写入和读出数据文件
l 数据文件的写入和读出
– 数据的写入和读出是编程语言必备的功能
– Java的输入/输出操作采用数据流的形式
– 数据流的两种形式:16位字符或8位字节
– 数据流的操作对象:
l 数据文件的读写
l 线程间的数据传输
l 网络间的数据传播
l 数据文件的读/写步骤:
– 导入输入/输出包
l 利用import关键字导入java.io.*包
l 输入/输出异常:IOException类,它不属于运行期异常,因此必须利用try...catch结构或throws关键字捕获
public static void main(String[] args) throws IOException { ... }
– 创建文件对象
l 对每个数据文件,都必须有一个文件类(File)对象与其关联
– 创建文件对象
l 对数据文件的操作反映为对文件对象的操作
l 构造一个文件对象,并在构造函数中给出文件名,就将对象与文件关联起来了
l File对象是不可变的,也就是说,一旦创建了一个File对象,它的绝对路径和文件名就不能改变
l 在本例中,我们创建了两个文件对象,file1和file2,分别关联读出的文件和写入的文件
File file1 = new File("data.txt");
File file2 = new File("dest.txt");
– 创建读出/写入操作的对象
l 文件对象仅仅表示文件本身,对文件进行读/写操作还需要读/写操作对象来完成
l 读出文件中的内容采用FileReader对象
l 在文件中写入内容采用FileWriter对象
l 在文件操作对象的构造函数中给出文件对象
FileReader reader1 = new FileReader(file1);
FileWriter writer1 = new FileWriter(file2);
数据文件的读/写操作
l FileReader对象的read函数用于从文件中读出一个字符,这个字符是整数类型的。如果read函数的返回值是-1,则说明到了文件的末尾
l FileWriter对象的write函数用于在文件的末尾写入一个字符
l 注意:write函数的参数是整数类型(32位),但实际写入的是一个字符(16位)
l Java在读/写文件时不需要事先打开文件,Java会在读/写的时候自动打开文件
写入和读出数据文件
– 关闭文件
l 无用对象回收机制会在回收文件读写对象时自动关闭文件,但对象回收的时间不定
l 因此,在操作完文件以后,应当及时关闭文件
l 关闭文件时调用文件读/写对象的close函数,而不是文件对象的关闭函数
l 本例中,需要关闭两个文件:
reader1.close();
writer1.close();
l 二进制数据文件的读写
– 很多情况下,应用程序希望将数据以二进制的形式保存在数据文件中
– 这种形式的文件无法直接阅读,但效率比文本文件更高,程序中使用也更加方便
– FileReader/FileWriter类主要用于文本文件的读写操作
– DataInputStream和DataOutputStream类用以完成二进制数据文件的读写
l 二进制数据文件的读写步骤
– 创建二进制文件对象
l 二进制文件对象不能使用File对象,而应使用FileInputStream和FileOutputStream对象
l FileInputStream对象用于文件读出
l FileOutputStream对象用于文件写入
l 二进制数据文件一般不能直接阅读,因此后缀也不要设为“txt”,以免误导用户
FileInputStream file1 = new FileInputStream("save1.dat");
FileOutputStream file2 = new FileOutputStream("save2.dat");
– 创建文件读写对象
l DataInputStream类用于文件读出的操作
l DataOutputStream类用于文件写入的操作
l 在DataInputStream类的构造函数中,需要给出FileInputStream对象,作为与文件关联的对象
l 在DataOutputStream类的构造函数中,需要给出FileOutputStream对象,作为与文件关联的对象
DataInputStream in1 = new DataInputStream(file1);
DataOutputStream out1 = new DataOutputStream(file2);
– 用DataInputStream类读出数据
l DataInputStream可以读出各种简单类型数据
l 读出每种数据的函数如下:
boolean readBoolean(); // 读出逻辑值
byte readByte(); // 读出字节型整数
char readChar(); // 读出一个字符
short readShort(); // 读出短整数
int readInt(); // 读出整数
long readLong(); // 读出长整数
float readFloat(); // 读出单精度浮点数
double readDouble(); // 读出双精度浮点数
– 用DataOutputStream类写入数据
l DataOutputStream可以写入各种简单数据数据
l 写入每种数据的函数如下:
void writeBoolean(boolean b); // 写入逻辑值
void writeByte(int v); // 写入字节型整数
void writeChar(int v); // 写入一个字符
void writeShort(int v); // 写入短整数
void writeInt(int v); // 写入整数
void writeLong(long v); // 写入长整数
void writeFloat(float v); // 写入单精度浮点数
void writeDouble(double v); // 写入双精度浮点数
void writeChars(String s); // 写入字符串
– 关闭文件
l 在读取或写入文件之后,同样应当调用文件读写对象的close函数关闭文件
l 在写入文件时,内容往往只写在缓存里,只有在关闭文件时才会真正将内容写入
l 关闭文件的代码如下:
in1.close();
out1.close();
文件与目录操作
l File对象的功能
– 功能不仅限于关联一个文本文件
– 具有强大的文件和文件夹操作能力
– 可以实现常用的文件和文件夹操作功能
l File对象的创建
– 创建File对象时必须给出文件或文件夹名:
File file1 = new File("C:/WINNT/Greenstone.bmp");
File file2 = new File("C:/Temp");
File file3 = new File("data.txt");
l File对象的特性
– File对象是不可变的,一旦创建了一个File对象,它的路径和文件名就不能改变
– File对象不一定要指向实际存在的文件或文件夹,它可以指向一个将要创建或已删除的文件
– 可以这样理解:File对象中存储的仅仅是路径和文件名,并不要求文件实际存在
修改文件、文件夹
l 修改文件、文件夹的指令
– 创建文件
boolean createNewFile();
l 如果文件不存在,则创建一个新文件;如果文件存在,则不进行任何操作,返回false
– 创建子目录
boolean mkdir();
boolean mkdirs();
l 这两个函数的功能都是创建子目录,子目录名存储在File对象中
l 修改文件、文件夹的指令
– 创建子目录
l mkdir只能创建一个子目录
l 例:路径名为“C:/JavaApp/IOTest<chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="1" unitname="”">1”</chmetcnv>,如果 “C:/JavaApp”目录不存在,则无法创建子目录
l mkdirs函数可以创建一串子目录
l 对于上面的例子,如果“C:/JavaApp”目录不存在,那么mkdirs函数会首先在C盘根目录下创建JavaApp子目录,再在这个子目录下创建下一级IOTest1子目录
l 修改文件、文件夹的指令
– 重命名/移动文件
boolean renameTo(File dest);
l 作用:给文件或文件夹改名。如果dest所指的路径与文件原来的路径不同的话,就会将文件或文件夹移动到目标位置
– 删除文件
boolean delete();
l 删除File对象所指向的文件或文件夹。文件夹必须是空的,否则无法删除
检查文件/文件夹状态
l 检查文件/文件夹状态的函数
– 检查文件是否存在
boolean exists();
l 如果File对象的文件或文件夹存在,则返回true
– 检查是否为文件
boolean isFile();
l 如果File对象所指的是一个文件,则返回true
– 检查是否为文件夹
boolean isDirectory();
l 如果File对象所指的是一个文件夹,则返回true
l 检查文件/文件夹状态的函数
– 查询文件长度
long length();
l 返回文件长度,文件夹的长度为0
– 查询文件修改日期
long lastModified();
l 返回文件上次修改的日期时间。
– 查询文件是否被隐藏
boolean isHidden();
l 检查文件或文件夹是否被隐藏。
l 检查文件/文件夹状态的函数
– 列出文件夹内的所有文件名
String[] list();
l 这个函数列出File所指向的子目录下的所有文件和文件夹名称。
– 列出文件夹内的所有文件
File[] listFiles();
l 这个函数的功能与list类似,它列出File所指向的子目录下的所有文件和文件夹,并把它们保存在一个File数组里。
获得文件/文件夹名称
l 文件名和相对路径
– 获得文件名和相对路径的函数:
String getName();
String getPath();
– getName函数获得文件名,不包含路径
– getPath函数获得文件所在路径和文件名称
– 如果文件是以相对路径形式给出的,例如“../IOTest1/data.txt”,那么getPath函数得到的也是相对路径
获得文件/文件夹名称
l 绝对文件名和绝对路径
– 获得绝对文件名和绝对路径的函数:
File getAbsoluteFile();
String getAbsolutePath();
– 绝对路径的获得方法:
l 如果文件本来就是以完整路径表示的,例如“C:/WINNT/Greenstone.bmp”,则绝对路径不变
l 如果文件是以相对路径来表示的,那么绝对路径就是将程序当前所在的路径加到相对路径之前,如:“C:/JavaApp/FileDemo/../IOTest1/ data.txt”
获得文件/文件夹名称
l 父文件夹
– 获得父文件夹的函数
String getParent();
File getParentFile();
– getParent函数获得文件所在的文件夹名称
– getParent函数不分析文件的绝对路径
– 例如:对于“../IOTest1/data.txt”,用getParent函数得到的结果是“../IOTest1”。
– getParentFile函数以File对象的形式返回文件所在的文件夹。
临时文件产生与自动删除
l 临时文件的产生
– 产生临时文件的函数形式:
public static File createTempFile(String prefix, String suffix);
– createTempFile用来自动产生临时文件
– 参数分别是临时文件名的前缀和后缀
– 文件名随机产生,确保不会重名
– 临时文件产生在系统指定的临时目录下
– 系统自动产生一个空的文件,并返回与该文件关联的File对象
l 临时文件的删除
– 设置自动删除临时文件的函数:
public void deleteOnExit();
– deleteOnExit函数给临时文件设置一个“退出时删除”的属性
– 当程序正常退出时,会自动删除该文件
– 一般在产生临时文件之后随即执行该函数
– 注意deleteOnExit设置后就不能清除,使用时应避免将正常的文件删除
第十一章 Java小程序(Applet)
Java 小程序初步
l Java小程序(Java Applet)概念
– 不同于标准的可以独立运行的Java程序
– Java小程序不能直接运行
– 需要嵌入在网页里,使用浏览器观看
– 属于图形用户界面(GUI)的一种
– 基于AWT GUI的小程序:已很少使用
– 基于Swing GUI的小程序:得到越来越广泛的使用
l 建立小程序的基本步骤
– (1) 创建一个小程序类
l 首先需要创建一个小程序类
l 基于Swing的小程序是从JApplet类继承而来
l 为了使用小程序,需要导入两个包:javax.swing和java.awt
import javax.swing.*;
import java.awt.*;
public class JAppletDemo extends JApplet {
. . . // 程序主体
}
– (2) 在小程序界面上放置各种图形界面组件
l 一个小程序相当于在网页内嵌入的一个窗体
l 窗体自带一块内容窗格,其上放各种组件
l 有关内容窗格、Swing组件的知识,会在下一章详细介绍,这里先利用它们:
public void init() {
JPanel panel1 = new JPanel();
JLabel label1 = new JLabel("Java小程序测试。");
panel1.add(label1);
getContentPane().add(panel1);
}
– (3) 在Applet的启动函数中显示图形界面
l 小程序中没有main函数,也不从main函数开始
l 小程序有4个函数,在启动与停止时自动调用
初始化小程序:
– 用框架对象的getContentPane()函数来获得窗格,再调用窗格的add()函数放置面板
l (3) 设置顶层容器
public static void main(String[] args) {
// 创建一个JFrame对象,作为顶层容器对象
JFrame frame = new JFrame("SwingApplication");
// 创建一个面板对象,将它放到主框架的窗格部分
JPanel panel1 = new JPanel();
frame.getContentPane().add(panel1, BorderLayout.CENTER);
. . . // 添加其它组件
// 调整窗体、组件的大小和位置
frame.pack();
// 显示窗体
frame.setVisible(true); }
l (4) 设置按钮和标签
– 下面的代码生成一个按钮:
JButton button = new JButton("这是一个按钮");
button.setMnemonic(KeyEvent.VK_I);
button.addActionListener(...建立一个动作监听器...);
– 第1行:创建一个按钮,按钮上的文字为“这是一个按钮”
– 第2行:为按钮定义了快捷键“Alt-i”
– 第3行:添加一个事件处理对象,用来处理用户点击按钮的动作
l (4) 设置按钮和标签
– 下面这段代码用来生成标签:
// 定义一个字符串,用于存储标签上显示的文字
private static String labelPrefix = "按钮点击的次数:";
private int numClicks = 0;
// 创立一个标签
final JLabel label = new JLabel(labelPrefix + "0 ");
...// 在点击按钮的事件处理函数中
numClicks++;
label.setText(labelPrefix + numClicks);
l (4) 设置按钮和标签
– 生成标签的步骤:
l 用new关键字产生一个标签对象
l 在标签对象的构造函数中给出标签所显示的内容即可
l 利用面板对象的add函数将标签放到面板上
– 在上面的程序中,利用setText函数更改标签的内容,每当用户点击一下按钮,标签上的数字就会加1
l (5) 将组件放到容器上
– 为便于排列,组件一般都不直接放在框架上,而是放在一个或多个面板上
– 下面的代码将按钮和标签放到面板上
JPanel panel1 = new JPanel();
panel1.setBorder(BorderFactory.createEmptyBorder(30, 30, 10, 30));
panel1.setLayout(new GridLayout(0, 1));
panel1.add(button);
panel1.add(label);
l (5) 将组件放到容器上
– 面板一般也不直接放在框架上,而是放在框架的窗格上
– 下面的代码将面板放在框架的窗格上
frame.getContentPane().add(panel1, BorderLayout.CENTER);
– frame.getContentPane()函数获得框架的窗格,再通过窗格的add函数放上面板
– add函数的参数分别是面板对象,以及面板在窗格中的步局位置
l (6) 为组件增加边框
– 下面的代码为面板增加边框:
panel1.setBorder(BorderFactory.createEmptyBorder(30, 30, 10, 30));
– 边框对象利用BorderFactory对象的静态函数
– createEmptyBorder产生
– 函数的4个参数分别表示上边框、左边框、下边框和右边框的宽度
– 这行代码将面板的上边框设为30个象素,左边框设为30个象素,下边框设为10个象素,右边框设为30个象素
l (7) 处理事件
– 本例中有两个事件处理过程:点击按钮事件和视窗关闭事件。下面是事件处理类
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
numClicks++;
label.setText(labelPrefix + numClicks); }
});
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0); }
});
l (7) 处理事件
– 每个事件处理需要用一个事件处理类来实现
– 这里为了简化程序,用了匿名类,因此程序有些难以理解
– 在第14章“事件处理”中,我们将介绍用普通的类来进行事件处理的方法
– 这样做虽然增加了类的数量,但使得程序更容易理解,也更容易维护
l (8) 辅助技术支持
– 一个好的程序应当为辅助技术提供支持,这样使得屏幕阅读器、布莱叶盲文器等设备也能够显示用户界面
– 下面的代码提供辅助技术支持:
label.setLabelFor(button);
– 代码的作用:指明标签用来说明按钮
– 普通人很容易可以看出标签的作用是用来说明按钮,但辅助设备却不能看出这点,因此需要通过语句说明
Swing容器和组件的层次
l Swing图形界面的三个层次
– 顶级容器:框架、对话框、小程序
– 中间级容器:面板
– 原子组件:按钮和标签等
– 实际中,虽然顶级容器-中间级容器-底层组件的三层结构不会改变,但每层中可能会分出更多更细的级别,以满足实际需要
l 顶层框架
– 顶层框架是一个JFrame对象,它就是我们平时所见到的应用程序图形界面本身
– 一个简单的框架至少包括边框、顶栏、以及内容窗格
– 顶栏位于框架的顶部,它的右侧放置最大化、最小化和关闭按钮,左侧放置框架菜单
– 内容窗格是框架的主体部分,所有的二级容器、组件都只能放在内容窗格内
l 顶层框架
– 内容窗格相当于在框架的内部铺设的一个大面板,所有内容都放在这个面板上
– 这个面板并非是假设,它是实际存在的。内容窗格作为JFrame对象的一个成员变量,它的类型就是JPanel,也就是面板类
– 通过JFrame对象的getContentPane()函数获得内容窗格:
JFrame myFrame = new JFrame();
JPanel contentPane = myFrame.getContentPane();
l 顶层框架
– JFrame框架的成员:
l rootPane(根窗格)
l layeredPane(分层窗格)
l contentPane(内容窗格)
l glassPane(玻璃窗格)
l 更复杂的图形界面程序会有菜单栏、快捷工具栏和状态栏
l 根窗格
– 框架对象(或者对话框对象、JApplet小程序对象)中包括一个JRootPane对象rootPane
– 根窗格中包括了分层窗格、内容窗格、工具栏以及玻璃窗格
– 根窗格的作用是管理这些子窗格,它本身是不可见的,用户根本不知道有根窗格的存在
– 调用getRootPane函数来获得根窗格:
JFrame frame = new JFrame();
JRootPane rootPane = frame.getRootPane();
l 根窗格
– 根窗格的主要功能都被封装入主框架中,可以通过主框架的函数直接访问各功能窗格
– 唯一需要用根窗格的函数直接设定的功能是设定缺省按钮
– 根窗格的setDefaultButton函数可以将一个按钮设置为缺省按钮
– 缺省按钮的作用是响应用户的回车键消息
l 根窗格
– 在用户登录时,当用户输入用户名和密码之后,一般习惯于直接敲回车登录,下面的代码实现这一功能:
JRootPane rootPane = frame.getRootPane();
rootPane.setDefaultButton(button1);
– 设置了缺省按钮的登录界面如下图所示:
l 分层窗格
– 分层窗格(layeredPane)是根窗格的成员,在分层窗格中包括内容窗格和菜单栏
– 分层窗格是一个多层的容器,相互重叠的组件可以按照它们重叠的次序在界面上显示,上层的组件覆盖在下层组件之上
– 分层窗格的主要作用是为其中的每一层提供一个深度值(z方向,也就是垂直于屏幕方向的数值)
l 分层窗格
– 当你将一个组件放在分层窗格上的时候,你需要指定它的深度
– 深度值越大,组件就越靠近上层
– 屏幕是一个平面,它不能显示深度,因此分层窗格只能控制组件间的相互覆盖
– 上层的组件会覆盖下层组件,例:弹出式菜单会覆盖在图形界面之上
l 内容窗格
– 内容窗格位于分层窗格的最底层
– 尺寸等于框架减去边框、顶栏、菜单栏、工具栏、状态栏之后的尺寸,随框架而变
– 是主框架中最重要的部分,图形界面的主要组件和功能功能都放在内容窗格上面
– 内容窗格可以用框架对象的getContentPane()函数来获得,这个函数实际上是获取框架的根窗格,通过根窗格得到内容窗格
l 菜单栏
– 菜单栏与内容窗格同级
– 可选的,有些简单的框架可能没有菜单栏
– 通过根窗格的setJMenuBar函数把菜单栏放到窗格上,函数的形式为:
public void JRootPane.setJMenuBar(JMenuBar menu)
– 其中menu为一个JMenuBar对象,用来表示一个菜单
l 玻璃窗格
– 完全透明,可以拦截输入事件
– 通过在一个区域设置玻璃窗格,可以阻碍那里的组件接收消息,或者临时显示图画
l 面板的层次
– 内容窗格上是若干面板,用于放置组件
– 面板之间可以进一步细分级别,目的是让界面布置更加合理,层次更加明晰
l 对话框
– 与框架类似,属于顶级容器组件
– 存在多种标准对话框,用于实现特定功能
– 对话框不能单独存在,必须从属于程序的主框架,但它有自己的内容窗格和组件
– Java将对话框的定义限制为从框架中弹出的一个子窗口,并限定对话框不能单独存在,实际上清晰地界定了框架与对话框的区别,有利于界面设计
布局管理器概念
l 布局管理器的功能
– 同样按钮、不同管理器的界面对比:
l 布局管理器的功能
– 布局管理器决定容器中组件的尺寸和位置
– 上图显示了5种常用的布局管理器:
l BorderLayout(边界布局)
l BoxLayout(盒式布局)
l FlowLayout(流式布局)
l GridBagLayout(格袋布局)
l GridLayout(格式布局)
l 布局管理器的功能
– 每个容器都有一个缺省的布局管理器
– 组件可以提供大小和排列的建议,但最终大小和位置由布局管理器决定
– 所有的JPanel(面板)对象在缺省状态下都使用FlowLayout(流式布局)
– 内容窗格缺省使用BorderLayout(边界布局),特点是菜单栏、工具栏的高度固定,并且可以在界面上随意拖动
l 布局管理器的功能
– 使用add()函数将组件加入容器时,必须考虑布局管理器的因素
– 一些布局管理器需要提供相对位置、大小等信息,而大部分布局管理器无需提供信息
– 使用setLayout函数更换布局管理器:
JPanel panel1 = new JPanel();
panel1.setLayout(new BorderLayout());
– 若参数为null,则不要布局管理器:
panel1.setLayout(null);
l 提供尺寸与位置提示
– 设定组件的最小尺寸、最佳尺寸和最大尺寸
// 构造函数的两个参数分别是宽度和高度
Dimension minimumSize = new Dimension(30, 45);
Dimension preferredSize = new Dimension(80, 100);
Dimension maximumSize = new Dimension(150, 180);
JButton myButton = new JButton();
myButton.setMinimumSize(minimumSize);
myButton.setPreferredSize(preferredSize);
myButton.setMaximumSize(maximumSize);
l 提供尺寸与位置提示
– 建立组件的子类,并覆盖它的相应函数
l getMinimumSize:设定最小尺寸
l getPreferredSize:设定最佳尺寸
l getMaximumSize:设定最大尺寸
– 布局管理器通过调用这三个函数来获得组件的最小尺寸、最佳尺寸和最大尺寸
– 覆盖这三个函数以后,就可以直接告诉布局管理器组件的最小、最佳和最大尺寸
l 提供尺寸与位置提示
– 提供横向、竖向的排列提示
myButton.setAlignmentX(Component.LEFT_ALIGNMENT);
myButton.setAlignmentY(Component.TOP_ALIGNMENT);
– setAlignmentX的参数值:
Component.LEFT_ALIGNMENT(左对齐)
Component.RIGHT_ALIGNMENT(右对齐)
Component.CENTER_ALIGNMENT(中间对齐)
– setAlignmentY的参数值:
Component.TOP_ALIGNMENT(顶对齐)
Component.BOTTOM_ALIGNMENT(底对齐)
Component.CENTER_ALIGNMENT(中间对齐)
l 组件之间的间隙
– 影响组件之间间隙的因素:
– 布局管理器:某些布局管理器会自动在组件间插入间隙,或指定间隙的总和
– 不可见的组件:可创建一些不在屏幕上显示的组件,但它们仍然占据图形界面中的相应空间,以达到调整屏幕显示的目的
– 空边框:通过给组件加上空的边框来改变空隙的大小和组件的距离
l 布局管理器工作的步骤
– (1) 在创建了JFrame对象之后,调用pack函数,这个函数使框架调整成合适的大小
– (2) 框架的合适大小由框架的边框尺寸加上内容窗格、标题栏的尺寸组成
– (3) 面板的布局管理器向它直接包含的每个组件询问它们合适的尺寸
l 对于元素组件,会直接汇报合适大小
l 对于面板,会询问它所包含的组件,通过递归的方法获得
事件处理基本原理
l 事件处理的基本概念
– 事件处理对象监听各种事件(例如用户键入字符或点击鼠标),并执行相应的操作
– 常用的事件处理类:
l 键盘输入焦点
– 含义:键盘操作针对这个组件而进行
– 文本框:得到焦点以后,光标就会出现在文本框里,用户可以在文本框里输入文字
– 列表框或下拉框:得到焦点以后,列表项或下拉项周围会出现虚线框,用户可以通过键盘上的上下键来移动选择某项
– 对于按钮:得到焦点以后也会出现虚线框,用户按空格键就相当于按动按钮,或者选择单选框、复选框
l 事件监听器
– 每个事件都由一个对象表示,对象里有这个事件的信息,以及事件源的标识
– 事件源通常是一个组件,但也可以是其它类型的对象
– 一个事件源可以有好几个监听器在同时监听,一个监听器也可以同时监听几个事件源
l 事件处理步骤:
– (1) 为事件定义事件处理类:
public class MyActionListenerClass implements ActionListener {
JFrame mainFrame;
MyActionListenerClass(JFrame mainFrame) {
this. mainFrame = mainFrame; }
. . . }
– 本例中,事件处理类,这个类实现了ActionListener监听器接口
– 当事件发生时,组件就将调用接口的actionPerformed函数
– (2) 将事件处理对象注册为组件的监听器
– 例如:刚才定义的MyActionListenerClass类注册为myButton1组件的ActionListener事件的监听器:
myButton1.addActionListener(new MyActionListenerClass(this));
– (3) 编写代码实现监听器接口的函数。如本例中,需要实现actionPerformed函数:
public void actionPerformed(ActionEvent e) {
mainFrame.myButton1ActionPerformed(e); }
――――――――――――――――――――――――――――
public void init();
加载小程序时自动调用,完成初始化过程
l 执行小程序:
public void start();
在初始化以后调用,不需要再次初始化
– (3) 在Applet的启动函数中显示图形界面
l 停止执行小程序:
public void stop();
当用户离开小程序所在的网页,或关闭浏览器时,这个小程序就被自动停止
l 卸载小程序:
public void destroy();
在卸载之前,调用此函数完成最后清除工作
l 小程序不一定需要重载全部的4个函数,可根据需要进行重载
– (4) 在网页中嵌入小程序
l 小程序只能嵌入网页中执行。下面是一个示例的简单网页的源代码:
<html>
<head>
<title>Java小程序演示</title>
</head>
<body bgcolor="#FFFFFF">
这是一个用于演示Java小程序的页面。<br>
<applet code="JAppletDemo.class" width=300 height=100></applet>
</body>
</html>
小程序的生命周期
l 小程序的生命周期:
– 包括载入、运行、离开、返回、卸载、重载等阶段
– 不是所有的阶段都必须要运行
l 小程序的载入与运行
– 当用户第一次打开网页时,如果网页中有小程序,浏览器就会自动载入它
l 小程序的载入与运行
– (1) 创建一个小程序对象,它必须是Applet类的子类,作为小程序的控制对象;
– (2) 调用小程序对象的init函数,对小程序自身进行初始化;
– (3) 调用小程序对象的start函数,启动小程序
– 初始化与启动的区别:
l 初始化完成布置界面的工作,例如在界面上放置文字、按钮等。而启动则是让小程序运行起来
l 小程序的离开与返回
– 小程序是在网页中运行的
– 如果用户点击其它链接进入别的网页时,就会通过调用stop函数停止小程序的运行
– 但小程序本身不会卸载,因为用户还有可能回到这个网页
– 当用户返回这个网页时,浏览器再调用start函数启动小程序的运行,此时不需要完成繁琐的初始化工作
l 小程序的卸载
– 当关闭浏览器时,小程序会被彻底卸载
– 卸载的步骤有三步:
– (1) 调用stop函数停止小程序;
– (2) 调用destroy函数进行最后的清除过程;
– (3) 清除Applet对象,以及小程序中产生的任何其它对象。
l 小程序的卸载
– 由于Java会自动清理对象,并且小程序不能读写文件,因此destroy函数一般为空
l 小程序的重载
– 当用户刷新浏览器的当前页面时,就会对小程序进行重载
– 重载的过程:先卸载小程序,再重新下载小程序代码,载入、运行小程序
在小程序中显示图片
l 在小程序中显示图片
– 在图形界面中,图片用ImageIcon对象表示
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
label1.setIcon(new ImageIcon("test.jpg"));
}
});
l 程序创建一个ImageIcon对象,这个对象的构造函数中的参数为图片文件名
l 调用label1的setIcon函数,让文本标签也显示ImageIcon图片
在小程序中显示图片
l 显示图片的注意事项
– 传输速度问题
l 图片比较大,一般在几十到几百kB之间,因此必须考虑下载速度问题
l 一般需要编写多线程程序,让后台线程从网络上下载图片,而前台线程先完成没有图片的小程序界面,供用户看
l 否则,在所有图片下载完成之前,整个小程序的载入不能结束,也就不能显示,从而意味着在载入期小程序没有任何响应
– 路径问题
l 小程序是准备嵌入网页中,放在远方的服务器上,供别人浏览的
l 类似于“C:/MyJava/MyApplet”的本地路径没有任何意义,必须使用相对路径来调用小程序和小程序中的图片
l 假如网页、图片和小程序位于同一个目录下,情况最简单,只需要直接使用文件名即可,浏览器会自动在文件名前加上合适的网络地址
– 路径问题
l 如果网页和小程序放在不同目录下,则需要在<applet>标识的“codebase”参数中给出路径:
<applet code="JAppletDemo4.class" codebase="code/" width=300 height=100></applet>
l 尽量避免将小程序和图片放在不同目录下,因为小程序不能从本地计算机上读取或写入文件,因此也不能使用相对路径读取图片
l 只能用Applet类的getCodeBase函数获得小程序的网址(URL),再根据相对路径得到图片的网址,然后才能下载
向小程序传输参数
l 向小程序传输参数的方法
– 利用<applet>和</applet>标识之间的<param>项,从网页向小程序传输参数:
< applet code="JAppletDemo5.class" width=300 height=100>
<param name=welcomeText value="欢迎使用Java小程序!">
<param name=buttonText value="按这里">
</applet>
l 向小程序传输参数的方法
– 在小程序中,参数值通过getParameter函数获得:
String welcomeText, buttonText;
welcomeText = getParameter("welcomeText");
buttonText = getParameter("buttonText");
JButton button1 = new JButton(buttonText);
. . .
label1.setText(welcomeText);
l 本例中,按钮上的文字以及欢迎文字都通过参数传进来,使得程序使用更加灵活
小程序的安全限制
– 为了避免有人编写带有恶意代码的小程序来控制甚至毁坏浏览网页的客户机,Java对小程序进行了非常严格的安全限制:
– (1) 小程序不能导入其它库或者以其它语言写的代码,小程序只能使用它们自己的代码,以及标准Java库。
– (2) 小程序不能读出或写入客户机的文件,它只能读出以网址形式给出的文件,例如网络上的一副图片。
– (3) 小程序不能与服务器以外的任何主机建立网络连接。这样就保证了小程序不会偷偷连到某个黑客站点或广告站点。
– (4) 小程序不能执行客户机上的任何程序。
– (5) 小程序只能读取客户机上的部分系统信息,这些信息是保证小程序能够运行,同时又不泄露用户隐私的最少信息。
– (6) 用小程序产生的视窗与标准视窗之间有显著区别,小程序视窗的底部有警告信息。