第5章异常处理和多线程Word文档下载推荐.docx
《第5章异常处理和多线程Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《第5章异常处理和多线程Word文档下载推荐.docx(21页珍藏版)》请在冰点文库上搜索。
算术错误,如除数为0
ArrayStoreException
数组存储空间不足
NullPointerException
访问一个空的对象中的方法或变量
SecurityException
安全性错误,如Applet要读写文件
ArrayIndexOutOfBoundsException
数组越界使用
IOException
输入输出错误
ClassNotFoundException
没有找到要装载使用的类
NoSuchMethodException
没有找到要调用的方法
StringIndexOutOfBoundsException
字符串序号越界
FileNotFoundException
没有找到指定的文件或目录
UnknownHostException
无法确定主机的IP地址
MalformedURLException
URL格式错误
Exception类的两个构造函数是:
publicException();
publicException(Strings);
其中,第二个构造函数可以接受字符串参数传入的信息,这个信息是对异常对象所对应的错误的描述。
Exception类也从父类Throwable那里继承了一些方法,最常用的两个方法是:
publicStringtoString();
publicvoidprintStackTrace();
其中,第一个方法是返回描述当前Exception类信息的字符串。
第二个方法的主要功能是在屏幕上输出异常信息,这个异常信息是由Java系统对用户程序执行过程中产生的异常方法进行跟踪时产生的,并由PrintStackTrace()方法输出到标准出错输出流,对于控制台程序来说,这个输出流就是屏幕。
Java程序在执行期间如果引发了一个Java系统能够识别的错误,就会产生一个与该错误相对应的异常类对象,这个过程称为抛出(throw)异常。
所有Java系统定义的执行异常都可以由系统自动抛出。
下面的例子用来测试在数组越界时出现的异常。
publicclassTestSystemException{
publicstaticvoidmain(Stringargs[]){
intnum[]=newint[3];
for(inti=0;
i<
4;
i++){
num[i]=i;
System.out.println("
num["
+i+"
]="
+i);
}
}
程序的运行结果如图5-2所示。
图5-2运行结果
上例所示的程序在执行期间,由于在进行第四次for循环时存在着数组越界的错误,所以将引发ArrayIndexOutOfBoundsException异常。
这个异常是Java系统已经定义好的一个异常类,所以Java系统遇到这个错误就自动终止程序的执行,并新建立一个ArrayIndexOutOfBoundsException类的对象,也就是抛出了一个数组越界异常。
5.2异常的处理
当程序在执行期间发生异常时,可以采取两种方法对异常进行处理。
一是由Java语言的异常处理机制来完成处理工作,但是采用这种处理方法时用户预先无法得知程序是发生了何种异常,用户无法对可能发生的异常做出适当的处理;
二是用户使用Java系统提供的try-catch-finally组合语句处理可能的异常。
这样一方面可以允许用户修正错误,另一方面可以避免因程序引起的异常而终止程序的执行。
异常的处理通常由捕捉异常、程序流程的转移和异常处理语句块的定义3个执行步骤组成。
5.2.1捕捉异常
当程序抛出一个异常时,程序中应该有专门的语句来接收这个被抛出的异常对象,这个过程被称为捕捉(catch)异常。
当一个异常类的对象被捕捉和接收后,用户程序的执行流程就会发生转移,Java系统中止当前的程序流程而转到专门用来处理异常的语句块,或者直接终止当前程序的执行而返回到操作系统状态。
在进行程序设计时,为了避免因程序引起的异常而终止程序的执行,通常将监视异常的程序代码放在try语句块中。
当程序代码出现异常时,这个try语句块就启动Java系统的异常处理机制来抛出一个异常对象,然后这个异常对象将被紧接在try语句块之后的catch语句块捕获。
当异常对象被抛出后,程序的执行流程将按非正常、非线性的方式执行。
如果此时在程序中没有匹配的catch语句块,那么程序将被终止而返回到操作系统状态。
为了避免发生这种情况,Java系统提供了finally语句块来解决这个问题。
具体做法是,将finally语句块放在try与catch语句块之后,也就是说,不管异常对象被抛出还是没有被抛出,都将执行finally语句块。
try-catch-finally组合语句的一般语法格式如下:
try{//可能发生异常的语句块}
catch(异常类名异常形式参数名){//处理异常的语句块}
finally{//无论是否发生异常都要被执行的语句块}
(1)catch语句括号内的异常类名指明了用户程序想要捕捉的异常类型。
当程序执行期间发生异常时,catch语句块会捕捉这个异常,并以语句块内的程序代码来处理异常。
(2)无论try语句块内的程序代码是否发生异常,finally语句块内的程序代码都会被执行。
需要说明的是,finally语句块是可以省略的,而try语句块必须与catch或finally语句块之一配对使用。
也就是说,try语句块单独使用是没有意义的。
//TestCatchException.java
publicclassTestCatchException{
publicstaticvoidmain(Stringargs[]){
intnum[]=newint[2];
try{
3;
i++){
catch(ArrayIndexOutOfBoundsExceptione){
数组下标越界引起的异常"
);
e.printStackTrace();
finally{
程序执行期间发生了异常!
!
"
程序的运行结果如图5-3所示。
图5-3运行结果
5.2.2异常的抛出
上小节介绍的是如何使用异常处理机制来捕捉系统已经定义好的异常类的对象,这些异常发生时会由Java系统自动地抛出。
同时,Java系统还允许用户在程序中自己定义异常类,并且用throw语句抛出,这就为Java程序的设计带来了更大的灵活性。
1.用户自定义的异常类
用户自定义的异常类一般通过继承Exception类的形式来实现。
下面是一个自定义异常类的程序段:
publicclassMyExceptionextendsException
{//类体}
对于自定义的异常类,Java系统是不会自动为用户抛出属于该类的对象的,用户必须在程序中使用关键字throw来自行抛出异常对象。
使用关键字throw的一般语法格式如下:
throw异常类对象;
这里的“异常类对象”既可以是用户自定义的异常类,也可以是Java系统已经定义好的异常类。
例如:
TestThrowException.java
classMyExceptionextendsException{//自定义的异常类
StringNewExceptionObject;
publicMyException(){//构造函数
this.NewExceptionObject="
;
publicMyException(Strings){//构造函数
this.NewExceptionObject=s;
StringShowExceptionInfo(){//返回接收到的异常信息
returnthis.NewExceptionObject;
}
publicclassTestThrowException{
thrownewMyException("
这是一个自行抛出的异常!
);
//抛出异常
catch(MyExceptione){
MyException类:
已经捕捉到抛出的异常对象!
异常信息:
+e.ShowExceptionInfo());
}
程序的运行结果如图5-4所示。
图5-4运行结果
说明:
(1)在程序中自定义了一个新的异常类MyException,该类自Exception类继承而得,并定义了两个构造函数和一个用来显示接收到的异常参数“这是一个自行抛出的异常!
”的方法ShowExceptionInfo()。
(2)在主类TestThrowException的定义中,try语句块使用关键字throw将属于MyException类的对象抛出。
由于抛出的异常对象类型与catch语句括号中的异常类MyException相匹配,所以catch语句块将捕捉这个异常对象,并将调用MyException类中的方法ShowExceptionInfo(),将接收到的异常信息显示在屏幕上。
(3)该程序省略了finally语句块。
2.指定方法抛出的异常
如果一个方法不能处理它自己所引发的异常,那么异常处理的工作就需要由调用者来完成。
在这种情况下,应该在方法的定义中使用关键字throws来指明该方法可能引发的所有异常,让调用者来处理这个异常。
包含throws子句的方法定义的一般语法格式如下:
[修饰符][返回值类型]方法名(形式参数列表)
throws异常类1,异常类2,…"
$"
{//方法体}
//TestThrowsException.java
classMyExceptionextendsException{
publicMyException(){
publicMyException(Strings){
StringShowExceptionInfo(){
publicclassTestThrowsException{
Test();
+e.ShowExceptionInfo());
staticvoidTest()throwsMyException{
方法自身产生的异常将由调用者处理!
程序的运行结果如图5-5所示。
图5-5运行结果
(1)第1~12句中有关MyException类的定义与前面的例子完全一样。
(2)在main()方法中,第16句调用Test()方法,而Test()方法是通过throw语句(第25句)产生一个MyException类的异常,但是Test()方法本身并不能处理所引发的异常。
(3)由于抛出的异常对象类型与第18句的catch语句括号中的异常类MyException相匹配,所以catch语句块将捕捉这个异常对象,并将调用MyException类中的方法ShowExceptionInfo(),将接收到的异常信息输出显示在屏幕上。
5.3多线程
5.3.1线程与多线程
1.线程
线程是存在于程序中的一个单独的顺序执行流程。
所有的编程人员都很熟悉编写顺序执行的程序,比如显示“HelloWorld!
”、对数据进行简单排序、求方程的根等。
这些程序有一个共同的特点:
每个程序只有一个起始点、一个执行序列和一个结尾,在程序运行的某一特定时刻只有一个执行点。
对于一个线程来说,它很类似于一个顺序执行的程序,即一个单独的线程也只有一个起始点、一个执行序列和一个结尾,在线程运行的某一特定时刻也只有一个执行点。
但是,一个线程只是一个程序的一部分,它本身并不能构成一个完整的程序,换言之,程序可以独立运行,也可以拥有多个相互独立的线程,而线程则不然,它不能独立运行,也不能独立存在,而必须“寄生”于一个程序之中。
只包含一个线程的程序就是我们所熟悉的顺序执行程序,这时线程这一概念并未给我们带来什么新意。
而Java使用线程的神奇之处在于它使得一个程序可以使用多个线程,这些线程同时运行,而每个线程则完成不同的功能。
多线程的一个最典型的应用就是HotJava网络浏览器。
在HotJava网络浏览器中,当正在下载一个小应用程序或一张图的时候,用户可以上下滚动页面,也可以使动画显示与声音同步等。
引入多线程后,Java程序可以同时完成多项工作。
有些人称线程为“轻量级的进程”。
一个线程和一个进程都是一个单独的顺序执行流程,线程被认为是“轻量级”的原因在于线程位于一个完整的程序上下文之中,它利用了程序分配的资源和程序的运行环境。
作为一个顺序执行流程,线程也必须拥有自己的运行资源。
它必须拥有自己的执行堆栈和程序计数器,线程的代码只能在该上下文中运行。
因此,有人也把线程称为“执行上下文”。
2.一个简单的多线程示例
下面是一个简单的线程示例。
这个程序包含了SimpleThread和TwoThreadsTest两个类。
其中,SimpleThread类是从java.1ang类组定义的类Thread派生而来的。
classSimpleThreadextendsThread{
publicSimpleThread(Stringstr){//构造过程,设置线程的名称
super(str);
//构造过程,通过引用父类的构造过程来实现
publicvoidrun(){//线程的核心过程
for(inti=0;
i<
10;
i++){
System.out.println(i+"
"
+getName());
try{//Math.random()产生(0,1)上的均匀分布随机数
sleep((int)(Math.random()*1000));
catch(InterruptedExceptione){}
System.out.println("
DONE!
+getName());
SimpleThread类的实现包含了两个过程。
它的构造过程通过调用其父类的构造过程实现,设置了线程的名称。
用户可以用Thread类的过程getName()获取这个名称。
过程run()是SimpleThread类的核心过程。
过程run()也是所有线程的核心过程,它定义线程的行为。
SimpleThread类的过程run()中包含一个次数为10次的循环,每次循环输出循环变量值和线程的名称,并随机休眠一段(1s以内)时间;
当循环结束时,过程run()显示“DONE!
”信息和线程名,这就是线程SimpleThread的所有工作。
TwoThreadsTest类提供了main()过程,创建并启动了两个SimpleThread线程:
“Threadl”和“Thread2”,其代码如下:
classTwoThreadsTest{
publicstaticvoidmain(Stringargs[]){
newSimpleThread("
Thread1"
).start();
//创建并启动线程Thread1
Thread2"
//创建并启动线程Thread2
这个程序每次的输出结果是不一样的,如图5-6所显示的是某一次输出的结果。
图5-6运行结果
从上述输出结果不难看出:
两个线程交错输出,这是因为两个SimpleThread线程是同时运行的,即它们的run()过程同时运行,因而一起显示各自的输出。
这里主要是向用户说明一个Java程序可以拥有多个线程,而这些线程是可以同时运行的。
而该示例中所使用的sleep(),start()等过程将在后面讨论。
5.3.2创建线程
在Java语言中,可以用两种方法创建线程。
本节中将分别进行介绍。
1.创建线程的方法之一——继承Thread类
java.1ang.Thread是Java语言中用来表示进程的类,其中所定义的许多方法为完成线程的处理工作提供了比较完整的功能。
如果将一个类定义为Thread的子类,那么这个类也就可以用来表示线程。
publicclassTwoThread{
DelayPrintThreadthread1,thread2;
thread1=newDelayPrintThread();
//创建两个线程对象
thread2=newDelayPrintThread();
thread1.start();
//开始执行两个线程
thread2.start();
try{Thread.sleep(10000);
//主线程休眠10000ms
catch(InterruptedExceptione){
threadhaswrong"
classDelayPrintThreadextendsThread{
privatestaticintthreadCount=0;
privateintthreadNumber=0;
privateintdelay;
publicDelayPrintThread(){
delay=(int)(Math.random()*5000);
//计算休眠时间
threadCount++;
//线程计数
threadNumber=threadCount;
//线程号
publicvoidrun(){
try{sleep(delay);
//子线程休眠一段时间
catch(InterruptedExceptione){}
System.out.println("
ThisisThread#"
+threadNumber+"
withadelayof"
+delay+"
."
运行结果如图5-7所示。
图5-7运行结果
TwoThread类的main()方法中通过实例化两个DelayPrintThread不同的对象来创建两个执行线程。
每个线程在它的start()方法被调用时开始执行。
因为每个DelayPrintThread表示一个独立的线程,所以它们同时执行。
程序的一次输出如屏幕图(图5-7)中所示。
TwoThread程序的输出在每次执行时都将不一样,这是因为两个线程在Java执行系统中的执行顺序的不确定和这两个线程中的每一个都将睡眠一个随机产生的时间所造成的。
2.创建线程的方法之二——实现Runnable接口
Runnable是Java语言中用以实现线程的接口,从根本上讲,任何实现线程功能的类都必须实现该接口。
前面所用到的Thread类实际上就是因为实现了Runnable接口,所以它的子类才相应具有线程功能。
Runnable接口中只定义了一个方法,即run()方法,也就是线程体。
Thread的第二种构造方法中包含有一个Runnable实例的参数,这就是说,必须定义一个实现Runnable接口的类并产生一个该类的实例,对该实例的引用就是适合于这个构造方法的参数。
classTwoThreadimplementsRunnable{//实现Runnable接口