标签:
异常处理机制
异常机制可以使程序中的异常处理代码和正常业务代码分离。
Java的异常处理机制主要依赖于try、catch、finally、throw和throws五个关键字。
try关键字后面紧跟一个花括号括起来的代码块(花括号)不可省略,简称try块,里面放置可能引发异常的代码。
catch后对应异常类型和一个代码块,用于表达该catch块用于处理这种类型的代码块。
finally块用于回收try块里打开的物理资源,异常机制可以保证finally块总会被执行。
throws主要在方法签名中使用,用于声明该方法可能抛出的异常。
throw用于抛出一个实际的异常,throw可以单独作为语句使用,抛出一个具体的异常对象
Java将异常分为两种:Checked异常和Runtime异常。Checked异常都是可以在编译阶段被处理的异常,所以Java强制程序处理所有的Checked异常;而Runtime异常无须处理。
Java异常处理机制可以让程序员具有极好的容错性,让程序更加健壮。当程序出现意外情形时,系统会自动生成一个Exception对象类通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。
使用try…catch捕获异常
如果程序可以顺利完成,那就“一切正常”,把系统的业务实现代码放在try块中执行,所有异常处理逻辑放在catch块中进行处理。
try{ //业务实现代码 }catch (Exception e){ //异常处理逻辑 }
执行try块里的业务逻辑代码时出现异常,系统会生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为throw(抛出)异常。
当运行时环境收到异常对象,会寻找能处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给catch块处理,这个过程被称为catch(捕获)异常。如果没有找到合适的catch块,运行时环境终止,Java程序也将退出。
不管程序代码块处于try块中,甚至包括catch块中的代码,只要执行该代码块时出现了异常,系统总会自动生成一个异常对象。如果没有为这段代码定义任何的catch块,Java运行时环境无法找到处理该异常的catch块,程序就此退出。
catch块中可以使用continue,忽略本次循环剩下的代码,开始下一次循环。
异常类的继承体系
每个catch块都有专门用于处理异常类及其子类的异常实例。
当Java运行时环境接收到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例。如果是,Java运行时环境调用该catch块来处理异常。如果不是,再拿该异常对象和下一个catch块进行比较。
在通常情况下,try块被执行一次,则try块后只有一个catch块被执行,绝不可能有多个catch块被执行。除非在循环中使用continue开始下一次循环,下一个循环重新运行了try块,这才可能导致多个catch块被执行。
注意:try块后面的花括号不可以被省略,即使只有一行。try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块中不能访问该变量。
//花括号不可省略 try{ //业务实现代码 //try块内声明的变量是局部变量,只在try块内有效 int a = 0; }catch (Exception e){ //异常处理逻辑 }
Java所有非正常情况分为两种:异常(Exception)和错误(Error),它们都继承了Throwable父类。
Error错误一般指与虚拟机相关的问题,如:系统奔溃、虚拟机错误、动态链接失效等。这种错误无法恢复或不可能捕获,将导致应用程序中断。应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其子类。
//请注意异常类范围由小异常到大异常 public class ErrorTest{ public static void main(String[] args){ try { int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); int c = a / b; System.out.println("您输入 两个数相除结果是:" + c); } catch (IndexOutOfBoundsException ie){ System.out.println("数组越界"); } catch (NumberFormatException ne){ System.out.println("数字格式异常"); } catch (ArithmeticException ae){ System.out.println("算术异常"); } catch (Exception e){ System.out.println("未知异常"); } } }
所有父类异常的catch块都应该排在子类异常的catch块后面(简称:先处理小异常,再处理大异常)否则将会出现编译错误。
多捕获异常
使用一个catch块捕获多种类型的异常需要注意如下两个地方:
1、捕获多种类型异常时,多种异常直接使用竖线(|)隔开;
2、捕获多种类型异常时,异常变量有隐式的final修饰,因此程序不可以对异常变量重新赋值
public class ErrorTest{ public static void main(String[] args){ try { int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); int c = a / b; System.out.println("您输入 两个数相除结果是:" + c); } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie){ System.out.println("数据异常"); //捕获多种异常,异常变量有默认的final修饰,不可对异常变量重新赋值 //下面代码又错 ie = new ArithmeticException("test"); } catch (Exception e){ System.out.println("未知异常"); //下面代码没有问题,捕获一种类型的异常时,异常变量没有final修饰 e = new RuntimeException("test"); } } }
访问异常信息
如果需要在catch块中访问异常对象的相关信息,则可通过访问catch块后的异常形参来获取。
所有异常对象包含下面几个常用方法:
getMessage():返回该异常的详细描述字符串。
printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
printStackTrace(PrintStream s):将跟踪栈信息输出到指定流。
getStackTrace():返回该异常的跟踪栈信息。
使用finally回收资源
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。而try块中程序有时候会打开一些物理资源(比如数据库连接、网络连接和磁盘文件等),这些物理资源必须显式回收。
异常处理语法结构中只有try块是必须的,也就是说,如果没有try块,则不能后面的catch块和finally块;catch块和finally块是可选的,但至少必须出现其中之一。可以有多个catch块,但捕获父类异常必须位于捕获子类异常后面。finally块必须位于所有catch块后面。
public class FinallyTest{ public static void main(String[] args){ FileInputStream fis = null; try{ fis = new FileInputStream("a.txt"); } catch(IOException IOe){ System.out.println(IOe.getMessage()); //使用return语句强制方法返回 return; //使用exit退出虚拟机 //System.exit(); } finally{ if(fis != null){ try{ fis.close(); }catch(IOException e){ e.printStackTrace(); } } System.out.println("执行finally块的资源回收"); } } }
除非在try块、catch块中调用退出虚拟机的方法,否则不管try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总会被执行。上面程序中使用return强制返回方法,finally块依然会执行,但退出虚拟机,就不会执行了。
通常情况下不要在finally块中使用如return或throw等导致方法终止的语句,一旦在finally块中使用了return或throw语句,将导致try块、catch块中的return、throw语句失效。
public class FinallyFlowTest{ public static void main(String[] args) throws Exception{ boolean a = test(); System.out.println(a); } public static boolean test(){ try{ return true; }finally{ return false; } } }
异常处理的嵌套
如上面FinallyTest程序所示,finally块中也包含一个完整的异常处理流程,这种在try块、catch块或finally块中包含完整异常狐狸流程的情形被称为异常处理的嵌套。
通常没必要使用超过两层的嵌套异常处理。
自动关闭资源的try语句
Java 7开始,try关键字后面可以紧跟一个圆括号,圆括号可以声明、初始化一个或多个资源,此处的资源指的是那些必须在程序结束时显示关闭的资源(比如:数据库连接、网络连接等),try语句在该语句结束时自动关闭这些资源。
为了保证try块可以正常关闭资源,这些资源实现类必须实现AutoCloseable或者Closeable接口,实现这两个接口就必须实现close()方法。
Closeable是AutoCloseable的子接口,可以自动关闭的资源需要实现这两个接口中的一个。Closeable接口里的close()方法声明抛出了IOException,因此它的实现类在实现close()方法时只能声明抛出IOException或其子类。AutoCloseable接口里的close()方法声明抛出Exception,因此它的实现类在实现close()方法时可以声明抛出任何异常。
public class AutoTest{ public static void main(String[] args) throws IOException{ //声明初始化可关闭的资源,try块结束资源关闭 try( BufferedReader br = new BufferedReader(new FileReader(AutoTest.java)) ){ //使用资源 System.out.println(br.readLine()); } } }
自动关闭资源的try语句相当于包含了隐式的finally块(这个finally块用于关闭资源)。因此,这个try块既没有catch块,也没有finally块。
程序需要的话,自动关闭资源的try块后面可以带多个catch块和一个finally块。
Checked异常和Runtime异常
Java的异常分为两大类:Checked异常和Runtime异常(运行时异常)。所有的RuntimeException类及其子类的实例被称为Runtime异常。不是RuntimeException类及其之类的实例被称为Checked异常。
只有Java提供了Checked异常。Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显示处理Checked异常,如果没有处理,编译时就会发生错误,无法通过编译。
Checked体现了java的设计哲学:没有完善错误处理的代码根本不会被执行。
对于Checked异常的处理方式有两种:1、当前方法明确知道如何处理该异常,程序使用try…catch块来捕获该异常,然后在对应catch块中修复该异常;2、当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出异常。
Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果需要捕获,也可以使用try…catch块来实现。
使用throws声明抛出异常
当前方法不知道如下处理这种类型的异常,该异常应该由上一级调用者处理;如果main方法也不知道如何处理,也可以使用throws声明抛出异常,该异常交给JVM处理。
JVM对异常的处理方法是:打印异常的跟踪栈信息,并终止程序运行。
使用throws声明抛出异常,只能在方法签名中使用,throws可以抛出多个异常,多个异常直接用逗号隔开。
一旦使用throws语句声明抛出该异常,程序就无须使用try…catch块来捕获该异常。
public class ThrowsTest{ public static void main(String[] args) throws IOException{ FileInputStream fis = new FileInputStream("a.txt"); } }
上面程序声明不处理IOException,将该异常交给JVM处理,所以程序一旦遇到该异常,JVM就会打印该异常的跟踪栈信息,并结束程序。
使用throws声明抛出异常时有一个限制,就是方法重写时“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
使用throw抛出异常
Java允许程序自行抛出异常,自行抛出异常用throw语句完成。
系统跟是否抛出异常,可能需要根据应用的业务需求来决定,如果程序中的数据、执行与既定的业务需求不符,这就是一种异常。
由于与业务需求不符产生的异常,必须由程序员决定抛出,系统无法抛出这种异常。
程序需要抛出异常,使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
try{ … //使用throw语句抛出异常 throw new Exception("抛出异常"); }catch(Exception e){ … }
不管系统自动抛出的异常,还是程序手动抛出的异常,Java运行时环境对异常的处理没有任何差别。
如果throw抛出的是Checked异常,则该throw语句要么处于try块里,显示捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给该方法的调用者处理;如果抛出的是Runtime异常,则该语句无须放在try块中,也无须放在带throws声明抛出的方法中;程序即可通过try…catch来捕获并处理异常,也可以完全不理会异常,把异常交给该方法调用者处理。
自定义异常类
通常情况下,程序很少会自行抛出系统异常,因为异常的类名通常也包含了该异常的有用信息。
用户自定义异常类都应该继承Exception基类,如果需要定义Runtime异常,则应该继承RuntimeException基类。
定义异常类通常需要提供两个构造器:1、一个是无参数的构造器;2、带个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象getMessage()方法的返回值)。
public class AutiException extends Exception{ //无参数构造器 public AutiException(){} //带字符串参数构造器 public AutiException(String str){ //调用父类构造器 //super调用可以将此字符串参数传给异常对象的message属性 super(str); } }
catch和throw同时使用
前面介绍异常处理方式有两种,实际应用中可能出现一个异常出现,单靠某个方法无法完全处理该异常,必须由几个方法写作才可以完全处理。也就是,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获异常。
try{ … }catch(Exception e){ … //使用throw语句抛出异常 throw new AuctionException("抛出异常"); }
注:这种catch和throw结合使用的情况在大型企业级应用中非常常用。企业级应用对异常的处理通常分为两个部分:1、应用后台需要通过日志来记录异常发生的详细情况;2、应用还需要根据异常应向使用者传达某种提示。这种情况下必须将catch和throw结合使用。
try{ new FileOutputStream("a.txt"); }catch(Exception e){ e.printStackTrace(); throw e; }
上面程序中,程序不会该异常时,声明该异常的类型为Exception;但实际上try块可能只调用FileOutputStream构造器,这个构造器声明只是抛出FileNotFoundException异常。因此该异常处理的调用者在方法声明处只需抛出FileNotFoundException异常即可。
异常链
当业务逻辑层访问持久层出现SQLException异常时,程序不应该把底层的SQLException异常传到用户界面。有两个原因:1、正常用户不想看到底层的SQLException异常,对他们使用系统没有任何帮助;2、恶意用户,异常暴露不安全。
通常的做法是:程序先捕获原始异常,然后抛出一个新的业务异常,新的业务异常包含了对用户的提示信息,这种处理方式被称为转译。
把原始异常信息隐藏起来,仅向上提供必要的异常信息的处理方式,可以保证底层异常不会扩散到表现层,避免向上暴露太多的实现细节。
把捕获一个异常抛出另一个异常,并把原始异常信息保存下来的典型链式处理(23中设计模式之一:职责链模式),也称为“异常链”。
所有Throwable的子类在构造器中可以接受一个cause对象作为参数。这个cause就用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置创建并抛出新的异常,你也可以通过这个异常链追踪到异常最初发生的位置。
public class AutiException extends Exception{ //创建一个可以几首Throwable参数的构造器 public AutiException(Throwable t){ suprt(t); } }
Java异常跟踪栈
异常对象的printStackTrace()方法用于法印异常的跟踪栈信息,根据这个方法输出结果,可以找到异常的源头,并跟踪到异常一路触发的过程。
只要异常没有被完全捕获(包括异常没有被捕获,或异常捕获以后重新抛出新异常),异常从发生异常的方法逐渐向外传播,首先传给该方法调用者,该方法调用者再次传给其调用者,知道最好传给main方法,如果main方法依然没有处理,JVM会终止该程序,并打印异常跟踪栈信息。
通常程序的入口是main方法或Thread类的run方法(多线程情形)
异常处理规则
1、使程序代码混乱最小化;
2、捕获并保留诊断信息;
3、通知合适的人员;
4、采用合适的方式结束异常活动。
不要过度使用异常
过度使用异常主要表现两个方面:1、把异常和错误混淆在一起,不在写任何错误处理代码,而是简单抛出异常;2、使用异常处理来代替流程控制。
只有对外部的、不能确定和确定的运行时错误才使用异常。
异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝不要使用异常处理来代替正常的业务逻辑判断。
异常机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。异常只能用于处理非正常情况
不要使用过于庞大的try块,把大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,从而分别捕获并处理异常。
catch All语句
避免使用catch All语句,所谓catch All语句指的是一种异常捕获模块,它可以处理程序发生的所有可能异常,这种情况有两种不足:1、所有异常采用相同的处理方式,这将导致无法对不同的异常分情况处理,如果要分情况处理,则需要在catch块中使用分支语句进行控制;2、这种捕获方式可能将程序中的错误、Runtime异常可能导致程序终止的情况全部捕获到。从而压制异常。一些关键异常,被静悄悄忽略。
catch All语句不过是一种通过避免错误处理而加快编程进度的机制,应尽量避免在实际应用中使用这种语句。
不要忽略捕获到的异常
异常捕获到catch块应该做些有用的事:处理并修复这个错误。catch块整个为空,或仅仅打印出错误信息都是不妥的。
捕获到异常应该处理异常、重新抛出新异常、在合适的层处理异常。
标签:
原文地址:http://www.cnblogs.com/changzuidaerguai/p/5720925.html