1.6 抛出异常
异常处理学习到现在,所有的异常都是由系统抛出。作为程序员,有时需要手工抛出一个异常,让异常处理程序进行处理,接下来将学习如果使用throw关键字手工抛出异常。
1.6.1 手工抛出异常
在Java语言中,可以使用throw关键字手工抛出一个异常,手工抛出异常的语法形式为:
throw 异常对象;
例如,手工抛出一个算数异常的代码为:
throw new ArithmeticException();
观察下面的代码,通过throw new NullPointerException("the ");语句,手工抛出了一个空指针异常,指定信息为“the ”。catch语句块对空指针异常进行捕获,输出异常对象m. getMessage()的值(即为“the ”),程序运行结果如图1.17所示。
public class TestEx15 { public static void main(String[] args) { System.out.print("Now "); try{ System.out.print("is "); throw new NullPointerException("the"); //抛出一个空指针异常,指定信息为“the” //System.out.print("此句不会被执行!"); }catch(NullPointerException e){ //捕获抛出的空指针异常 System.out.print(e.getMessage()); } System.out.print("time. \n"); } }
图1.17 手工抛出异常
1.6.2 再次抛出异常
catch语句捕获到的异常对象e,在catch语句块的处理程序中,可以使用throw e;语句再次将这个异常抛出,以便它能被外部catch语句捕获。再次抛出异常最常见的情况是允许多个程序处理一个异常。例如,需要一个异常处理代码块处理异常的一个方面,另一个异常处理代码块处理另一个方面,则可以在一个异常处理代码块处理完毕之后再次抛出该异常,让另一个异常处理代码块继续进行处理。通过下面的代码,让我们熟悉如何在两个方法中处理同一个异常。
public class TestEx16{ public static void doEx1() { try{ String teachers[]={"柳海龙","孙传杰","孙悦"}; for(int i = 0;i < 4;i++){ System.out.println(teachers[i]); } }catch(ArrayIndexOutOfBoundsException e) //捕获数组下标越界异常 { System.out.println("doEx1方法中处理数组下标越界异常!"); throw e; //再次抛出该数组下标越界异常 } } public static void main(String[] args) { try{ doEx1(); }catch(ArrayIndexOutOfBoundsException e) //再次捕获数组下标越界异常 { System.out.println("main方法中处理数组下标越界异常!"); }finally{ System.out.println("程序结束!"); } } }
在该段程序中,doEx1()方法中的异常处理代码块捕获并处理了数组下标越界异常,在处理结束后,又将该异常抛出给调用doEx1()方法的main()方法进行异常处理,main()方法中的异常处理代码块又捕获处理了数组下标越界异常。程序运行结果如图1.18所示。
图1.18 再次抛出异常
数组下标越界异常属于运行时异常,所以main()方法在调用这个需要抛出数组下标越界异常的doEx1()方法时,即使不对这个抛出的异常进行捕获处理,也能编译通过,只是在运行时会抛出异常而已。去掉main()方法中的异常处理程序,只保留对doEx1()方法的调用,编译、运行程序,程序运行结果如图1.19所示。通过程序运行结果可以看出,在doEx1()方法中的捕获处理代码得到了运行,main()方法未捕获处理抛出的异常,所以运行时抛出异常。
图1.19 运行时异常抛出不捕获处理
1.6.3 抛出检查时异常
前面介绍检查时异常的时候提到过,检查时异常要求程序员必须在程序中对异常进行捕获处理,否则程序不能编译通过。还是刚才的程序结构,只是doEx1()方法里抛出的异常不再是运行时异常,而是检查时异常,会出现什么样的结果呢?程序代码如下:
import java.net.*; import java.io.*; public class TestEx17{ public static ServerSocket ss = null; public static void doEx1() { try { ss = new ServerSocket(5678); Socket socket = ss.accept(); }catch(IOException e){ //捕获IOException异常 System.out.println("doEx1方法中处理IOException异常!"); throw e; //再次抛出该IOException异常 } } public static void main(String[] args) { try{ doEx1(); }catch(IOException e) //再次捕获IOException异常 { System.out.println("main方法中处理IOException异常!"); }finally{ System.out.println("程序结束!"); } } }
编译程序,编译器报错,如图1.20所示。
图1.20 检查时异常抛出不捕获
仔细查看错误原因,提示为throw e;语句抛出一个检查时异常,必须要对其进行捕捉或声明以便抛出。该程序的目的就是再次抛出该异常,让调用doEx1()方法的main()方法再次进行异常捕获处理,所以如果在doEx1()方法里继续进行捕获则失去了该程序的定义。如何解决这个问题呢?接下来我们通过声明方法抛出异常的方式,处理这个问题。
1.6.4 声明方法抛出异常
所谓声明方法抛出异常,就是当方法本身不知道或者不愿意处理某个可能抛出的异常时,可以选择用throws关键字将该异常提交给调用该方法的方法进行处理。当这个异常是检查时异常时,该方法必须进行声明,调用该方法的方法必须进行处理或再次声明向外抛出。声明方法抛出异常很简单,需要在方法的参数列表之后,在方法体的大括号前,增加“throws异常列表”进行声明。
修改前面的代码,在doEx1()后增加throws IOException,声明doEx1()方法可能抛出IOException异常,因为在main()方法中已有对IOException异常的捕获和处理代码,所以程序可以编译通过。
让我们使用声明方法抛出异常的方式,处理“给孩子们分苹果”的程序中,孩子数输入不能转换为数字的字符时的问题,具体代码如下:
import java.util.*; //InputMismatchException在java.util包下 public class TestEx18{ //抛出InputMismatchException异常,自己不处理,让方法的直接调用者来处理 private static void p() throws InputMismatchException { int appleNum=0; //苹果数 int stuNum=0; //学生数 System.out.println("***现在给孩子们分苹果***"); Scanner input = new Scanner(System.in); System.out.print("请输入桌子上有几个苹果:"); appleNum = input.nextInt(); System.out.print("请输入班上有几个孩子:"); stuNum=input.nextInt(); //用户输入“abc”,则系统会抛出InputMismatchException异常 System.out.println("班上每个孩子分得多少苹果:" + appleNum/stuNum); System.out.println("孩子们非常开心!"); } public static void main(String args[]){ try{ p(); //方法的直接调用者捕获、处理异常 }catch(InputMismatchException e) { System.out.println("main方法处理InputMismatchException异常"); } } }
p()方法声明该方法可能抛出InputMismatchException异常,调用p()方法的main()方法处理了p()方法中不处理并声明抛出的这个异常。程序运行结果如图1.21所示。
图1.21 声明方法抛出异常