Chapter 9 异常
异常:指程序在执行过程中,出现的非正常的情况,最终导致JVM非正常停止。
在Java等面向对象的编程语言中,异常是一个类,所有异常都是发生在运行阶段的(因为也只有程序运行阶段方可new
对象),产生异常其实就是创建异常对象。而Java处理异常的方式为中断处理。
9.1 异常体系
如下的UML图关于异常的继承结构,异常的根类为java.lang.Throwable
(顾名思义,所有异常均可抛出),其下有两个子类:java.lang.Error
与 java.lang.Exception
。而我们平常所说的异常指的是后者。
- 错误(Error):Java程序运行过程中若发生错误,则无法恢复,只能够退出。
- 编译时异常(CheckException,或称受控异常、检查异常):出现了这种类型的异常必须显式地处理,若不处理则Java程序将无法编译通过。
- 运行时异常(RuntimeException,或称UncheckedException,未检查异常,非受控异常):此种异常发生几率低,可以不用显式地处理(eg: 算术异常),也能够编译通过。
异常信息的获取:
Throwable
类中定义了一些查看方法:
-
public String getMessage()
:获取异常的描述信息与原因,多用于向用户提示错误的原因。 -
public void printStackTrace()
:打印异常的跟踪栈信息并输出至控制台。(开发与调试阶段常用)
9.2 异常处理机制
Java异常的作用是增强程序健壮性。
9.2.1 声明异常throws
Java的一个方法不仅需要告诉编译器将要返回什么值,还需要告诉编译器有可能发生什么错误。
一个方法必须声明所有可能抛出的已检查异常(CheckedException),而未检查异常要么是不可控制的Error,要么是应该避免发生的未检查异常(RuntimeException)。
故,方法应在其首部用throws
声明可能发生的异常(但不一定会发生),其格式为:
修饰符 返回值类型 方法名(参数) throws 异常类名1, 异常类名2 ...{ }
-
例如:
public FileInputStream(String name) throws FileNotFoundException
这个声明表示该构造器由
String
参数产生一个FileInputStream
对象,但也有可能抛出一个FileNotFoundException
类对象。若该方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException
对象。
在Java中,没有throws
说明符的方法将不能抛出任何已检查异常,它是可以单独使用的。一旦对该方法进行throws
声明,必须交由调用该方法的上一级方法的语句来处理(捕获 或 继续声明),若调用者不进行处理,编译一般不能通过!
运行时异常被抛出可以不处理,即允许不捕获也不声明抛出。
注意:Java中异常发生后若一直往上抛,最终抛给
main
方法,main
方法继续向上抛,抛给调用者JVM,JVM知道这个异常发生后会终止Java程序运行。
9.2.2 抛出异常throw
在Java中,提供throw
关键字,用在方法内,抛出一个异常对象,将该异常对象传递到调用者处,并直接结束当前方法的执行。
格式为throw new 异常类名(参数);
或者 异常类 异常对象名 = new 异常类名(参数); throw 异常对象名
。
String readData(Scanner in) throws EOFException{ //声明可能出现的异常
//...
while(...){
if(!in.hasNext()){
...
if(n < len) throw new EOFException(); //创建异常对象,并将其抛出
}
}
return s;
}
抛出异常的4种情况:
- 调用一个抛出已检查异常的方法,例如,
FileInputStream
构造器。 - 程序运行过程中发现错误,并且利用
throw
语句抛出一个已检查异常。 - 程序出现错误。例如,
a[-1] = 0;
。 - Java虚拟机和运行时库出现的内部异常。
一般而言,
throw
语句不能单独使用,它应与throws
声明配套使用。
我们一般是使用一次捕获 多次处理方式,格式如下:
try{
//编写可能会出现异常的代码
} catch(异常类型A e){
//处理A类型异常
} catch(异常类型B e){
//处理B类型异常
}
注意,这种异常处理方式,要求多个catch
中异常不能相同,且catch
中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch
处理,父类异常在下面的catch
处理。
9.2.3 捕获异常try-catch
捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行制定方式的处理。要想捕获一个异常,必须设置try...catch
语句块。语法格式如下:
try{
//编写可能产生异常的代码。(一般调用方法,该方法声明了异常并能够对异常抛出)
} catch(异常类型 异常名){
//处理异常的代码:记录日志/打印异常信息/继续抛出异常
}
如果在
try
语句块中任何代码抛出一个在catch
子句中说明的异常类,那么:
- 程序将跳过
try
语句块的其余代码;- 程序将执行
catch
子句中的处理器代码。
举例如下:
public class JustTest {
public static void main(String[] args) {
try{
read(b.txt);
} catch(FileNotFoundException e){
System.out.println(e); //try中抛出的是什么异常,在括号中就定义什么异常类型
}
System.out.println("Over!");;
}
public static void read(String path) throws FileNotFoundException{
if(!path.equals("a.txt")){
throw new FileNotFoundException("文件不存在");
}
}
}
9.2.4 finally 代码块
当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。当我们在try
语句块中打开一些物理资源(磁盘文件/网络连接/数据库连接等),我们需要在使用完之后关闭已打开的资源。注意,finally
不能单独使用。
public class JustTest {
public static void main(String[] args) {
try{
read(b.txt);
} catch(FileNotFoundException e){
System.out.println(e); //try中抛出的是什么异常,在括号中就定义什么异常类型
} finally {
System.out.println("不管程序如何,我会被执行的~");
}
System.out.println("Over!");;
}
public static void read(String path) throws FileNotFoundException{
if(!path.equals("a.txt")){
throw new FileNotFoundException("文件不存在");
}
}
}
finally
子句中的代码是一定会执行的!如果try
语块有返回值,finally
子句也会执行完再最后返回(除非使用System.exit(0);
则会中断finally
子句的执行)
public class JustTest {
public static void main(String[] args) {
int f = Judge();
System.out.println(f);
}
public static int Judge() {
try{
System.out.println("11111");
return 0;
} finally {
System.out.println("233333");
}
}
}
finally
语句块设计的目的仅是为了让方法执行一些重要的收尾工作,而不是用来计算返回值的,故在finally
语句块中修改返回值是无效的!同时不建议在finally
语句块中返回一些值。
关于final
、finally
、finalize
的区别
final
属于关键字,其修饰的类无法继承、修饰的方法无法覆盖、修饰的变量不能重新赋值。finally
属于关键字,与try...catch
联合使用,finally
语句块中的代码是必须执行的。finalize
标识符,是一个Object
类中的方法名,该方法是由垃圾回收器GC负责调用。
9.3 自定义异常
在实际开发中总是有些异常情况是SUN没有定义的,根据自己业务的异常情况来定义异常类。
自定义异常类的方式:
- 自定义一个编译时异常类:自定义类,并继承于
java.lang.Exception
- 自定义一个运行时期的异常类:自定义类,并继承于
java.lang.RuntimeException
习惯上,定义的类应包含两个构造器,一个是默认无参构造器,另一个是带有详细描述信息的构造器。
public class RegisterException extends Exception{
public RegisterException(){ //无参构造
}
public RegisterException(String message){
super(message); //超类Throwable的toString方法将会打印出这些详细信息,调试中有用。
}
}