java之异常详解

一、什么是异常?

异常就是有异于常态,和正常情况不一样,有错误出错。在java中,阻止当前方法或作用域正常运行的情况,称之为异常。

 

二、异常体系

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。

Throwable分成了两个不同的分支:

Error:它表示不希望被程序捕获或者是程序无法处理的错误。Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。

Exception:它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。

Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

 

根据Javac对异常的处理要求,将异常类分为2类:

非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常,对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。常见的RuntimeException :

ArrayIndexOutOfBoundsException(数组下标越界)

NullPointerException(空指针异常)

ArithmeticException(算术异常)

MissingResourceException(丢失资源)

ClassNotFoundException(找不到类)

 

检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try catch finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等

 如果只考虑Exception,则非检查异常(unckecked exception)等同于RuntimeException,检查异常等同于非运行时异常。

 

三、异常处理

异常的处理机制分为抛出异常和捕获异常

抛出异常:在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境中跳出,并把问题提交给上一级环境,这就是抛出异常时所发生的事情。抛出异常后,会有几件事随之发生。首先,是像创建普通的java对象一样将使用new在堆上创建一个异常对象;然后,当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序,这个恰当的地方就是异常处理程序或者异常处理器,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。

 

捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

 

Java异常处理涉及到五个关键字,分别是:try、catch、finally、throw、throws:

   try     -- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。

  catch   -- 用于捕获异常。catch用来捕获try语句块中发生的异常。

  finally  -- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。

  throw   -- 用于抛出异常。

       throws -- 用在方法签名中,用于声明该方法可能抛出的异常。

 

捕获异常:try catch / try catch finally

1、try-catch语句

try {
                  //可能产生的异常的代码区,也成为监控区
        }catch (ExceptionType1 e) {
                  //捕获并处理try抛出异常类型为ExceptionType1的异常
        }catch(ExceptionType2 e) {
                  //捕获并处理try抛出异常类型为ExceptionType2的异常
     }

2、try-catch-finally

try {
       //可能产生的异常的代码区
   }catch (ExceptionType1 e) {
       //捕获并处理try抛出异常类型为ExceptionType1的异常
   }catch (ExceptionType2 e){
       //捕获并处理try抛出异常类型为ExceptionType2的异常
   }finally{
       //无论是出现异常,finally块中的代码都将被执行
   }

3、try-catch-finally代码块的执行顺序:

      A)try没有捕获异常时,try代码块中的语句依次被执行,跳过catch。如果存在finally则执行finally代码块,否则执行后续代码。

      B)try捕获到异常时,如果没有与之匹配的catch子句,则该异常交给JVM处理。如果存在finally,则其中的代码仍然被执行,但是finally之后的代码不会被执行。

      C)try捕获到异常时,如果存在与之匹配的catch,则跳到该catch代码块执行处理。如果存在finally则执行finally代码块,执行完finally代码块之后继续执行后续代码;否则直接执行后续代码。另外注意,try代码块出现异常之后的代码不会被执行。

 

注意点:

1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。

2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。

3、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行

 

小结:

  1、不管有木有出现异常或者try和catch中有返回值return,finally块中代码都会执行;

  2、finally中最好不要包含return,否则程序会提前退出,返回会覆盖try或catch中保存的返回值。

  3.  e.printStackTrace()可以输出异常信息。

  4.  return值为-1为抛出异常的习惯写法。

  5.  如果方法中try,catch,finally中没有返回语句,则会调用这三个语句块之外的return结果。

       6.  finally 在try中的return之后 在返回主调函数之前执行。

 

接下来看两段有意思的代码:

代码一:

public class TestFinally {
    public static void main(String[] args){
        int[] a = test1();
        System.out.println(a[0]);
    }
    public static int[] test1(){
        int[] a = new int[10];
        a[0]=1;
        try{
            a[0]=2;
            return a;
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            a[0] =3;
            System.out.println("执行了finally");
        }
        return a;
    }
}

代码二:

public class TestFinally {
    public static void main(String[] args){
        System.out.println(test1());
    }
    public static int test1(){
        int i= 1;
        try{
            i=2;
            return i;
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            i =3;
            System.out.println("执行了finally");
        }
        return i;
    }
}

