异常老是不可避免的,就算咱们自身的代码足够优秀,但却不能保证用户都按照咱们想法进行输入,就算用户按照咱们的想法进行输入,咱们也不能保证操做系统稳定,另外还有网络环境等,不可控因素太多,异常也不可避免。java
但咱们能够经过异常处理机制让程序有更好的容错性和兼容性,当程序出现异常时,系统自动生成Exception对象通知系统,从而将业务功能实现代码和错误处理代码分离。sql
异常处理已经成为衡量一门语言是否成熟的标志之一,增长了异常处理机制后程序有更好的健壮性和容错性。数据库
安全
网络
try{ //业务代码 } catch(IOException ex){ //错误处理 } catch(Exception ex){ //错误处理代码 }
当try块代码出错时,系统生成一个异常对象,并将对象抛给运行环境,这个过程叫作抛出异常,运行环境接收到异常对象是,会寻找处理该异常对象的catch代码块,找到合适的catch块,就将对象给其处理,若是找不到,则运行环境终止,程序也将退出。性能
Java提供了丰富的异常类,这些异常类有严格的继承关系优化
从这个图能够看出异常主要分为两类,Error与Exception,Error错误通常是指与虚拟机相关的问题,如系统崩溃、虚拟机错误,这些错误没法恢复或不可能捕获,将致使应用程序崩溃,这些不须要咱们去捕获。spa
在捕获异常时咱们一般把Exception类放在最后,由于按照异常捕获的机制,从上至下判断该异常对象是不是catch中的异常类或其异常子类,一旦比较成功则用此catch进行处理。若是将Exception类放在前面,那么就会进行直接进入其中,由于Exception类是全部异常类的父类,那排在它后面的异常类将永远得不到执行的机会,这种机制咱们称为先小后大。操作系统
Java 7 开始,一个catch块中能够捕获多种类型的异常:日志
public static void main(String[] args) { try { Integer a = Integer.parseInt(args[0]); Integer b = Integer.parseInt(args[1]); Integer c = a / b; System.out.println(c); } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) { //异常变量默认final,不能从新赋值 ie = new ArithmeticException("text"); } catch (Exception ex) { } }
多个异常之间用竖线(|)隔开,而且异常变量默认final,不能从新赋值。
异常捕获后咱们想要查看异常信息,能够经过catch后的异常形参来得到,经常使用的方法以下:
getMessage():返回异常的详细描述字符串。
getStackTrace():返回异常跟踪栈信息。
printStackTrace():将异常跟踪栈信息按照标准格式输出。
printStackTrace(PrintStream p):将异常跟踪栈信息输出到指定输出流。
try{ //业务代码 }catch(XXXException xx){ //异常处理 }catch(XXXException xx){ }finally{ //资源回收 }
在异常处理中,try是必须的,没有try块,后面的catch和finally没有意义,catch和finally必须出现一个,finally块必须是最后。
若是try块中有return语句,则会先执行finally,而后再执行return语句,若是try块中有exit语句,则不会执行finally,都直接退出虚拟机了固然不会再去执行。
try ( BufferedReader bufferedReader = new BufferedReader(new FileReader("")) ) { bufferedReader.read(); }
Java的异常分为两大类:Checked(可检查)异常和Runtime(运行时)异常,全部的RuntimeException类及其子类的实例就是Runtime异常,其余的都是Checked异常。
对于Checked异常处理方式有两种,一种是明确知道如何处理该异常,用try catch来捕获异常,而后在catch中修复异常,一种是不知道如何处理,在定义方法时申明抛出异常。
Runtime异常无需显示申明抛出,须要捕获异常,就用try catch来实现。
使用throws声明的思路是:当前方法不知道如何处理这种类型的异常,则由上一级调用者处理,若是main方法也不知道如何处理,也可使用throws抛给JVM,JVM的处理是,打印异常的跟踪栈信息,并终止程序。
throws声明抛出只能在方法签名中使用,能够声明抛出多个异常类,多个异常类用逗号隔开,如:
public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream(""); }
申明了throws就不须要再使用try catch来捕获异常了。
若是某段代码中调用了一个带throws声明的方法,那么必须用try catch来处理或者也带throws声明,以下例子:
public static void main(String[] args) { try { test(); } catch (IOException e) { e.printStackTrace(); } } public static void test () throws IOException{ FileInputStream fileInputStream = new FileInputStream(""); }
这个时候要注意,子类方法声明抛出的异常应该是父类方法声明抛出异常的子类活相同,不容许比父类声明抛出的异常多。
程序出现错误,系统会抛出异常,有时候咱们也想自行抛出异常,好比用户未登陆,咱们可能就直接抛出错误,这种自行抛出的异常通常都与业务相关,由于业务数据与既定不符,可是这种异常并非一种错误,系统不会捕捉,就须要咱们自行抛出。
使用throw语句进行异常抛出,抛出的不是一个异常类,而是一个异常实例,并且每次只能抛出一个:
if (user== null) { throw new Exception("用户不存在"); }
这里咱们又要区分Checked异常与运行时异常,运行时异常申明很是简单,直接抛出便可,而Checked异常又要像以前同样,要么使用try catch,要么声明throws
public static void main(String[] args) { try { //检查时异常须要写try catch test1(); } catch (Exception e) { e.printStackTrace(); } //运行时异常直接调用便可 test2(); } public static void test1() throws Exception { if (1 > 0) { throw new Exception("用户不存在"); } } public static void test2() { if (1 > 0) { throw new RuntimeException("用户不存在"); } }
public class GlobalException extends RuntimeException { //无参构造器 public GlobalException() { } //带有错误描述信息的构造器 public GlobalException(String msg) { super(msg); } }
在实际开发中,咱们通常会分层开发,比较经常使用的是三层,表现出、业务逻辑层、数据库访问层,咱们不会抛出数据库异常给用户,由于这些异常中有堆栈信息,很不安全,也很是的不友好。
一般,咱们捕获原始异常(能够写入日志),而后再抛出一个业务异常(一般是自定义的异常),这个业务异常能够提示用户异常的缘由:
public void update() throws GlobalException{ try{ //执行sql } catch (SQLException ex){ //记录日志 ... //抛出自定义错误 throw new GlobalException("数据库报错"); } catch (Exception ex){ //记录日志 ... throw new GlobalException("未知错误!"); } }
这种捕获一个异常而后抛出另外一个异常,并将原始信息保存起来的是一种典型的链式处理(责任链模式)。
异常给系统带来了健壮性和容错性,可是使用异常处理并不是如此简单,咱们还要注意性能和结构的优化,有些规则咱们必须了解,而这些规则的主要目标是:
程序代码混乱最小化。
捕获并保留诊断信息。
通知合适的人员
采用合适的方式结束异常。
什么叫过分使用异常呢?有两种状况,一是把异常和普通错误放在一块儿,使用异常来代替错误,什么意思呢?就是对一些咱们已知或可控的错误进行异常处理,如一些业务逻辑判断,用户的输入等,并非只有直接抛出异常这种选择,咱们能够直接经过业务处理进行错误返回,而不是抛出错误,抛出错误的效率要低一些,只有对外部的、不能肯定和预知的运行时错误使用异常。
二就是使用异常来代替流程控制,异常处理机制的初衷是将不能够预期的错误和正常的业务代码分离,不该该用异常来进行流程控制。
不要把大量的业务代码放在try中,大量的业务代码意味着错误可能性也增大,也意味着一旦出错,分析错误的复杂度也增长,并且try中包含大量业务,可能后面紧跟的catch块也不少,咱们会使用多个catch来捕获错误,这样代码也很臃肿,应该尽可能细分try,去分别捕获并处理。
不要忽略异常,当咱们捕获到异常时,咱们不要去忽略它,若是在catch中什么也不作,那是一种恐怖的作法,由于这意味着出现了错误咱们并不知道(极特殊的状况例外,好比:一些可重试的业务处理),最起码的作法是打印错误日志,更进一步看是否能够修复错误,或者向上抛出错误。