Java异常处理机制(预备知识)
异常是在程序运行过程中发生的异常事件,比如除 0 溢出、数组越界、找不到文件等,这些事件的发生会导致程序不能正常运行。因此,为了加强程序的健壮性,在进行程序设计时,必须考虑可能发生的异常事件并要做出相应的处理。
异常是在程序运行过程中发生的异常事件,比如除 0 溢出、数组越界、找不到文件等,这些事件的发生会导致程序不能正常运行。因此,为了加强程序的健壮性,在进行程序设计时,必须考虑可能发生的异常事件并要做出相应的处理。
1.掌握Java的异常处理机制。实验目的
2.掌握throw, throws子句及用法。
实验流程
1.简介。
Java 通过面向对象的方法来处理异常。假如在一个方法的运行过程中发生了异常,则这个方法生成代表该异常的一个对象,并把它交给运行时系统,运行时系统会寻找相应的代码来处理发生的异常。我们把生成异常对象并把它交给运行时系统这一过程称作抛出(throw) 一个异常。运行时系统在方法的调用技中查找,从生成异常的方法开始进行搜索,直到找到 包含相应异常处理的方法为止,这一个过程称为捕获(catch)一个异常。
2.异常处理对象。
在 Java 的异常处理体系结构中,它将每个异常看作是一个对象,当程序发生异常时,会抛出这个异常的对象,而这个对象包含发生异常的信息。在java.lang包中Throwable类提供了处理错误和异常的类,包括两个直接的子类。
(1) Error 类
对于程序中出现的错误error,若出现在编译期间,此类错误属于语法错误,并不会产生错误类的对象,这类错误可通过修改代码使得程序正确编译。对于运行期间出现的错误,Java虚拟机将生成Error类的对象,并将此对象抛给运行时系统,由系统处理此类的错误。如动态链接错误,内存溢出错误等。
(2) Exception 类
程序在运行时发生的异常,可以由程序本身抛出并处理。当发生异常时,程序抛出发生异常类的对象,而且提供相应的处理机制,能捕获此类异常对象,并作出相应处理,使得程序得以继续运行。如除数为0(ArithmeticException),输入/输出异常(IOException),数组下标越界异常(ArrayIndexOutOfBounds Exception)等。
随着 Java 语言的不断发展,Java中异常类的数目也在持续增加,异常并不全在 java.lang包里定义,例如FontFormatException 就是在 java.awt包中定义的异常。还有很多异常是用来支持像util、net和io这样的程序包,并且我们使用的第三方类库也可能会有自己的异常类。
3.异常处理机制。
Java语言提供两种异常处理机制:捕获异常和声明抛弃异常;
(1) 捕获异常
在Java程序运行过程中系统得到一个异常对象是,它将会沿着方法的调用栈逐层回溯,寻找处理这一异常的代码。找到能够处理这种
类型异常的方法后,运行时系统把当前异常交给这个方法处理;如果找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出。捕获异常是通过
try-catch-finally语句实现的。语法为:
try{ ... }catch(ExceptionName1 e){ ... }catch(ExceptionName2 e){ ... } finally{ ... } |
(2) 声明抛弃异常
当Java程序运行时系统得到一个异常对象时,如果一个方法并不知道如何处理所出现的异常,则可在方法声明时,声明抛弃异常。声明抛弃异常是在一个方法声明中的
throws子句中指明的。如:
public int read() throws IOException{ ... } |
4.try…catch语句。
try…catch语句中的catch分支可以有一个或多个,即可以捕获一个或多个异常,但是至少要有一个catch 语句或是finally 语句。
(1) try 语句
try 语句用大括号{}指定一段代码,该段代码中可能会抛出一个或多个异常,同时,该段代码也指定了它后面catch语句所捕获异常的范围。
(2) catch 语句
catch 语句的参数类似于方法的声明,包括一个异常类型和一个异常对象。其中,异常类型必须是 Throwable 的一个子类。参数异常类型指明了catch 语句中要处理的异常类型, 异常对象则由运行时系统在 try 所指定的代码块中生成并被捕获,catch 大括号中包含对对象的处理,其中可以调用对象的方法。
catch 语句可以有多个,用来分别处理不同类型的异常。Java 运行时系统从上向下分别对每个catch语句处理的异常类型进行检测,直到找到与类型相匹自的catch语句为止。其中,类型相匹配是指 catch 所处理的异常类型需与生成异常对象的类型完全一致或者是异常对象所属类的父类。也可用一个 catch 语句处理多种异常类型,此时异常类型参数应该是多个异常类型的父类,比如直接捕获Exception类,程序设计中要根据具体情况来选择catch语句的异常处理类型。
另外需要注意的是 在使用多条 catch 语句时,异常子类必须在它的任何父类,这是因为使用父类的一条 catch 语句将捕获该类型的对象以及它的任何子异常类的对象。因此,假如一个子类在它的父类之后,那么它将永远不会到达,因此就会产生错误。
// 此处有三条catch子句,一旦程序运行到异常处,将不会继续往下运行 public void exceptionTest() { try { // 除数不能为0,会抛出ArithmeticException异常类 float result = 100/0; // 当字符串为null时,无法求其长度,会抛出NullPointerException异常 String name = null; System.out.println(name.length()); // 试图转换一个字符串为整数,但是该字符串并不包含正常的数字 // 但是catch子句并没有捕获到该异常,所以将会走最后一个catch子句 int value = Integer.parseInt("123Hi"); } // 如果ArithmeticException出现,后面的程序将不再运行 catch (ArithmeticException e) { System.out.println("除数不能为0"); } catch (NullPointerException e) { System.out.println("字符串不能为空"); } // 当前面异常子类处理后,有可能还有一些异常并不是既定异常类,将走该分支 // 在JDK中,java.lang包下的标准异常都会直接或间接继承自Exception父类 // 该异常父类通常必须放在最后一个catch子句,否则后续catch子句将失效 catch (Exception e) { System.out.println("程序出现异常:" + e.getMessage()); } } |
5. finally子句。
finally关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。在以下三种情况下会进入finally块:
(1) try块中的代码正常执行完毕。
(2) 在try块中抛出异常。
(3) 在try块中执行return、break、continue。
因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些代码放入finally块中。当你的程序中使用了外界资源,如数据库连接,文件等,必须将释放这些资源的代码写入finally块中。必须注意的是,在finally块中不能抛出异常。Java异常处理机制保证无论在任何情况下必须先执行finally块然后在离开try块,因此在try块中发生异常的时候,Java虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再向外抛出异常。如果在finally块中抛出异常,try块捕捉的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try块中发生的真正的异常堆栈信息则丢失了。
6.throws子句。
在定义一个方法时可以使用throws关键字声明,使用throws声明的方法表示此方法不处理异常,而交给方法的调用处进行处理,throws使用格式如下:
public void methodName(参数列表) throws 异常类{ } |
例如:
package com.woniuxy.java.basic; public class ExceptionDemo { public static void main(String[] args) { ExceptionDemo ed = new ExceptionDemo(); try { ed.divide(100, 0); } catch (Exception e) { System.out.println(e.getMessage()); } } // 方法内可以不处理异常,直接抛给上一层调用该方法时处理 public void divide(int x, int y) throws Exception { float result = x / y; System.out.println(result); } } |
因为除法操作有可能出现异常,也有可能没有异常,所以在上面代码的div()方法中使用了throws关键字,表示不管是否会有异常,在调用此方法处都必须进行异常处理,调用代码如下:
在以上代码中,不管是否有问题,都要使用try…catch块进行异常的捕获与处理,既然throws是在方法处定义的,那么在方法中也可以使用throws关键字,但是主方法为程序的起点,所以此时主方法再向上抛异常,则只能将异常抛给JVM处理。这种感觉有点像推卸责任一样。但是请注意:尽量不要让主方法中使用throws子句,因为主方法为一切的起点,一切的操作也是由主方法开始的。所以,如果在主方法中使用throws,则程序出现问题后肯定交给JVM处理,将导致程序中断。
7.throw子句。
在通常情况下,程序发生错误时系统会自动抛出异常,而有时希望程序自行抛出异常,可以使用 throw 语句来实现。throw语句通常用在方法中,在程序中自行抛出异常,使用throw 语句抛出的是异常类的实例,通常与 if 语句一起使用。throw 语句的语法格式如下:
throw new Exception("对异常的说明"); |
throw 是抛出异常的关键字,Exception 是异常类(通常使用自定义异常类)。
例:在项目中创建 Example 类,使用该类计算圆的面积。设定园的半径不能小于 20,如果半径小于 20,则使用 throw 语句抛出异常,并给出提示信息。
package com.woniuxy.java.basic; public class ExceptionDemo { // 定义一个PI常量 private final static float PI = 3.1415926f; // 计算圆的面积,如果半径小于20,则抛出一个异常 public void calc(int r) throws Exception { if (r < 20) { throw new Exception("圆的半径不能小于20."); } else { double area = PI * r * r; System.out.println("圆的面积为:" + area); } } public static void main(String[] args) { ExceptionDemo ed = new ExceptionDemo(); try { ed.calc(10); ed.divide(100, 0); } catch (Exception e) { System.out.println(e.getMessage()); } ed.exceptionTest(); } } |
这种主动抛出异常信息的情况通常适用于在某个方法中,当参数不正确或者处理有问题时,无法单纯通过返回值来处理时,利用抛出异常的方式中止当前程序运行,交由调用处处理会是一种比较方便的做法。
8.利用异常检查用户输入。
在WoniuATM中,我们有很多地方要求用户输入数据,比如输入转账金额时,那么如何对金额的正确性进行判断呢?当然,我们有很多种方法:
(1) 让用户输入一个字符串,并遍历整个字符串,根据每个字符的ASCII码来确认是否包含有除0-9的字符和小数点以外的其它字符,如果发现1个,则说明用户输入无效。在我们基础部分的练习中有结这种用法的练习,读者朋友可以回顾一下具体的用法。
(2) 使用“sc.nextInt()”或“sc.nextFloat()”直接要求用户输入一个数字,但是此时,如果用户输入的不是一个数字,则会抛出异常。
(3) 仍然使用“sc.next()”接收用户输入一个字符串,并尝试利用“Integer.parseInt()”将该字符串转换为一个小数,如果出现异常,则说明用户输入无效。
(4) 利用正则表达式对用户输入的字符串进行检查,后续实验中我们会专门为大家介绍正则表达式。
那么,此处,我们就用异常处理机制来为大家讲解一下验证用户输入时的具体用法,代码如下:
public int inputCheck() { Scanner sc = new Scanner(System.in); System.out.println("请输入转账金额(整数):"); String value = sc.next(); int money = 0; try { money = Integer.parseInt(value); System.out.println("输入正确,继续处理转账代码即可."); sc.close(); } catch (Exception e) { System.out.println("你输入的金额不正确,请重新输入."); this.inputCheck(); } return money; } |
1.请利用异常处理机制重构WoniuATM的代码。思考练习
2.请自学使用Java的自定义异常类及异常信息。
3.请总结使用异常处理机制和使用if…else…进行判断两种情况下的差异。