定义
尽管人人希望自己身体健康,处理的事情都能顺利进行,但在实际生活中总会遇到各种状况,比如感冒发烧,工作时电脑蓝屏、死机等。同样,在程序运行的过程中,也会发生各种非正常状况,例如,程序运行时磁盘空间不足、网络连接中断、被装载的类不存在等。
针对这种情况, Java语言引入了异常,以异常类的形式对这些非正常情况进行封装,通过异常处理机制对程序运行时发生的各种问题进行处理。
异常发生后,程序会立即结束,无法继续向下执行
Java提供了大量的异常类,这些类都继承自java.lang.Throwable类。
分类
Throwable有两个直接子类Error和Exception,其中,Error代表程序中产生的错误,Exception代表程序中产生的异常。
● Error类称为错误类,它表示Java程序运行时产生的系统内部错误或资源耗尽的错误,这类错误比较严重,仅靠修改程序本身是不能恢复执行的。举一个生活中的例子,在盖楼的过程中因偷工减料,导致大楼坍塌,这就相当于一个Error。例如,使用java命令去运行一个不存在的类就会出现Error错误。
● Exception类称为异常类,它表示程序本身可以处理的错误,在Java程序中进行的异常处理,都是针对Exception类及其子类的。在Exception类的众多子类中有一个特殊的子类—RuntimeException类,RuntimeException类及其子类用于表示运行时异常。 Exception类的其他子类都用于表示编译时异常。
常用方法
Throwable类中的常用方法如下表。
try-catch-finally
出现异常后,程序会立即终止。为了解决异常,Java提供了对异常进行处理的方式一一异常捕获。异常捕获使用try…catch语句实现,try…catch具体语法格式如下:
try{
//程序代码块
}catch(ExceptionType(Exception类及其子类) e){
//对ExceptionType的处理
}
上述语法格式中,在try代码块中编写可能发生异常的Java语句,catch代码块中编写针对异常进行处理的代码。
当try代码块中的程序发生了异常,系统会将异常的信息封装成一个异常对象,并将这个对象传递给catch代码块进行处理。
catch代码块需要一个参数指明它所能够接收的异常类型,这个参数的类型必须是Exception类或其子类。
示例:
1 public class Example25 {
2 public static void main(String[] args) {
3 //下面的代码定义了一个try…catch语句用于捕获异常
4 try {
5 int result = divide(4, 0); //调用divide()方法
6 System.out.println(result);
7 } catch (Exception e) { //对异常进行处理
8 System.out.println("捕获的异常信息为:" + e.getMessage());
9 }
10 System.out.println("程序继续向下执行...");
11 }
12 //下面的方法实现了两个整数相除
13 public static int divide(int x, int y) {
14 int result = x / y; //定义一个变量result记录两个数相除的结果
15 return result; //将结果返回
16 }
17 }
上述代码中,第4~9行代码是对可能发生异常的代码用try…catch语句进行了处理。在try代码块中发生除0异常时,程序会通过catch语句捕获异常,第8行代码在catch语句中通过调用Exception对象的getMessage()方法,返回异常信息“/ by zero”。catch代码块对异常处理完毕后,程序仍会向下执行,而不会终止程序。
需要注意的是,在try代码块中,发生异常语句后面的代码是不会被执行的,如上述代码中第6行代码的打印语句就没有执行。
在程序中,有时候会希望有些语句无论程序是否发生异常都要执行,这时就可以在try…catch语句后,加一个finally代码块。
1 public class Example26 {
2 public static void main(String[] args) {
3 //下面的代码定义了一个try…catch…finally语句用于捕获异常
4 try {
5 int result = divide(4, 0); //调用divide()方法
6 System.out.println(result);
7 } catch (Exception e) { //对捕获到的异常进行处理
8 System.out.println("捕获的异常信息为:" + e.getMessage());
9 return; //用于结束当前语句
10 } finally {
11 System.out.println("进入finally代码块");
12 }
13 System.out.println("程序继续向下执行…");
14 }
15 //下面的方法实现了两个整数相除
16 public static int divide(int x, int y) {
17 int result = x / y; //定义一个变量result记录两个数相除的结果
18 return result; //将结果返回
19 }
20 }
上述代码中,第9行代码是在catch代码块中增加了一个return语句,用于结束当前方法,程序第13行代码就不会执行了,而finally代码块中的代码仍会执行,不受return语句影响。也就是说不论程序是发生异常还是使用return语句结束,finally中的语句都会执行。因此,在程序设计时,通常会使用finally代码块处理完成必须做的事情,如释放系统资源。
注意 finally中的代码块在一种情况下是不会执行的,那就是在try…catch中执行了System.exit(0)语句。System.exit(0)表示退出当前的Java虚拟机,Java虚拟机停止了,任何代码都不能再执行了。
throws关键字
在实际开发中,大部分情况下我们会调用别人编写方法,并不知道别人编写的方法是否会发生异常。针对这种情况,Java允许在方法的后面使用throws关键字对外声明该方法有可能发生的异常,这样调用者在调用方法时,就明确地知道该方法有异常,并且必须在程序中对异常进行处理,否则编译无法通过。
throws关键字声明抛出异常的语法格式如下:
修饰符 返回值类型 方法名(参数1,参数2.....)throws 异常类1, 异常类2.....{
//方法体.....
}
从上述语法格式中可以看出,throws关键字需要写在方法声明的后面,throws后面需要声明方法中发生异常的类型。
示例:
1 public class Example27 {
2 public static void main(String[] args) {
3 int result = divide(4, 2); //调用divide()方法
4 System.out.println(result);
5 }
6 //下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
7 public static int divide(int x, int y) throws Exception {
8 int result = x / y; //定义一个变量result记录两个数相除的结果
9 return result; //将结果返回
10 }
11 }
上述代码报错:
在上述代码中,第3行代码调用divide()方法时传入的第二个参数为2,程序在运行时不会发生被0除的异常,但是由于定义divide()方法时声明了抛出异常,调用者在调用divide()方法时就必须进行处理,否则就会发生编译错误。
1 public class Example28 {
2 public static void main(String[] args) {
3 //下面的代码定义了一个try…catch语句用于捕获异常
4 try {
5 int result = divide(4, 2); //调用divide()方法
7 System.out.println(result);
8 } catch (Exception e) { //对捕获到的异常进行处理
9 e.printStackTrace(); //打印捕获的异常信息
10 }
11 }
12 //下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
13 public static int divide(int x, int y) throws Exception {
14 int result = x / y; //定义一个变量result记录两个数相除的结果
15 return result; //将结果返回
16 }
17 }
上述代码由于使用了try…catch对divide()方法进行了异常处理,因此程序可以编译通过,运行后正确的打印出了运行结果2。程序运行结果如图。
由于使用了try…catch对divide()方法进行了异常处理,因此程序可以编译通过,运行后正确的打印出了运行结果2。下面修改上述程序,将divide()方法抛出的异常继续抛出。
1 public class Example29 {
2 public static void main(String[] args)throws Exception {
3 int result = divide(4, 0); // 调用divide()方法
4 System.out.println(result);
5 }
6 // 下面的方法实现了两个整数相除,并使用throws关键字声明抛出异常
7 public static int divide(int x, int y) throws Exception {
8 int result = x / y; // 定义一个变量result记录两个数相除的结果
9 return result; // 将结果返回
10 }
11 }
上述代码,在main()方法继续使用throws关键字将Exception抛出,程序虽然可以通过编译,但从运行结果可以看出,在运行时期由于没有对“/by zero”的异常进行处理,最终导致程序终止运行。
编译时异常和运行时异常
在实际开发中,经常会在程序编译时产生一些异常,这些异常必须要进行处理,这种异常被称为编译时异常,也称为checked异常。另外还有一种异常是在程序运行时产生的,这种异常即使不编写异常处理代码,依然可以通过编译,因此被称为运行时异常,也称为unchecked异常。
编译时异常
在Exception类中,除了RuntimeException类及其子类,Exception的其他子类都是编译时异常。编译时异常的特点是Java编译器会对异常进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。
处理编译时期的异常有两种方式,具体如下:
(1)使用try…catch语句对异常进行捕获处理。
(2)使用throws关键字声明抛出异常,调用者对异常进行处理。
运行时异常
RuntimeException类及其子类都是运行时异常。运行时异常的特点是Java编译器不会对异常进行检查。也就是说,当程序中出现这类异常时,即使没有使用try…catch语句捕获或使用throws关键字声明抛出,程序也能编译通过。运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。
自定义异常
JDK中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的异常情况,例如,前面讲解的程序中的divide()方法,不允许被除数为负数。为了解决这个问题,Java允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。
// 下面的代码是自定义一个异常类继承自Exception
public class DivideByMinusException extends Exception{
public DivideByMinusException (){
super(); // 调用Exception无参的构造方法
}
public DivideByMinusException (String message){
super(message); // 调用Exception有参的构造方法
}
}
在实际开发中,如果没有特殊的要求,自定义的异常类只需继承Exception类,在构造方法中使用super()语句调用Exception的构造方法即可。
自定义异常类中使用throw关键字在方法中声明异常的实例对象,格式如下:
throw Exception异常对象
示例:
1 public class Example30 {
2 public static void main(String[] args) {
3 int result = divide(4, -2);
4 System.out.println(result);
5 }
6 //下面的方法实现了两个整数相除
7 public static int divide(int x, int y) {
8 if(y<0){
9 throw new DivideByMinusException("除数是负数"); }
10 int result = x / y; // 定义一个变量result记录两个数相除的结果
11 return result; // 将结果返回
12 }
13 }
从运行结果可以看出,程序在编译时就发生了异常。因为在一个方法内使用throw关键字抛出异常对象时,需要使用try…catch语句对抛出的异常进行处理,或者在divide()方法上使用throws关键字声明抛出异常,由该方法的调用者负责处理。但是程序没有这样做。
为了解决上面的问题,对程序进行修改,在divide()方法上,使用throws关键字声明抛出DivideByMinusException异常,并在调用divide()方法时使用try…catch语句对异常进行处理。
1 public class Example31 {
2 public static void main(String[] args) {
3 // 下面的代码定义了一个try…catch语句用于捕获异常
4 try {
5 int result = divide(4, -2);
6 System.out.println(result);
7 } catch (DivideByMinusException e) { // 对捕获到的异常进行处理
8 System.out.println(e.getMessage()); // 打印捕获的异常信息
9 }
10 }
11 // 下面的方法实现了两个整数相除,并使用throws关键字声明抛出自定义异常
12 public static int divide(int x, int y) throws DivideByMinusException {
13 if (y < 0) {
14 throw new DivideByMinusException("除数是负数");
15 }
16 int result = x / y; // 定义一个变量result记录两个数相除的结果
17 return result; // 将结果返回
18 }
19 }
上述代码中的main()方法中,第4~9行代码使用try…catch语句捕获处理divide()方法抛出的异常。在调用divide()方法时,如果传入的被除数为负数,程序会抛出一个自定义的DivideByMinusException异常,该异常最终被catch代码块捕获处理,并打印出异常信息。