java中的异常处理机制

异常概述
异常处理已经成为衡量一门语言是否标准的标准之一。增长了异常处理机制后的程序有更好的容错性。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块能够捕获多种类型的异常。
规则简述:

  • 捕获多种类型的异常时,多种异常类型之间用"|"隔开;
  • 捕获多类型的异常时,异常变量之间有隐式的final修饰,所以程序不能对异常变量从新赋值。
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块。

Checked异常和Runtime异常体系

Java的异常被分为两大类:Checked(编译时)异常和Runtime(运行时)异常。全部的RuntimeException类及子类的实例被称为Runtime异常;不是RuntimeException类及子类的异常则被称为Checked异常。
对于Checked异常处理的方式有两种:

  • 当前方法明确知道如何处理异常,程序应该使用try…catch块来捕获该异常,而后在对应的catch块中修复该异常。
  • 当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。

使用throws声明抛出异常

使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;若是main方法也不知道如何处理,则抛给JVM处理。JVM对异常的处理方法是打印跟踪栈信息,并停止程序运行。
throws声明抛出只能在方法签名中使用,throws能够声明抛出多个异常类,由逗号隔开。一旦抛出该异常,就不用使用try…catch来捕获了。

使用throws声明抛出异常有一个限制:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法声明抛出的异常不容许比父类方法声明抛出的异常多。

使用Checked存在的不便之处:

  • 对于程序中的Checked异常,java要求必须显式捕获并处理该异常,或者显式声明抛出该异常,这样增长了编程复杂度;
  • 若是在方法显式声明抛出Checked异常,将会致使方法签名与异常耦合,若是该方法重写了父类的方法,则抛出的异常还会受到被重写方法所抛出异常的限制。

使用throw抛出异常

抛出异常

当程序出现错误时,系统会自动抛出异常;除此以外,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异常传到用户界面。缘由以下:

  • SQLException异常对用户而言毫无帮助;
  • 异常会暴露隐私安全信息;

把底层异常暴露给用户是不负责任的表现,一般的作法是:程序先捕获异常,而后抛出一个新的业务异常,新的业务异常包含了对用户的提示信息,这种处理方式称为异常转译
这种把原始信息隐藏起来,仅向上提供必要的异常提示信息的处理方式,能够保证底层异常不会扩散到表现层,避免向上暴露太多实现细节。这种把捕获一个异常而后接着抛出另外一个异常,并把原始异常信息保存下来是一种典型的链式处理,也被称为异常链
从java1.4以后全部Throwable的子类在构造器中均可以接受一个cause对象做为参数。这个cause就用来表示原始异常,这样能够把原始异常传递给新的异常,使得即便在当前位置建立并抛出新的异常,你也能经过这个异常链追踪到异常最初发生的位置。

异常处理规则

成功的异常处理应该实现以下4个目标:

  • 使程序代码混乱最小化;
  • 捕获并保留诊断信息;
  • 通知合适的人员;
  • 采用合适的方式结束异常活动。

不要过分使用异常
主要体如今两个方面:

  • 把异常和普通错误混淆在一块儿,再也不写任何错误处理代码,而是以简单地抛出异常来代替全部错误处理;
  • 使用异常来代替流程控制。
    异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,所以毫不要使用异常处理来替换正常的业务逻辑判断

不要使用过于庞大的try块

由于try块里的代码过于庞大,业务过于复杂,就会形成try块中出现异常的可能性大大增长,从而致使分析异常缘由的难度也增长。正确的作法是把大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,分别捕获异常并处理。

避免使用Catch All语句
Catch All语句是指一种异常捕获模块,它能够处理程序发生的全部可能异常。
这种处理方式的不足之处:

  • 全部异常都采用相同的处理方式,这将致使没法对不一样的异常分状况处理,若是要分就得在catch块中使用分支语句,这是得不偿失的。
  • 这种捕获方式可能将程序中的错误、Runtime异常等可能致使程序终止的状况所有捕获到,从而压制了异常。

不要忽略捕获到的异常

既然捕获到了异常,就要进行处理,不然程序除了错误全部人看不到任何异常。
应采起适当措施:

  • 处理异常,对异常进行合适的修复,而后绕过异常发生的地方继续执行。
  • 从新抛出异常,把当前运行环境下能作的事情尽可能作完,而后进行异常转译,把异常包装成当前层的异常,从新抛出给上层调用者。
  • 在合适的层处理异常。