为什么第一段代码中的finally覆盖了try中的return,而第二段代码就没有覆盖呢?

在debug的调试过程中,try中的return语句执行了两次,在try中第一次执行到return语句时,不会真正的return,即只是会计算return中的表达式,之后将结果保存在一个临时栈中,接着执行finally中的语句,最后才会从临时栈中取出之前的结果返回。所以在第一段代码中,保存的数组的地址,我们在finally中我们对其的内容修改,其实还是返回保存在临时栈中的地址,只是地址指向堆中的值改变了,而在第二段代码中,把i=2存在临时栈中,当我们执行完finally的时候,直接把保存的值i=2直接返回了。

有了这些认识之后,我们讨论一下try,catch,finally中有return语句的几种情况。

第一种:try{}catch(){}finally{}return;

该情况语句后顺序执行。(不考虑异常)

第二种:try{return;}catch(){}finally{}return;

该情况为刚才说的题目情况,即执行完try语句块,将return的值保存在临时栈中,再执行finally语句块,之后返回临时栈中的值。

第三种:try{}catch(){return;}finally{}return;

无异常:执行try,执行finally,再执行return;

有异常:执行完catch语句块,将return的值保存在临时栈中,再执行finally语句块,之后返回临时栈中的值。

第四种:try{}catch(){}finally{return;}

执行finally中的return语句。

第五种:try{return;}catch(){return;}finally{};

根据有无异常执行和情况二或情况三。

第六种:try{return;}catch(){}finally{return;}

执行完try语句块,将return的值保存在临时栈中,再执行finally语句块,因为finally中有return,所以返回finally中的return值。

第七种:try{}catch(){return;}finally{return;}

执行完catch语句块,将return的值保存在临时栈中,再执行finally语句块,因为finally中有return,所以返回finally中的return值。

第八种:try{return;}catch(){return;}finally{return;}

有异常:执行情况七。

无异常:执行情况六。

 

抛出异常:throw/throws

 

throws 函数声明

声明将要抛出何种类型的异常(声明)。

throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。

throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。

采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。

public void 方法名(参数列表)
    throws 异常列表{
 //调用会抛出异常的方法或者:
 throw new Exception();
}

 

throw ----将产生的异常抛出,是抛出异常的一个动作

一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。

程序执行完throw语句之后立即停止;throw后面的任何语句不被执行,

public static void main(String[] args) { 
     String s = "abc"; 
     if(s.equals("abc")) { 
       throw new NumberFormatException(); 
     } else { 
       System.out.println(s); 
     } 
     //function(); 
}

throw与throws的比较
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

 

使用throw和throws关键字需要注意以下几点:

1.throws的异常列表可以是抛出一条异常,也可以是抛出多条异常,每个类型的异常中间用逗号隔开

2.方法体中调用会抛出异常的方法或者是先抛出一个异常:用throw new Exception() throw写在方法体里,表示“抛出异常”这个动作。

3.如果某个方法调用了抛出异常的方法,那么必须添加try catch语句去尝试捕获这种异常, 或者添加声明,将异常抛出给更上一层的调用者进行处理

 

四、自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。

在程序中使用自定义异常类,大体可分为以下几个步骤:

1、创建自定义异常类。

2、在方法中通过throw关键字抛出异常对象。

3、如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。

4、在出现异常方法的调用者中捕获并处理异常。

举例自定义异常:

class MyException extends Exception {
    private int detail;
    MyException(int a){
        detail = a;
    }
    public String toString(){
        return "MyException ["+ detail + "]";
    }
}
public class TestMyException{
    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 me){
            System.out.println("Caught " + me);
        }
    }
}

参考:

https://blog.csdn.net/qq_30816657/article/details/80297646

https://blog.csdn.net/zhanaolu4821/article/details/81012382

https://www.cnblogs.com/hysum/p/7112011.html

https://blog.csdn.net/yongyuai/article/details/79752608

https://blog.csdn.net/ShyTan/article/details/81434219

posted @ 2019-07-02 14:27  一寸HUI  阅读(2192)  评论(0编辑  收藏  举报