异常处理
 
一、 Java异常基础 1.1 为什么要引入异常处理机制? 程序的错误分为: 编译错误: 程序员编写程序时语法上出现的错误;运行错误: 程序员编写的程序在语法上没有错误,但是程序在运行时出现错误,本章就主要针对该类错误讲解 - - 即异常、异常类和异常处理机制。
 
1.2 以往的错误处理方法 **主要缺陷:**
程序复杂
可靠性差
返回信息有限
返回代码标准化困难
以往的程序开发过程中,常常采用**返回值** 进行处理。例如,在编写一个方法,可以返回一个状态代码,调用者
根据状态代码判断出错与否。若状态代码表示一个错误,则调用该错误的处理程序进行相应的处理,或显示一
个错误页面或错误信息。
举例 以往的错误处理方法:采用返回值进行处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 举例:实现将一个文件从硬盘加载近来,导致加载可能失败的运行错误有硬盘错误、文件无法找到等 int status=loadTextfile();If (status!=1 ){ switch (status) { case 2 : break ; case 3 : default : }else { ```     #### <font color="#20B2AA" face="Comic sans MS">1.3 Java异常处理方法</font> <table><tr><td bgcolor=#ADD8E6><font face="Comic sans MS">Java异常处理方法:Java为运行错误引入了异常、异常类和异常处理机制。 **异常:**特殊的运行错误,是在程序运行过程中发生的、会打断程序正常执行的错误 例如: 除0 溢出 文件找不到 数组元素下标越界 **异常类:**Java用面向对象的方法处理异常,Java的异常类是处理运行时错误的特殊类,每一种异常类对应一种 特定的运行错误,每一个异常事件由一个异常类的对象来代表。 *例如:除0 溢出(ArithmeticException) 文件找不到(FileNotFoundException) 数组元素下标越界(ArrayIndexOutofBoundsException)* **异常处理机制:抛出异常——捕捉异常:** 1. 当出现了一些错误,方法都会产生一个<font color="red">异常对象</font>,这个异常对象将交由运行系统来处理。此过程就称为抛出 <font color="red">(throwing)异常</font>。**包括:系统抛出、用户自定义抛出。** 2. 接着,运行系统开始寻找合适的处理方法,来处理这个异常。如果系统找到了一个适合的处理该异常方法, 这一过程就叫<font color="red">捕获异常</font>。 </font></td></tr></table>     #### <font color="#20B2AA" face="Comic sans MS">1.4 异常处理的语法支持</font> <table><tr><td bgcolor=#ADD8E6><font face="Comic sans MS"> **-try,catch,throws,throw,finally** (1 ) try 包含可能出现异常的语句块; (2 ) 一个或多个catch 块紧随try {}块,每个catch 块通常处理指定类型的异常; (3 ) finally 引导块紧随catch 块后,主要用于清理现场(可有可无)。 格式: try { ...... }catch ( ExceptionName1 e ) { ......} catch ( ExceptionName2 e ){ ......} finally { ......} 注意:finally 总是执行,catch 块不一定执行 </font></td></tr></table>     #### <font color="#20B2AA" face="Comic sans MS">1.5 总结</font> <table><tr><td bgcolor=#EEE0E5><font face="Comic sans MS"> (1) Java的异常处理把错误集中起来统一处理。程序员只需要说明何处可能出现异常,如何处理即可; (2 ) 采用面向对象的思想标准化了各种错误的类型; (3 ) Java把程序运行过程中可能遇到的问题分为两类,一类是致命性的,即程序遇到了非常严重的不正常状 态,不能简单地恢复执行,这就是<font color="red">**错误(对应Error类)**</font>,如程序运行过程中内存耗尽。另一类是非致命性的, 通过某种处理后程序还能继续运行,这就是<font color="red">**异常(对应Exception类)**</font>。 </font></td></tr></table>     ### <font color="#FF6347" face="Comic sans MS">二、 异常类的层次</font> #### <font color="#20B2AA" face="Comic sans MS">2.1 异常分类</font> <font face="Comic sans MS"><font color="red">**☕异常类:**</font>异常在Java中都是作为类的实例(对象)的形式出现的。   如 Throwable类, Exception类, Error类…… <font color="red">**☕Java中异常分类:**</font> (1) <font color="#00B2EE">**Error类及其子类:**</font>描述Java运行时刻系统内部的错误或资源枯竭导致的错误,无法恢复和抛出,发生几率小; (2) <font color="#00B2EE">**Exception类及其子类:**</font>普通程序可以从中恢复,分为<font color="red">**运行时异常**</font>和<font color="red">**非运行时异常**</font>。  异常在Java中也是作为类的实例的形式出现的。Java中的所有的异常类都是从Throwable类派生出来的。<font color="red">**Throwable类有两个直接子类:**</font><font color="#00B2EE">**java.lang.Error**</font> 和 <font color="#00B2EE">**java.lang.Exception**</font>。 异常类的层次结构如下图所示。 ![58. png](https: **(1 ) 🔺Error类及其子类主要用来描述一些Java运行时刻系统内部的错误或资源枯竭导致的错误。**普通的程序不能从这类错误中恢复,也无法抛出这种类型的错误,这类错误出现的几率是很小的。 **(2 ) 🔺另一个异常类的子类是Exception类和它的子类。**在编程中错误的处理主要是对这类错误的处理,如除数为零、数组下标越界等。类Exception是普通程序可以从中恢复的所有规范了的异常的父类。 </font>     #### <font color="#20B2AA" face="Comic sans MS">2.2 Exception类的子类</font> <font face="Comic sans MS"><font color="red">**☕Exception类子类有两种:**</font> **运行时异常**和**非运行时异常(一般异常)**     *区别???* <font color="#00B2EE">**(1)运行时异常:**</font>RuntimeException类及其所有子类。 运行时异常是程序员编写程序不正确所导致的异常,理论上,程序员经过检查和测试可以查出这类错误。如**除数为零等,错误的强制类型转换、数组越界访问、空引用**。 <font color="#00B2EE">**(2)非运行时异常(一般异常):**</font>指可以由编译器在编译时检测到的、可能会发生在方法执行过程中的异常,如找不到指定的文件等,这不是程序本身的错误,如果这些异常情况没有发生,程序本身仍然是完好的。 <font color="red">**注意:**</font>**编译器强制要求Java程序必须**<font color="#00B2EE">**捕获**</font>**或**<font color="#00B2EE">**声明抛出**</font>**所有非运行时异常,但对运行时异常不作要求。运行时异常编译可以通过但是运行时出现异常;非运行时异常编译的时候就通不过。** *例如:格式不正确的URL、试图为一个不存在的类找到一个代表它的类的对象。除了runtimeexception及其子类以外,其他exception类的子类都是非运行时异常。* </font> ```java [例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 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [例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编译器对程序进行编译的时候,便指出用户需要①捕获该类异常 或者②声明抛出 。即对于非运行时异常,用户需要在程序中进行处理,否则编译时无法通过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ① 捕获该类异常 [例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(); } }
   
2.3 对于运行时异常和非运行时异常的一些说明 1. ☕编译器和异常: 编译器强制要求程序员捕获或声明抛出非运行时异常 到底为什么要这么做呢??? 对于运行时异常,编译器不强制要求,但用户也可以自己去捕获 这个时候会出现什么效果呢???
2. ☕运行时系统和异常: (1) 异常都有抛出的轨迹; (2) 对于所有异常,某个方法产生的异常 ,如果没有被捕获,就会自动抛给方法的调用者(但是对非运行时异常一定要声明抛出),如果调用者还没有捕获,再抛给调用者的调用者,以此类推,直到main方法里发现还没有捕获,那么运行时系统就会来处理这个异常,把异常信息和异常的轨迹信息打印给用户。
3. ☕两点建议: (1) 建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程者希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误; (2) 建议对于非运行时异常,用户应该去捕获,以交代程序员的处理该错误的代码,实在不行,就声明抛出给方法的调用者。
3. ☕异常类常用的方法: public Exception() public Exception(String s)   :该参数一般表示该异常对应的错误的描述public String toString()  :返回描述当前异常对象信息的字符串public String getMessage()  :返回描述当前异常对象信息的详细信息。public void printStackTrace()  :打印当前异常对象使用堆栈的轨迹。
   
三、 try-catch-finally异常处理 3.1 概括try-catch-finally语句 用户处理异常的三种方法: (1)用户可以用try-catch-finally语句进行抛出 和捕获处理 ; (2)如果不想捕获和处理异常,可以通过throws语句声明要抛出的异常 ; (3) 用户可以定义自己的异常类,并用throw语句来抛出。
运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置。即:运行时异常:会输出到设备显示哪里存在错误,请更正;非运行时异常:编译报错时提示必须添加非运行时异常处理,比如加try{}…catch(类名 对象){}或者使用throws来抛出异常 。
1 2 3 4 5 6 7 8 9 10 11 12 13 try -catch -finally 语句对程序运行进行监控,捕获和处理异常通常形式: try {调用可能产生异常的方法及其它java语句; } catch (异常类名1 异常对象名e){异常处理语句块; } catch (异常类名2 异常对象名e){异常处理语句块; } finally {最终处理; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [例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); } catch (IOException e){ System.out.println("捕获异常:" +e); } finally { System.out.println("finally块总是执行!" ); } } } 运行结果: 捕获异常:java.io.FileNotFoundException: text.txt (系统找不到指定的文件。) finally 块总是执行!
   
3.2 try语句 ☕try{} :将可能抛出一个或者若干个异常的代码放入try语句块中。注意: 应当尽量减小 try代码块的大小,不要将整个程序代码全部放入try语句块中,而是应当仔细分析代码,在可能出现异常情况的地方用try进行监控。
因为当发生异常时,程序控制由try块转到catch块,Java将跳过try中后面的语句,且永远不会从catch块返回到try块。因此若将整个程序代码都放在try中,若一开始发生异常,则后面的语句将永远不会被执行,从而影响了程序的实现。
 
3.3 catch语句 **📒(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块放在后面。**
 
3.4 finally子句 **1.finally语句:** 无论在try块中是否产生异常,也不管产生的异常是否会被捕获,finally中的语句最终都会
被执行。
**2.作用:** 为异常处理事件提供一个清理机制,例如清理打开文件、Socket、JDBC连接之类的资源。
如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。  finally语句可以说是为异常处理事件提供的一个清理机制. 一般是用来关闭文件或释放其他的系统资源,作 为try-catch-finally结构的一部分,可以没有finally语句,如果存在finally语句,不论try块中是否发生 了异常,是否执行过catch语句,都要执行finally语句。
3. 带有finally子句的try-catch-finally语句的形式如下:
1 2 3 4 5 6 try { … } catch (异常类1 e) { … } catch (异常类2 e) { … } …… catch (异常类n e) { … } 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-throw抛出异常 4.1 throws抛出异常 📒throws说明: 在设计可能会抛出异常的方法时,可以有两个选择: (1) 使用try-catch-finally处理方法中的异常 ;(2) 声明抛出异常: 不捕获异常(没有throws方法的创建异常类的实例和抛出异常。是个空语句。 ),而是将异常交由上一层处理,在其他地方捕获异常。如果使用后者,那么应该(在某些情况下)向编译器表明:此方法可能会抛出异常,但方法本身不会捕获它。可以在方法头中用throws子句来实现此功能。
(2.1)带throws异常说明的方法说明形式如下: … 方法名(…) [throws 异常类列表] { 方法体 }  📒注意: (1)方法抛出的异常类是throws子句中指定的异常类或其子类。 (2)并不是所有可能发生的异常都要在方法的说明中指定,从Error类中派生出的异常和从RuntimeException类中派生的异常就不用在方法声明中指定。
📒在下列情况下Java方法可以声明抛出异常: (1)调用的方法抛出了异常; (2)检测到了错误并使用throw语句抛出异常;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 例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 { …… } }
   
4.2 throw抛出异常 📒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)抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 举例 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; }的语句,是个空语句。