• 1. 第7章 异常处理
  • 2. 为什么要异常处理?对于任何语言的程序设计而言,错误的发生总是不可避免的. 比如说: ① 用户输入出错 ② 所需文件找不到 ③ 运行时磁盘空间不够 ④内存耗尽无法进行类的实例化 ⑤ 算术运算错 (数的溢出,被零除…) ⑥ 数组下标越界 ⑦JVM崩溃 …… 当Java程序出现以上的错误时,就会在所处的方法中产生一个异常对象。这个异常对象包括错误的类型,错误出现时程序的运行状态以及对该错误的详细描述。下面我们先看一个简单的例子。
  • 3. 例5.1 public class ExceptionDemo { public static void main(String args[]) { int x=100; System.out.println("The result is"+x/10); System.out.println("Divided by zero: "+x/0); } }
  • 4. 当我们对其编译后运行时,其对应的结果如下: 其意思是说,本程序执行到语句“System.out.println ("Divided by zero: "+x/0)”时,系统会抛出一个例外,该例外在Java中定义为Arithmetic Exception (即属于算术运算例外)。 c:\jbuilder3\java\bin>java Exception Demo The result is10 Exception in thread "main" java.lang.Arithmetic Exception: / by zero at Exception Demo.main(Exception Demo.java:5)
  • 5. 什么是异常?异常(Exception)又称为例外,是指在程序运行过程中发生的非正常事件,它会中断指令的正常执行,影响程序的正常运行。
  • 6. 异常对象在Java语言中,我们用异常对象来表示不同的异常。 所谓Java异常对象就是一个存放着相关错误信息的对象,如果方法运行时产生了异常,该方法就可以抛出一个异常对象 为了表示不同种类的异常,Java语言中定义了许多异常类。
  • 7. 异常处理的一般步骤: 异常抛出异常捕获异常处理异常处理机制
  • 8. 异常处理机制在Java程序的执行过程中,如果出现了异常事件,就会生成一个异常对象。 生成的异常对象将传递给Java运行时系统,这一异常的产生和提交过程称为抛出(throw)异常。
  • 9. 异常处理机制当Java运行时系统得到一个异常对象时,它将会寻找处理这一异常的代码。找到能够处理这种类型的异常的方法后,运行时系统把当前异常对象交给这个方法进行处理,这一过程称为捕获(catch)异常。 如果Java运行时系统找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出。
  • 10. 方法的调用堆栈main() methodA() methodB() methodC()调 用Java程序在执行的过程中,形成了一个先进后出的调用堆栈,各方法之间依照调用先后的不同,由先至后的进入调用堆栈,堆栈的最上层即是当前被调用执行的方法。该方法执行完毕后,会将处理器控制权交还给调用他的方法,依此类推。
  • 11. 方法调用堆栈中异常对象的传递 当某一方法中的一个语句抛出一个异常时,如果该方法中没有处理该异常的语句,那么该方法就会中止执行,并将这个异常传递给堆栈中的下一层方法,直到某一方法中含有处理该异常的语句为止。如果该异常被传递至主方法,而主方法中仍然没有处理该异常的语句,则异常将会被抛至JVM,程序中断。main() methodA() methodB() methodC()调 用传 递
  • 12. 例 程public class ExampleOfException { String[] lines = {"The first line", "The second line","The last line"}; public static void main (String[] args) { ExampleOfException eoe = new ExampleOfException(); eoe.methodA(); System.out.println("Program finished."); } void methodA() { methodB(); } void methodB() { methodC(); } void methodC() { for (int i=0; i<4; i++) System.out.println (lines[i]); } } The first line The second line The last line Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 at ExampleOfException.methodC(ExampleOfException.java:16) at ExampleOfException.methodB(ExampleOfException.java:12) at ExampleOfException.methodA(ExampleOfException.java:9) at ExampleOfException.main(ExampleOfException.java:6)
  • 13. Java中的异常类在Java语言中,任何的异常对象都是Throwable类的直接子类或间接子类的实例。Java的类库已经提供了一些常见的异常类,如果这些异常类不能够满足要求,用户也可以创建自己的异常类。
  • 14. AWTExceptionThrowableErrorExceptionRuntimeExceptionIOExceptionLinkageErrorVirtualMachineErrorAWTErrorArithmeticExceptionIndexOutOfBounds.InterruptedExceptionFileNotFoundExceptionEOFException.................
  • 15. 异常(Throwable)分类Error 动态链接失败,虚拟机错误等,通常Java程序不应该捕获这类异常,也不会抛出这种异常。 Exception 运行时异常 继承于RuntimeException。Java编译器允许程序不对它们做出处理。 非运行时异常 除了运行时异常之外的其他由Exception继承来的异常类。Java编译器要求程序必须捕获或者声明抛出这种异常。
  • 16. Error类 Error类表示Java运行时产生的系统内部错误或资源耗尽等严重错误。 这种错误通常是程序无法控制和解决的,如果发生这种错误,通常的做法是通知用户并中止程序的执行。
  • 17. 典型的错误类NoClassDefFoundError OutOfMemoryError VirtualMachineError 。。。。
  • 18. 表5.1 Java常见错误列表类 名 功 能 描 述 ClassCircularityError 初始化某类检测到类的循环调用错误 ClassFormatError 非法类格式错误 IllegalAccessError 非法访问错误 IncompatibleClassChangError 非兼容类更新错误 InternalError 系统内部错误 LinkageError 链接错误 NoClassDefFoundError 运行系统找不到被引用类的定义
  • 19. NoSuchFieldError 找不到指定域错误 NoSuchMethodError 所调用的方法不存在 OutofMemoryError 内存不足错误 UnknownError 系统无法确认的错误 UnsatisfiedLinkError 定义为本地的方法运行时与另外的例程相连接错误 VerifyError 代码校验错误 VirtualMachineError 虚拟机出错,可能JVM错或资源不足 InstantiationError 企图实例化一个接口或抽象类的错误
  • 20. Exception类 Exception的子类表示了不同类型的异常,例如RuntimeException表示运行时异常,而IOException表示I/O问题引起的异常。 这些子类也可以被继承以对不同类型的异常进行细分,如RuntimeException还可细分为NullPointerException、ArithmeticException等;IOException还可细分为FileNotFoundException、EOFException等。
  • 21. 典型的异常类ArithmeticException ArrayIndexOutOfBandsException IOException FileNotFoundException NullPointerException NumberFormatException
  • 22. 表5.2 Java常见的一般异常列表 类 名 功 能 描 述 IllegalAccessException 非法访问异常 ClassNotFoundException 指定类或接口不存在异常 CloneNotSupportException 对象使用clone方法而不实现cloneable接口 IOException 输入/输出异常 InterruptedIOException 中断输入/输出操作异常
  • 23. InterruptedException 中断异常(常常应用于线程操作中) EOFException 输入流中遇到非正常的EOF标志 FileNotFoundException 指定文件找不到 MalformedURLException URL格式不正确 ProtocolException 网络协议异常 SocketException Socket操作异常 UnknownHostException 给定的服务器地址无法解析 UnknownServiceException 网络请求服务出错 UTFDataFormatException UTF格式字符串转换出错 InstantiationException 企图实例化接口或抽象类 NoSuchMethodException 找不到指定的方法
  • 24. 表5.3 Java常见的运行异常列表 类 名 功 能 描 述 ArithmeticException 算术运算除数为零 IndexOutofBoundException 下标越界错误 ArrayIndexOutofBoundsException 数组元素下标越界错误 StringIndexOutofBoundsException 字符串下标越界错误 ClassCastException 类型强制转换异常 NegativeArraySizeException 数组的长度为负异常 NullPointerException 非法使用空指针异常
  • 25. NumberFormatException 非法数据格式异常 IllegalArgumentException 非法参数异常 IllegalMonitorStateException 非法监视器操作异常 IllegalThreadStateException 非法线程状态异常 EmptyStackException 栈空异常,对空栈进行操作 NoSuchElementException 枚举对象不存在给定的元素异常 SecurityException 安全性异常
  • 26. 必检异常与非必检异常RuntimeException类及其子类被称为“运行时异常” 一般发生在JRE内部 也称“非必检异常” 如NullPointerException 其他异常被成为“非运行时异常” 一般发生在JRE外部 也称“必检异常” 如IOException
  • 27. 异常的捕获要捕获一个异常,程序员只需要在程序中设置一个try/catch块,其格式如下: try{ 抛出异常的代码 }catch (某Exception类型 e){ 处理该异常类型的代码 }catch (某Exception类型 e){ 处理该异常类型的代码 }
  • 28. try 捕获异常的第一步是用try{…}选定捕获异常的范围,由try所限定的代码块中的语句在执行过程中可能会生成异常对象并抛出。
  • 29. catch 每个try代码块可以伴随一个或多个catch语句,用于处理try代码块中所生成的异常事件。catch语句只需要一个形式参数指明它所能够捕获的异常类型,这个类必须是Throwable的子类,运行时系统通过参数值把被抛出的异常对象传递给catch块. 在catch块中是对异常对象进行处理的代码,与访问其它对象一样,可以访问一个异常对象的变量或调用它的方法。getMessage( )是类Throwable所提供的方法,用来得到有关异常事件的信息,类Throwable还提供了方法printStackTrace( )用来跟踪异常事件发生时执行堆栈的内容。
  • 30. 异常的捕获过程当try块中的某条代码抛出异常时:首先,自该语句的下一条语句起的所有try块中的剩余语句将被跳过不予执行;其次,程序执行catch子句进行异常捕获,异常捕获的目的是进行异常类型的匹配,并执行与所抛出的异常类型相对应的catch子句中的异常处理代码。
  • 31. 异常的捕获过程需要注意的是:如果try块中没有任何的异常抛出,则所有的catch子句将会被跳过;如果try块中所抛出的异常对象类型与所有的catch子句中的所声明的异常类型都不匹配,则方法会立即中止,并将该异常对象继续抛出,沿调用堆栈传递。
  • 32. examplepublic class ExampleOfException { String[] lines = {"The first line", "The second line","The last line"}; public static void main (String[] args) { ExampleOfException eoe = new ExampleOfException(); eoe.methodA(); System.out.println("Program finished."); } ... void methodC() { for (int i=0; i<4; i++) { try { System.out.println (lines[i]); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Re-setting Index Value"); } } } }
  • 33. example上面的例子中可能会产生数组越界异常,所以将其置于try块中,并在catch子句中对ArrayIndexOutOfBoundsException类型的异常进行捕获,并进行处理。 如果try块中可能抛出多个类型的异常,程序员可以使用多个catch子句对这些异常进行捕获,每种异常类型对应一个单独的catch子句。 需要注意的是,这些catch子句是顺序执行的。这意味着,异常对象总是被第一个catch子句首先捕获,如果类型不匹配,才会执行下一个catch子句。读者可以试着分析下面的程序片断,看看有什么样的问题存在。
  • 34. 问题Java运行系统从上到下分别对每个catch语句处理的例外类型进行检测,直到类型匹配为止; catch语句的排列顺序应该是从子类到父类try { … }catch(Exception e) { … }catch(IOException e) { … }catch(ArrayIndexOutOfBoundsException e) { … }
  • 35. catch语句的顺序捕获异常的顺序和不同catch语句的顺序有关,当捕获到一个异常时,剩下的catch语句就不再进行匹配。因此,在安排catch语句的顺序时,首先应该捕获最特殊的异常,然后再逐渐一般化。也就是一般先安排子类,再安排父类。
  • 36. finally 捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。不论在try代码块中是否发生了异常事件,finally块中的语句都会被执行。
  • 37. finallyfinally子句的一般格式: try{ 抛出异常的代码 }catch (某Exception类型 e){ 处理该异常类型的代码 } … }catch (某Exception类型 e){ 处理该异常类型的代码 }finally{ 最后一定会被执行的代码 }
  • 38. finally不论try块中的代码是否抛出异常及异常是否被捕获,finally子句中的代码一定会被执行: 如果try块中没有抛出任何异常,当try块中的代码执行结束后,finally中的代码将会被执行; 如果try块中抛出了一个异常且该异常被catch正常捕获,那么try块中自抛出异常的代码之后的所有代码将会被跳过,程序接着执行与抛出异常类型匹配的catch子句中的代码,最后执行finally子句中的代码。 如果try块中抛出了一个不能被任何catch子句捕获(匹配)的异常,try块中剩下的代码将会被跳过,程序接着执行finally子句中的代码,未被捕获的异常对象继续抛出,沿调用堆栈顺序传递。
  • 39. 问题当调用上述方法m()时,try块中包含方法的return语句,返回值为1。然而,实际调用该方法后产生的返回值为0。这是因为在方法实际返回并结束前,finally子句中的内容无论如何要被执行,所以finally子句中的return语句使得该方法最终实际返回值为0。 public int m(){ try { return 1; }finally{ return 0; } }
  • 40. examplepublic class TryCatchFinally{ static void Proc( int sel ){ System.out.println("----- In Situation"+sel+" -----"); try{ if( sel==0 ){ System.out.println("no Exception caught"); return; }else if( sel==1 ){ int i=0; int j=4/i; }else if( sel==2 ){ int iArray[]=new int[4]; iArray[10]=3; }
  • 41. example }catch( ArithmeticException e ){ System.out.println("Catch "+e); }catch( ArrayIndexOutOfBoundsException e ){ System.out.println("Catch "+e.getMessage()); }catch( Exception e ){ System.out.println("Will not be executed"); }finally{ System.out.println("in Proc finally"); } }  public static void main( String args[] ){ Proc( 0 ); Proc( 1 ); Proc( 2 ); } }程序运行结果: ----- In Situation0 ----- no Exception caught in Proc finally ----- In Situation1 ----- Catch java.lang.ArithmeticException: / by zero in Proc finally ----- In Situation2 ----- Catch 10 in Proc finally
  • 42. 声明异常一个方法不处理它产生的异常,而是沿着调用堆栈向上传递,由调用它的方法来处理这些异常,则需要声明异常。 相比较捕获异常来讲,这种处理异常的方式较消极,所以有时候也该方式叫做异常的消极处理方式,管捕获异常叫做积极处理方式。
  • 43. 声明抛出异常声明抛出异常是在一个方法声明中的throws子句中指明的。 声明异常的方法: returnType methodName([paramlist]) throws exceptionList 例如: void compute(int x) throws ArithmeticException{ … } 例如: public int read () throws IOException{ ...... } throws子句中同时可以指明多个异常,说明该方法将不对这些异常进行处理,而是声明抛出它们。
  • 44. public class ThrowsException { static void proc( int sel ) throws ArithmeticException,ArrayIndexOutOfBoundsException{ System.out.println("In Situation"+sel); if( sel==0 ){ System.out.println("no Exception caught"); return; }else if( sel==1 ){ int iArray[]=new int[4]; iArray[10]=3; } }
  • 45. public static void main( String args[] ){ try{ proc( 0 ); proc( 1 ); }catch( ArrayIndexOutOfBoundsException e ){ System.out.println("Catch "+e); }finally{ System.out.println("in Proc finally"); } } }
  • 46. 运行结果In Situation0 no Exception caught In Situation1 Catch java.lang.ArrayIndexOutOfBoundsException: 10 in Proc finally
  • 47. examplepublic class ThrowsException1{ static void Proc(int sel) throws ArrayIndexOutOfBoundsException { System.out.println("-----In Situation"+sel+"-----"); if(sel==0){ System.out.println("no Exception caught"); return; }else if(sel==1){ int iArray[]=new int[4]; iArray[10]=3; } }
  • 48. example public static void main(String args[]){ try{ Proc(0); Proc(1); }catch(ArrayIndexOutOfBoundsException e){ System.out.println("Catch "+e); }finally{ System.out.println("in Proc finally"); } } }程序运行结果: ----- In Situation0 ----- no Exception caught ----- In Situation1 ----- Catch java.lang.ArrayIndexOutOfBoundsException:10 in Proc finally
  • 49. 抛出异常若某方法中需要直接抛出某异常时,可使用throw语句实现,具体步骤应该是: 选择合适的异常类; 创建该类的一个对象; 使用throw语句抛出该对象。
  • 50. 抛出异常抛出异常首先要生成异常对象,异常或者由虚拟机生成,或者由某些类的实例生成,也可以在程序中生成。生成异常对象是通过throw语句实现的。 IOException e=new IOException(); throw e ; 可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误: throw new String("want to throw");
  • 51. 抛出异常例如,某方法readFile()对文件进行读操作,根据前面章节的介绍可以知道:当进行I/O操作时,可能会产生I/O异常。所以,在方法readFile中如果读文件操作不成功,则应抛出I/O异常。如下列程序片断所示:readFile() throws IOException{ … if (读文件不成功) throw new IOExcepion(); }
  • 52. 抛出异常如果一个方法可能抛出多个必检异常,那么必须在方法的声明部分一一列出,多个异常间使用逗号进行分隔: Class MyClass { … public myMethod(String s) throws IOException, MalformedURLException { … } … }
  • 53. 抛出异常一个方法必须通过throws语句在方法的声明部分说明它可能抛出而并未捕获的所有的“必检异常”,如果没有这么做,将不能通过编译。 值得注意的是:如果在子类中覆盖了父类的某一方法,那么该子类方法不可以比被其覆盖的父类方法抛出更多的异常(但可以更少)。所以,如果被覆盖父类的方法没有抛出任何的“必检异常”,那么子类方法绝不可能抛出“必检异常”。
  • 54. 抛出异常在下面的例子里,对于父类SuperClass而言,类SubClassA是正确的子类,而SubClassB则是错误的。 class SuperClass{ public superMethod() throws EOFException { … } } class SubClassA extends SuperClass{ //正确 public superMethod() { … } } class SubClassB extends SuperClass{ //错误 public superMethod() throws FileNotFoundException{ … } }
  • 55. 创建自己的异常类 Java语言中允许用户定义自己的异常类,但自定义异常类必须是Throwable的直接子类或间接子类。 同时要理解一个方法所声明抛出的异常是作为这个方法与外界交互的一部分而存在的。方法的调用者必须了解这些异常,并确定如何正确的处理他们。
  • 56. 创建异常类注意: 根据Java异常类的继承关系,用户最好将自己的异常类定义为Exception的子类,而不要将其定义为RuntimeException的子类。因为对于RuntimeException的子类而言,即使调用者不进行处理,编译程序也不会报错。将自定义异常类定义为Exception的子类,可以确保调用者对其进行处理。
  • 57. exampleclass MyException extends Exception{ private int detail; MyException( int a ){ detail = a; } public String toString( ){ return "MyException "+detail; } }
  • 58. examplepublic class ExceptionDemo{ static void compute(int a) throws MyException { System.out.println("called compute("+a+")"); if( a>10 ) throw new MyException(a); System.out.println("normal exit"); } public static void main( String args[] ){ try{ compute( 1 ); compute( 20 ); } catch( MyException e ){ System.out.println("Caught "+e); } } }程序运行结果: called compute(1) normal exit called compute(20) Caught MyException 20
  • 59. 异常类的使用运行时异常则表示由运行时系统所检测到的程序设计问题或者API的使用不当问题,它可能在程序的任何地方出现: (1)对于运行时异常,如果不能预测它何时发生,程序可以不做处理,而是让Java虚拟机去处理它。 (2)如果程序可以预知运行时异常可能发生的地点和时间,则应该在程序中进行处理,而不应简单的把它交给运行时系统。
  • 60. 异常类的使用(3)在自定义异常类时,如果它所对应的异常事件通常总是在运行时产生的,而且不容易预测它将在何时、何处发生,则可以把它定义为运行时异常,否则应定义为非运行时异常。
  • 61. 异常类的使用积极处理方式: import java.io.*; class ExceptionDemo1{ public static void main( String args[ ] ){ try{ FileInputStream fis = new FileInputStream( "text" ); } catch(FileNotFoundException e) { ……} …… } }
  • 62. 异常类的使用消极处理方式: import java.io.*; class ExceptionDemo1{ public static void main( String args[ ] ) throws FileNotFoundException{ FileInputStream fis = new FileInputStream( "text" ); …… } }
  • 63. 异常类的使用如果采用消极处理方式,则由调用该方法的方法进行处理;但是调用该方法的方法也可以采用消极和积极两种处理方式,一直传递到Java运行环境.
  • 64. 异常的优点将错误处理代码与“常规”代码分离; 例7.7.1 将错误沿调用堆栈传递; 可以由感兴趣的方法来处理异常 对错误类型进行分组和区分。
  • 65. 说明的问题方法也可以不对异常进行捕获而直接将其抛出,并在方法声明中进行说明,那么对方法产生的异常到底是应该直接进行捕获还是应该将其进行传递呢? 一般来说,对于方法的最终调用者而言,他必须捕获并处理该方法抛出的异常。而对于抛出异常的方法而言,应该对方法可能产生的异常进行区分,尽量避免一些异常的产生,捕获并处理那些你知道如何处理的异常,而对那些你不知道方法的调用者会如何处理的异常,最好将它们留给方法的调用者进行处理,这样会增加程序的灵活性。
  • 66. 说明的问题需要特别指出的是,虽然异常处理机制为程序员提供了非常大的方便,但是作为一个好的程序员要尽量避免异常的过度使用。这是因为:异常对象的实例化和其后续处理工作是非常消耗资源的,过度的使用异常会明显影响程序的执行速度。所以,在使用异常处理时应该仔细考虑,只对有必要的异常情况使用异常,而不可以将异常泛化。
  • 67. 两段代码的比较代码1: try { int n = InputReader.inputInteger(请输入一个整数"); if (n<100 || n>1) throw new NumberFormatException(); }catch (NumberFormatException e) { System.out.println("输入范围错误!"); } 代码2: int n = InputReader.inputInteger(请输入一个整数"); if (n<100 || n>1) System.out.println("输入范围错误!");代码1采用了异常处理方式;代码2则通过对用户输入的分析避免了异常的使用,提高了代码效率。
  • 68. 问题下面的代码合法吗? try { ... } finally { ... }下面的代码可以捕获何种异常?使用这种异常处理器有什么问题? catch (Exception e) { ... }
  • 69. 问题下面的处理器可以捕获什么异常? } catch (Exception e) { ... } catch (ArithmeticException a) { ... } 这个异常处理器中有错误吗?此代码能否被编译?
  • 70. 问题public static void cat(File named) { RandomAccessFile input = null; String line = null; try { input = new RandomAccessFile(named, “r”); while ((line = input.readLine()) != null { System.out.println(line); } return; } finally { if (input != null) { input.close(); } } }