程序的错误分为:
编译错误:程序员编写程序时语法上出现的错误;
运行错误:程序员编写的程序在语法上没有错误,但是程序在运行时出现错误,本章就主要针对该类错误讲解 - - 即异常、异常类和异常处理机制。
主要缺陷:
程序复杂
可靠性差
返回信息有限
返回代码标准化困难
以往的程序开发过程中,常常采用返回值进行处理。例如,在编写一个方法,可以返回一个状态代码,调用者
根据状态代码判断出错与否。若状态代码表示一个错误,则调用该错误的处理程序进行相应的处理,或显示一
个错误页面或错误信息。
举例 以往的错误处理方法:采用返回值进行处理
举例:实现将一个文件从硬盘加载近来,导致加载可能失败的运行错误有硬盘错误、文件无法找到等
int status=loadTextfile();
If (status!=1){
//something unusual happened, describe it
switch(status) {
case 2:
//file not found
break;
case 3:
//disk error
default:
//other error}
}else {
//file loaded OK, continue with program}
Java异常处理方法:Java为运行错误引入了异常、异常类和异常处理机制。
异常特殊的运行错误,是在程序运行过程中发生的、会打断程序正常执行的错误
例如:
除0溢出
文件找不到
数组元素下标越界
异常类:Java用面向对象的方法处理异常,Java的异常类是处理运行时错误的特殊类,每一种异常类对应一种
特定的运行错误,每一个异常事件由一个异常类的对象来代表。
例如:除0溢出(ArithmeticException)
文件找不到(FileNotFoundException)
数组元素下标越界(ArrayIndexOutofBoundsException)
异常处理机制:抛出异常——捕捉异常:
-try,catch,throws,throw,finally
(1) try包含可能出现异常的语句块;
(2) 一个或多个catch块紧随try{}块,每个catch块通常处理指定类型的异常;
(3) finally引导块紧随catch块后,主要用于清理现场(可有可无)。
格式:
try
{ …
}catch( ExceptionName1 e )
{ …}
catch( ExceptionName2 e )
{ …}
finally
{ …}
注意:finally总是执行,catch块不一定执行
(1) Java的异常处理把错误集中起来统一处理。程序员只需要说明何处可能出现异常,如何处理即可;
(2) 采用面向对象的思想标准化了各种错误的类型;
(3) Java把程序运行过程中可能遇到的问题分为两类,一类是致命性的,即程序遇到了非常严重的不正常状
态,不能简单地恢复执行,这就是错误(对应Error类),如程序运行过程中内存耗尽。另一类是非致命性的,
通过某种处理后程序还能继续运行,这就是异常(对应Exception类)。
☕异常类:异常在Java中都是作为类的实例(对象)的形式出现的。
如 Throwable类, Exception类, Error类……
☕Java中异常分类:
(1) Error类及其子类:描述Java运行时刻系统内部的错误或资源枯竭导致的错误,无法恢复和抛出,发生几率小;
(2) Exception类及其子类:普通程序可以从中恢复,分为运行时异常和非运行时异常。
异常在Java中也是作为类的实例的形式出现的。Java中的所有的异常类都是从Throwable类派生出来的。Throwable类有两个直接子类:java.lang.Error 和 java.lang.Exception。
异常类的层次结构如下图所示。
(1) Error类及其子类主要用来描述一些Java运行时刻系统内部的错误或资源枯竭导致的错误。普通的程序不能从这类错误中恢复,也无法抛出这种类型的错误,这类错误出现的几率是很小的。
(2) 另一个异常类的子类是Exception类和它的子类。在编程中错误的处理主要是对这类错误的处理,如除数为零、数组下标越界等。类Exception是普通程序可以从中恢复的所有规范了的异常的父类。
☕Exception类子类有两种:
运行时异常和非运行时异常(一般异常) 区别???
(1)运行时异常:RuntimeException类及其所有子类。 运行时异常是程序员编写程序不正确所导致的异常,理论上,程序员经过检查和测试可以查出这类错误。如除数为零等,错误的强制类型转换、数组越界访问、空引用。
(2)非运行时异常(一般异常):指可以由编译器在编译时检测到的、可能会发生在方法执行过程中的异常,如找不到指定的文件等,这不是程序本身的错误,如果这些异常情况没有发生,程序本身仍然是完好的。
注意:编译器强制要求Java程序必须捕获或声明抛出所有非运行时异常,但对运行时异常不作要求。运行时异常编译可以通过但是运行时出现异常;非运行时异常编译的时候就通不过。
例如:格式不正确的URL、试图为一个不存在的类找到一个代表它的类的对象。除了runtimeexception及其子类以外,其他exception类的子类都是非运行时异常。
//运行时异常和非运行时异常区别举例
[例5-1] RuntimeExceptionDemo1.java
class RuntimeExceptionDemo1{
public static void main(String args[]){
int i=0;
System.out.println(2/0);
}}
该程序能编译通过,而在运行时,出现如下提示:
> javac RuntimeExceptionDemo1.java
> java RuntimeExceptionDemo1
Exception in thread "main" java.lang.ArithmeticException: / by zero at
RuntimeExceptionDemo1.main(RuntimeExceptionDemo1.java:4)
//这里的"ArithmeticException"
[例5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{
public static void main(String args[]){
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}
}
会出现如下的错误提示:
javac NonRuntimeExceptionDemo1.java
NonRuntimeExceptionDemo1.java:5: unreported exception java.io.FileNotFoundException; must
be caught or declared to be thrown
FileInputStream in=new FileInputStream("text.txt");
^
NonRuntimeExceptionDemo1.java:7: unreported exception java.io.IOException; mustbe
caught or declared to be thrown
while((s=in.read())!=-1) System.out.print(s);
^
NonRuntimeExceptionDemo1.java:8: unreported exception java.io.IOException; mustbe caught
or declared to be thrown
in.close();
☕对运行时异常的说明:由于运行时异常可能会出现在程序的任何地方,而且出现的可能性非常大,因而由程序本身去检测运行异常出现与否,将会使程序的开销过大,所以
运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置
建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程的希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误。
☕对运行时异常和非运行时异常的总结:
(1) 当出现java.lang.ArithmeticException运行时异常时,不需要用户在程序中对其进行处理,而直接由Java运行时系统进行处理;
(2) 对于非运行时异常,Java编译器对程序进行编译的时候,便指出用户需要①捕获该类异常或者②声明抛出。即对于非运行时异常,用户需要在程序中进行处理,否则编译时无法通过。
① 捕获该类异常
[例5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{
public static void main(String args[]){
try{
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}catch(Exception e){…}
}
}
② 声明抛出
[例5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{
public static void main(String args[])throws Exception{
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}
}
1. ☕编译器和异常:编译器强制要求程序员捕获或声明抛出非运行时异常
到底为什么要这么做呢???
对于运行时异常,编译器不强制要求,但用户也可以自己去捕获
这个时候会出现什么效果呢???
2. ☕运行时系统和异常:
(1) 异常都有抛出的轨迹;
(2) 对于所有异常,某个方法产生的异常 ,如果没有被捕获,就会自动抛给方法的调用者(但是对非运行时异常一定要声明抛出),如果调用者还没有捕获,再抛给调用者的调用者,以此类推,直到main方法里发现还没有捕获,那么运行时系统就会来处理这个异常,把异常信息和异常的轨迹信息打印给用户。
3. ☕两点建议:
(1) 建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程者希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误;
(2) 建议对于非运行时异常,用户应该去捕获,以交代程序员的处理该错误的代码,实在不行,就声明抛出给方法的调用者。
3. ☕异常类常用的方法:
public Exception()
public Exception(String s) :该参数一般表示该异常对应的错误的描述
public String toString() :返回描述当前异常对象信息的字符串
public String getMessage() :返回描述当前异常对象信息的详细信息。
public void printStackTrace() :打印当前异常对象使用堆栈的轨迹。
用户处理异常的三种方法:
(1)用户可以用try-catch-finally语句进行抛出和捕获处理;
(2)如果不想捕获和处理异常,可以通过throws语句声明要抛出的异常;
(3) 用户可以定义自己的异常类,并用throw语句来抛出。
运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置。即:运行时异常:会输出到设备显示哪里存在错误,请更正;非运行时异常:编译报错时提示必须添加非运行时异常处理,比如加try{}…catch(类名 对象){}或者使用throws来抛出异常。
try-catch-finally语句对程序运行进行监控,捕获和处理异常通常形式:
try{
调用可能产生异常的方法及其它java语句;
}
catch(异常类名1 异常对象名e){
异常处理语句块;
}
catch(异常类名2 异常对象名e){
异常处理语句块;
}
finally{
最终处理;
}
[例5-3]访问文本文件text.txt,并将其在屏幕上打印出来。
import java.io.*;
class TryCatchFinally
{
public static void main(String args[]){
try{
FileInputStream in=new FileInputStream("text.txt");
int s;
while((s=in.read())!=-1) System.out.print(s);
in.close();
}
catch(FileNotFoundException e){
System.out.println(“捕获异常:”+e); //e会调用toString()方法回显异常信息给程序员
}
catch(IOException e){
System.out.println("捕获异常:"+e);
}
finally{
System.out.println("finally块总是执行!");
} }
}
运行结果:
捕获异常:java.io.FileNotFoundException: text.txt (系统找不到指定的文件。)
finally块总是执行!
☕try{}:将可能抛出一个或者若干个异常的代码放入try语句块中。
注意:应当尽量减小try代码块的大小,不要将整个程序代码全部放入try语句块中,而是应当仔细分析代码,在可能出现异常情况的地方用try进行监控。
因为当发生异常时,程序控制由try块转到catch块,Java将跳过try中后面的语句,且永远不会从catch块返回到try块。因此若将整个程序代码都放在try中,若一开始发生异常,则后面的语句将永远不会被执行,从而影响了程序的实现。
(1) try语句后面必须跟有一个或多个catch语句来处理try中产生的异常事件。如果try语句中未产生异常,
那么catch语句将不执行。
(2)catch语句需要一个参数:一个异常类名和该异常类的对象。注意该异常类必须是Throwable类的子类.
(3) try块中发生了一个异常,try-catch语句就会自动在try块后面的各个catch块中,找出与该异常类相
匹配的参数。当参数符合以下3个条件之一时,就认为这个参数与产生的异常相匹配:
(1)参数与产生的异常属于一个类;
(2)参数是产生的异常的父类;
(3)参数是一个接口时,产生的异常实现了这一接口。
(4) 注意:
(1) 当产生的异常找到了第一个与之相匹配的参数时,就执行包含这一参数的catch语句中的Java代码,执
行完catch语句后,程序恢复执行,但不会回到异常发生处继续执行,而是执行try-catch结构后面的代码。
(2) 可以用一个catch块来处理多个异常类型,此时catch的参数应该是这多个异常的父类。
(3) 有多个catch块时,要细心安排catch块的顺序。 将子类的catch块放在前面,父类的catch块放在后面。
1.finally语句: 无论在try块中是否产生异常,也不管产生的异常是否会被捕获,finally中的语句最终都会
被执行。
2.作用: 为异常处理事件提供一个清理机制,例如清理打开文件、Socket、JDBC连接之类的资源。
如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。
finally语句可以说是为异常处理事件提供的一个清理机制. 一般是用来关闭文件或释放其他的系统资源,作
为try-catch-finally结构的一部分,可以没有finally语句,如果存在finally语句,不论try块中是否发生
了异常,是否执行过catch语句,都要执行finally语句。
3. 带有finally子句的try-catch-finally语句的形式如下:
try { … } //…是正常执行的代码, 可能产生异常
catch (异常类1 e) { … } //…是异常类1的处理代码
catch (异常类2 e) { … } //…是异常类1的处理代码
……
catch (异常类n e) { … } //…是异常类2的处理代码
finally { … } //…是执行清除工作的语句
4.执行过程:
(1) try块中的语句没有产生异常。在这种情况下,Java首先执行try块中的所有的语句,然后执行finally子句
中的代码,最后执行try…catch…finally块后面的语句;
(2) try块中的语句产生了异常,而且此异常在方法内被捕获(有catch匹配)。 在这种情况下,Java首先执行try
块中的语句,直到产生异常处,然后跳过此try块中剩下的语句,执行捕获此异常的catch子句的处理代码;
然后执行finally子句中的代码;
(3) 如果在catch子句又重新抛出了异常。也会执行finally,然后将这个异常抛出给方法的调用者;
(4) try块中产生了异常,而此异常在方法内没有被捕获(没有catch匹配) 。在这种情况下,Java将执行try块
中的代码直到产生异常,然后跳过try块中的代码而转去执行finally子句中的代码,最后将异常抛出给方法
的调用者。
throws说明:
在设计可能会抛出异常的方法时,可以有两个选择:
(1) 使用try-catch-finally处理方法中的异常;
(2) 声明抛出异常:不捕获异常(没有throws方法的创建异常类的实例和抛出异常。是个空语句。),而是将异常交由上一层处理,在其他地方捕获异常。如果使用后者,那么应该(在某些情况下)向编译器表明:此方法可能会抛出异常,但方法本身不会捕获它。可以在方法头中用throws子句来实现此功能。
(2.1)带throws异常说明的方法说明形式如下:
… 方法名(…) [throws 异常类列表]
{ 方法体 }
注意:
(1)方法抛出的异常类是throws子句中指定的异常类或其子类。
(2)并不是所有可能发生的异常都要在方法的说明中指定,从Error类中派生出的异常和从RuntimeException类中派生的异常就不用在方法声明中指定。
在下列情况下Java方法可以声明抛出异常:
(1)调用的方法抛出了异常;
(2)检测到了错误并使用throw语句抛出异常;
例1:调用的方法抛出了异常
class Test {……
public String getInput() throws IOException
{……
System.in.read();
}
}
例2:检测到了错误并使用throw语句抛出异常
import java.io.*;
class Test {……
public String getInput() throws IOException
{……
IOException ae =new IOException("buffer is full");
throw ae;
}
例3: throws多个异常
class Animation
{
public Image loadImage(String s) throws
EOFException, MalformURLException
{
……
}
}
throw说明:
要使用throw,则必须在方法名后面指出throws;但是使用throws,抛出可以是空语句不用写,交给上一层处理!!
在捕获一个异常前,必须有一段Java代码来生成和抛出一个异常对象。Java用throw语句抛出异常。throw语句的格式如下:
throw ThrowableObject;
异常对象的生成和抛出可以有以下三种情况:
(1)Java运行时系统
(2)JDK中某个类
(3)在程序中创建异常对象抛出
使用throw语句应注意:
(1)一般这种抛出异常的语句应该在满足一定条件执行,例如把throw语句if分支中
(2)含有throw语句的方法,应该在方法头定义中用throws语句声明所有可能抛出的异常
抛出异常有这样三步:
(1)确定异常类;
(2)创建异常类的实例;
(3)抛出异常。
举例
static String getInput() throws IOException{
char[] buffer =new char[20];
int counter = 0;
boolean flag =true;
while(flag) {
buffer[counter] =(char)System.in.read();
if(buffer[counter]=='\n') flag = false;
counter++;
if(counter>=20){
} IOException ae =new IOException("buffer is full");
throw ae; }
return new String(buffer);
}
"//IOException ae =new IOException(""buffer is full""); throw ae;"使用throw
由于异常使用起来非常方便,以至于在很多情况下可能会滥用异常。但是,使用异常处理会降低程序运行的速度,几点建议:
(1) 在可以使用简单的测试就能完成的检查中,不要使用异常来代替它。例如:
if (ins!=null) //使用ins引用对象
{ … }
(2) 不要过细地使用异常。最好不要到处使用异常,更不要在循环体内使用异常处理, 可以将它包裹在循环
体外面。
(3)不要捕获了一个异常而又不对它做任何的处理。
try
{
…… //正常执行的代码
}
catch(Exception e) { }
(4) 将异常保留给方法的调用者并非不好的做法。
对于有些异常,将其交给方法的调用者去处理是一种更好的处理办法。没有类似这样if(counter>=20){
IOException ae =new IOException(“buffer is full”);
throw ae; }的语句,是个空语句。