异常概述
异常处理已经成为衡量一门语言是否标准的标准之一。增长了异常处理机制后的程序有更好的容错性。java
java的异常处理机制可让程序员具备极好的容错性,让程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Expection对象来通知程序,从而实现将“业务功能实现代码”和“错误处理代码”分离,提供更好的可读性。程序员
使用try…catch捕获异常
语法结构web
try{ //业务实现代码 ... } catch(Exception e){ alert输入不合法 }
当try块里的业务逻辑代码出现异常时,系统会自动生成一个异常对象,该异常对象被提交给java运行时环境,这个过程被称为抛出异常。
当运行时环境收到异常对象时,会寻找能处理该异常对象的catch块,若是找到合适的catch块,则把该异常对象交给该catch快处理,这个过程被称为捕获异常;若是java运行时环境找不到捕获异常的catch块,则运行时环境终止,java程序也将退出。数据库
运行下面的程序,报出异常ArithmeticException: / by zero,除数不能为0编程
public static void main(String[] args) { int a=1; int b=0; System.out.println(1/0); //Exception in thread "main" java.lang.ArithmeticException: / by zero }
用try…catch捕获这个异常,友好的处理。数组
public static void main(String[] args) { int a=1; int b=0; try { System.out.println(1/0); }catch (ArithmeticException e){ System.out.println("除数不能为0"); //除数不能为0 } }
异常类的继承体系安全
当java运行时环境收到异常后,会依次判断该异常对象是不是catch块后异常类或其子类的实例,若是是,java运行时环境将调用该catch块来处理异常;不然再次拿该异常对象和下一个catch块里的异常类进行比较。网络
当程序进入负责异常处理的catch块时,系统生成的异常对象ex将会传给catch后的异常参数。try后面能够跟catch块,当系统发生不一样的异常状况时,系统会生成不一样的ex对象。在一般状况下若是try块被执行一次,则try后只有一个catch块被执行,除非使用continue,不然不会执行多个catch块。svg
java中异常类的继承code
java把全部非正常状况分为两种:异常(Exception)和错误(Error),都集成Throwable父类。
Error错误,通常是指与虚拟机相关的问题,这种错误没法恢复或不可能捕获,将致使应用程序中断。
处理异常规则:先处理小异常,再处理大异常
java7提供的多异常捕获
从java7开始一个catch块能够捕获多种类型的异常。
规则简述:
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){ //异常变量默认有final修饰 System.out.println("程序发生了数组越界、数字格式异常、算数异常之一"); }catch (Exception e){ System.out.println("未知异常"); } }
访问异常信息
若是程序须要在catch块中访问异常对象的相关信息,则能够经过访问catch块后的异常形参来得到。当决定调用某个catch块来处理该异常时,会将异常对象赋值给异常数。
异常对象包含的经常使用方法:
getMessage()
:返回该异常的详细描述字符;printStackTrace()
:将该异常对象的跟踪栈信息输出到标准错误输出;printStackTrace(PrintStream s)
:将该异常的跟踪栈信息输出带指定输出流;getStackTrace()
:返回该异常的跟踪栈信息。public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("a.txt"); }catch (IOException ioe){ System.out.println(ioe.getMessage()); ioe.printStackTrace(); } }
使用finally回收资源
有些时候,程序在try块里打开一些物理资源(如:数据库链接,网络链接,磁盘文件),这些物理资源都必须显式的回收。
为了保证必定能回收try块中打开的物理资源,异常处理机制提供了finally块。无论try块中的代码是否出现异常,也无论哪个catch块被执行,甚至在try和catch快中执行了return语句,finally块总会被执行。
异常处理语法中只有try块是必须的,catch块和finally块是可选的,二者能够同时出>现,也能够只出现一个,但不能都没有。
public static void main(String[] args) { FileInputStream fis=null; try { fis = new FileInputStream("a.txt"); }catch (IOException ioe){ System.out.println(ioe.getMessage());//a.txt (系统找不到指定的文件。) return; //强制方法返回 //System.exit(1);//退出虚拟机 }finally { if(fis!=null){ try { fis.close(); }catch (IOException ioe){ ioe.printStackTrace(); } } System.out.println("执行finally块里的资源回收");//执行finally块里的资源回收 } }
上面的方法中即便执行了return仍是能够看到finally块中的代码被执行。
可是若是将return注释,而将System.exit(1)打开注释会看到虚拟机直接退出,finally块没有被执行。
注意
若是执行try或catch块遇到return,方法不会直接退出,而是去找是否有finally块,若是有先执行finally块,再返回执行try或catch中的return。若是finally块里还有return则会直接结束方法,就不会返回到try护catch执行return。因此尽可能避免finally块里使用return或throw等致使方法终止的语句,不然会出现一些奇怪的状况。
java7的自动关闭资源的try语句
在前面程序看到当程序使用finally块关闭资源时,程序显得异常臃肿;
java7的出现改变了这种局面,加强了try语句的功能,它容许在try关键字后紧跟一对圆括号,圆括号能够声明、初始化一个或多个资源,此处的资源是指那些在程序结束时显式关闭的资源。
为了保证try语句能够正常的关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口。
public static void main(String[] args) throws IOException { try ( BufferedReader bufferedReader = new BufferedReader(new FileReader("a.txt")); PrintStream printStream = new PrintStream(new FileOutputStream("b.txt")); ){ System.out.println(bufferedReader.readLine()); printStream.println("庄生晓梦迷蝴蝶"); } }
自动关闭资源的try语句至关于包含了隐式的finally块。
Java的异常被分为两大类:Checked(编译时)异常和Runtime(运行时)异常。全部的RuntimeException类及子类的实例被称为Runtime异常;不是RuntimeException类及子类的异常则被称为Checked异常。
对于Checked异常处理的方式有两种:
使用throws声明抛出异常
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;若是main方法也不知道如何处理,则抛给JVM处理。JVM对异常的处理方法是打印跟踪栈信息,并停止程序运行。
throws声明抛出只能在方法签名中使用,throws能够声明抛出多个异常类,由逗号隔开。一旦抛出该异常,就不用使用try…catch来捕获了。
使用throws声明抛出异常有一个限制:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不容许比父类方法声明抛出的异常多。
使用Checked存在的不便之处:
抛出异常
当程序出现错误时,系统会自动抛出异常;除此以外,java也容许程序自行抛出异常,自行抛出异经常使用throw语句来完成注意不是前面的throws,二者有区别。
若是须要在程序中自行抛出异常,则应使用throw语句,throw语句能够单独使用,throw语句抛出的不是异常类,而是一个异常实例,并且每次只能抛出一次异常实例。
不论是系统自动抛出的异常仍是程序员手动抛出的异常,java运行时环境对异常的处理没有任何差异。
public static void main(String[] args) throws Exception { //输入字符串长度不超过5,不然引起异常 Scanner sc = new Scanner(System.in); System.out.print("请输入长度不超过5的字符串:"); String s=sc.next(); if(s.length()<5){ System.out.println(s); }else { throw new Exception("字符串长度超度5"); } }
若是throw语句抛出的异常是Checked异常,则该throw语句要么处于try显式捕获异常,要么放在一个带throws声明抛出的方法中;若是throw语句抛出的异常时Runtime异常,则该语句无需放在try块里,也无需放在throws声明抛出的方法中,程序既能够显式的捕获异常,也能够彻底不理会该异常。
public static void main(String[] args) { try { throwChecked(3); }catch (Exception e){ System.out.println(e.getMessage()); } throwRuntime(3); } public static void throwChecked(int i) throws Exception { if(i>0){ //自行抛出Exception异常 throw new Exception("a的值大于0,不符合要求"); } } public static void throwRuntime(int i){ if(i>0){ //自行抛出RuntimeException异常,既能够显式捕获,也能够彻底不理会,把异常交给该方法的调用者处理 throw new RuntimeException("a的值大于0,不符合要求"); } }
自定义异常类
用户自定义异常类都应该继承Exception基类,若是但愿自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时须要两个构造器:一个是无参构造;另外一个是带一个字符串参数的构造器,这个字符串将做为该异常对象的描述信息。
public class DefineException extends Exception { //无参构造器 public DefineException(){}; //带一个字符串参数的构造器 public DefineException(String str){ super(str); } }
若是须要自定义Runtime异常,只需将继承父类Exception改为RuntimeException便可。
catch和throw同时使用
在实际生活中须要更复杂的处理方法,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理须要在该方法的调用者中才能完成,因此再次抛出异常,让该方法的调用者也能捕获到该异常。
下面的程序中,bid()方法不只try…catch了异常,并且throws抛出了异常,这样main方法(bid方法的调用者)也能够处理该异常,并将该异常的详细描述信息输出到标准错误输出。
package org.westos.demo8; public class ExceptionDemo6 { private double iniePrice=30.0; public void bid(String bidPrice) throws DefineException { double d=0.0; try { d = Double.parseDouble(bidPrice); }catch (Exception e){ //此处完成本方法中能够对异常执行的修复处理 //此处仅仅是在控制台打印异常的跟踪信息 e.printStackTrace(); //再次抛出自定义异常 throw new DefineException("竞拍价必须是数值,不能包含其余字符"); } if(iniePrice>d){ throw new DefineException("竞拍价比起拍价低,不运行竞拍"); } iniePrice=d; } public static void main(String[] args) { ExceptionDemo6 ex = new ExceptionDemo6(); try { ex.bid("df"); }catch (DefineException de){ //再次捕获到bid方法中的异常,并对该异常进行处理 System.out.println(de.getMessage()); } } }
java7加强的throw语句
在java7以前,像下面的程序,匹配到的异常是Exception,则抛出的异常也是Exception,可是在java7以后编译器会检查throw语句抛出异常的实际类型,因此抛出具体异常 FileNotFoundException。
public static void main(String[] args) throws FileNotFoundException { try { new FileOutputStream("a.txt"); }catch (Exception e){ e.printStackTrace(); throw e; } }
异常链
对于真正的企业级应用而言,经常有严格的分层关系,层与层之间有很是清晰地划分,上层功能的实现严格依赖下层的API,也不会跨层访问。
当业务逻辑访问持久层出现SQLException异常时,程序不该该把底层的SQLException异常传到用户界面。缘由以下:
把底层异常暴露给用户是不负责任的表现,一般的作法是:程序先捕获异常,而后抛出一个新的业务异常,新的业务异常包含了对用户的提示信息,这种处理方式称为异常转译。
这种把原始信息隐藏起来,仅向上提供必要的异常提示信息的处理方式,能够保证底层异常不会扩散到表现层,避免向上暴露太多实现细节。这种把捕获一个异常而后接着抛出另外一个异常,并把原始异常信息保存下来是一种典型的链式处理,也被称为异常链。
从java1.4以后全部Throwable的子类在构造器中均可以接受一个cause对象做为参数。这个cause就用来表示原始异常,这样能够把原始异常传递给新的异常,使得即便在当前位置建立并抛出新的异常,你也能经过这个异常链追踪到异常最初发生的位置。
成功的异常处理应该实现以下4个目标:
不要过分使用异常
主要体如今两个方面:
不要使用过于庞大的try块
由于try块里的代码过于庞大,业务过于复杂,就会形成try块中出现异常的可能性大大增长,从而致使分析异常缘由的难度也增长。正确的作法是把大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,分别捕获异常并处理。
避免使用Catch All语句
Catch All语句是指一种异常捕获模块,它能够处理程序发生的全部可能异常。
这种处理方式的不足之处:
不要忽略捕获到的异常
既然捕获到了异常,就要进行处理,不然程序除了错误全部人看不到任何异常。
应采起适当措施: