java异常机制

来源:互联网 发布:崩坏学园淘宝店名 编辑:程序博客网 时间:2024/05/21 08:44

一.java异常的相关概念
1.异常定义:异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时是可以避免的

2.java中的异常是用对象来表示的

3.异常是基于方法的,throw,throws,try…catch…finally都是在方法中进行的

4.异常对象的生成:

  • Java运行时环境自动抛出系统生成的异常
  • 程序员手动抛出的异常,这个异常可以是自定义的,也可以是Java自带的,用throw 关键字抛出异常,这种异常常用来向调用者汇报异常的一些信息。

二.异常体系结构
通常,Java的异常(包括Exception和Error)分为受检查的异常(checked exceptions)和不检查的异常(unchecked exceptions)。
Exception 这种异常分两大类,运行时异常和非运行时异常(编译异常)
异常体系结构

  • Checked Exception(受检查异常,编译器要求必须处置的异常):可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
  • Unchecked Exception(不检查异常,编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

1.Throwable:Throwable类是Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类才可以是 catch 子句中的参数类型。

2.Error(编译器不会检查Error是否被处理,是不检查异常):Error类及其子类是Throwable 的另一种形式,表示仅靠程序本身无法恢复的严重错误,大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题,通常是由Java虚拟机抛出的,JDK中与定义了一些错误类,比如VirtualMachineError和OutOfMemoryErrorJava。编译器不会检查Error,当程序运行时出现Error时,程序会终止运行。

3.Exception:Exception类及其子类是 Throwable 的一种形式,表示程序本身可以处理的异常

4.运行时异常(编译器不会检查RuntimeException是否被处理,是不检查异常):RuntimeException类及其子类都被称为运行时异常.运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误的操作。当程序运行时出现运行时异常时,程序会终止运行。这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try…catch语句捕获它,也没有用throws子句声明抛出它,还是会编译通过,因为它会自动被java虚拟机抛出。例如,当除数为零时,就会抛出java.lang.ArithmeticException异常。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生.
这里举例说明:当我们在写某个方法的时候,可能会偶然遇到某个错误,我们认为这个问题是运行时可能会发生的,并且理论上讲,如果没有这个问题的话,程序将会正常执行的时候,它不强制要求调用者一定要捕获这个异常,此时抛出RuntimeException异常

5.非运行时异常(编译异常)(编译器会检查此类异常是否被处理,是检查异常):非运行时异常表示程序可以处理的异常。如果抛出异常的方法本身不处理或者不能处理它,那么方法的调用者就必须去处理该异常,否则调用会出错,连编译也无法通过。除了RuntimeException类及其子类外,其他的Exception类及其子类都属于非运行时异常,这种异常的特点是要么用try…catch捕获处理,要么用throws语句声明抛出,否则编译不会通过。

总结:为什么抛出的异常一定是已检查异常?RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改;只有已检查异常才是程序员所关心的,程序应该且仅应该抛出或处理已检查异常。而已检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数;客户程序员自己使用throw语句抛出异常。


三.异常处理方法

  • 能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。
  • Java方法在运行过程中出现异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统试图寻找匹配的catch子句以捕获异常。若有匹配的catch子句,则运行其异常处理代码,try-catch语句结束。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被中止。
  • 匹配的原则是:如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配。
  • 异常处理原则:捕捉并处理哪些知道如何处理的异常,而传递哪些不知道如何处理的异常

1.抛出异常(throws/throw关键字): 如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常,然后在方法体内用throw子句来抛出异常
当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
(1)格式

public void test() throws MyException{    //语句    throw new MyException;}
public void test() throws RemoteException,InsufficientFundsException{       //语句}

可以抛出一个或多个异常一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。
(2)throws和throw的区别

  • throws:throws总是出现在函数声明中,用在方法定义时声明该方法要抛出的异常类型.可以声明方法将抛出异常,实际上却不抛出.
  • throw:throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到.它不可以单独使用,要么与try…catch配套使用,要么与throws配套使用。

(3)流程说明:
如果每个方法都是抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往上层查找(就是查找调用者),直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块,将按照下面的步骤处理:

  • 第一步:调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。
  • 第二步:如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。

通过分析思考可以看出,越早处理异常消耗的资源和时间越小,产生影响的范围也越小。因此,不要把自己能处理的异常也抛给调用者。
(4)方法重写中声明异常的注意事项:

  • 如果父类方法中没有用throws声明异常,子类中对应的方法也不能声明异常
  • 一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里(通俗的说就是,如果父类方法中用throws声明了异常,子类的该方法中可以声明也可以不声明)
  • 如果将一个类向上转型为基类型,那么编译器会要求你捕获基类的异常.
  • 子类对应方法不可抛出父类方法异常类的上层类(即子类声明的异常范围不能超过父类声明的范围)
  • 抛出的异常类型的数目不可以比原有方法抛出的还多(不是指个数,指的是类型)
  • 异常说明不属于方法类型的一部分,方法类型是由方法名和参数类型组成的,因此不能基于异常说明来重载方法(也就是说,不能因为抛出异常不同或者有无抛出异常来判断是否是重载的方法,重载只看方法名和参数类型)
  • throw语句后不允许有紧跟其他语句,因为这些没有机会执行。
  • 如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。
  • 从方法中抛出的任何异常都必须使用throws关键字声明
  • 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。

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

try{    //语句1    //语句2    //语句3}catch(Exception e){    //语句4}finally{    //语句5}

Catch语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try后面的catch块就会被检查。如果发生的异常包含在catch块中,异常会被传递到该catch块,这和传递一个参数到方法是一样。

try{    // 程序代码 }catch(异常类型1 异常的变量名1){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }catch(异常类型2 异常的变量名2){    // 程序代码 }finally{    //语句5}

可以在try语句后面添加任意数量的catch块。如果保护代码中发生异常,异常被抛给第一个catch块。如果抛出异常的数据类型与ExceptionType1匹配,它在这里就会被捕获。如果不匹配,它会被传递给第二个catch块。直到异常被捕获或者通过所有的catch块。
(2)注意事项:

  • try、catch、finally这三个关键字均不能单独使用.可以和catch、finally组成 try…catch…finally、try…catch、try…finally三种结构,catch语句可以有一个或多个,finally语句最多一个
  • 当异常处理的代码(catch语句块的代码)执行结束以后,不会回到try语句去执行尚未执行的代码
  • try、catch、finally三个代码块中变量的作用域分别独立,所以不能相互访问。如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
  • 多个catch块时候,按照语句顺序从上往下执行,一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。
  • 对于有多个catch子句的异常程序而言,程序会从上到下按照语句顺序捕捉,因此catch语句块在顺序安排上需注意.越是顶层的异常类,放在越下面捕捉,再不然就直接把多余的catch省略掉(放反了编译也不会通过)
  • finally语句块中的代码不管是否发生异常,一定会执行.通常在finally中关闭程序块已打开的资源,比如:文件流,释放数据库连接等.
  • finally语句先于return语句执行,不要在finally块中使用return语句,没有意义而且容易导致错误。
  • try,catch,finally,return执行顺序:①执行try,catch,给返回值赋值–>②执行finally–>③执行return
  • 派生类的异常对象也可以匹配其基类的处理程序.

示例:

public class ExceptionTest {    public static void main(String[] args) {        String str = new ExceptionTest().openFile();        System.out.println(str);    }    String openFile(){        try {            System.out.println("aaa");            FileInputStream fis = new FileInputStream("d:/a.txt");            int a = fis.read();            System.out.println("bbb");            return "step1";        } catch (FileNotFoundException e) {            System.out.println("catching!!!");            e.printStackTrace();            return "step2";        } catch (IOException e) {            e.printStackTrace();            return "step3";        }finally{            System.out.println("finally!!!");            //return "fff";     //不要在finally中使用return        }    }}

输出结果(当a.txt文件存在时):

finally中return语句注释掉了 finally中return语句没注释掉 aaa aaa bbb bbb finally!!! finally!!! step1 fff

输出结果(当a.txt文件不存在时):

finally中return语句注释掉了 finally中return语句没注释掉 aaa aaa catch!!! catch!!! 输出异常信息 输出异常信息 finally!!! finally!!! step2 fff

四.自定义异常
在Java中可以自定义异常。编写自己的异常类时需要记住下面的几点。

  • 所有异常都必须是Throwable的子类。
  • 习惯上,定义的类应该包含2个构造器:一个是默认的构造器,另一个是带有详细信息的构造器
  • 如果想写一个检查性异常类,一般继承Exception类。
  • 如果想写一个运行时异常类,一般继承RuntimeException 类。

例如:

public class MyException extends Exception{    public MyException(){}    public MyException(String smg){        super(smg);    }}

五.其他相关内容
1.异常最佳解决方案:

  • 运行时异常:我们不要用try…catch来捕获处理,而是在程序开发调试阶段,尽量去避免这种异常,一旦发现该异常,正确的做法就会改进程序设计的代码和实现方式,修改程序中的错误,从而避免这种异常。捕获并处理运行时异常是好的解决办法,因为可以通过改进代码实现来避免该种异常的发生。
  • 被检查异常:按照异常处理的方法去处理,要么用try…catch捕获并解决,要么用throws抛出!
  • Error(运行时错误):不需要在程序中做任何处理,出现问题后,应该在程序在外的地方找问题,然后解决。

2.异常转型:捕获到异常后,将异常以新的类型的异常再抛出,这样做一般为了异常的信息更直观
例如:

public void test2() throws MyException{    ...    try{        ...    }catch(SQLException e){        ...        throw new MyException();    }}

3.异常链
(1)基本概念:在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链.通俗的说,异常链就是把原始的异常包装为新的异常类,并在新的异常类中封装了原始异常类,这样做的目的在于找到异常的根本原因。
(2)在JDK1.4以后版本中,Throwable支持异常链机制。Throwable的子类在构造器中都可以接受一个 cause(原因,就是另一个导致此 throwable 抛出的 throwable)对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,是的及时在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置.
在Throwable的子类中,只有三种基本的异常类,提供了带cause参数的构造器,它们是Error,Exception,RuntimeException。如果要把其他类型的异常链接起来,要使用initCause()方法而不是构造器.
(3)用代码说明:

public class TestException {    public static void main(String[] args){        TestException testException = new TestException();        try {            testException.c();        } catch (CException e) {            e.printStackTrace();        }    }    public void a() throws AException{        AException aException = new AException("this is a exception");        throw  aException;    }    public void b() throws BException{        try {            a();        } catch (AException e) {            throw new BException("this is b exception");        }    }    public void c() throws CException{        try {            b();        } catch (BException e) {            throw new CException("this is c exception");        }    }}class AException extends Exception{    public AException(String msg){        super(msg);    }}class BException extends Exception{    public BException(String msg){        super(msg);    }}class CException extends Exception{    public CException(String msg){        super(msg);    }}

创建了三个异常类AException、BException、CException,然后在a()中抛出AException,在b()中捕获AException并抛出BException,最后在c()中捕获BException并抛出CException,结果打印如下:

CException: this is c exception    at TestException.c(TestException.java:31)    at TestException.main(TestException.java:8)

我们只看到了CException的信息,AException,BException的异常信息已丢失,这时候异常链的作用就出来了,看代码:

public class TestException {    public static void main(String[] args){        TestException testException = new TestException();        try {            testException.c();        } catch (CException e) {            e.printStackTrace();        }    }    public void a() throws AException{        AException aException = new AException("this is a exception");        throw  aException;    }    public void b() throws BException{        try {            a();        } catch (AException e) {//            throw new BException("this is b exception");            BException bException = new BException("this is b exception");            bException.initCause(e);            throw bException;        }    }    public void c() throws CException{        try {            b();        } catch (BException e) {//            throw new CException("this is c exception");            CException cException = new CException("this is c exception");            cException.initCause(e);            throw cException;        }    }}class AException extends Exception{    public AException(String msg){        super(msg);    }}class BException extends Exception{    public BException(String msg){        super(msg);    }}class CException extends Exception{    public CException(String msg){        super(msg);    }}

我们用initCause()方法将异常信息给串联了起来,结果如下:

CException: this is c exception    at TestException.c(TestException.java:35)    at TestException.main(TestException.java:8)    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)    at java.lang.reflect.Method.invoke(Method.java:606)    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)Caused by: BException: this is b exception    at TestException.b(TestException.java:24)    at TestException.c(TestException.java:32)    ... 6 moreCaused by: AException: this is a exception    at TestException.a(TestException.java:15)    at TestException.b(TestException.java:21)    ... 7 moreProcess finished with exit code 0

4.java异常处理的原则和技巧
(1)细化异常的类型,不要不管什么类型的异常都写成Excetpion
(2)catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获之后要么处理,要么转译,要么重新抛出新类型的异常。
(3)不要把自己能处理的异常抛给别人
(4)不要用try…catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况。
(5)Throwable类中的常用方法
catch关键字后面括号中的Exception类型的参数e。Exception就是try代码块传递给catch代码块的变量类型(就是一个异常对象),e是变量名。catch代码块中语句”e.getMessage();”用于输出错误性质。通常异常处理常用3个函数来获取异常的有关信息:

  • getCause():返回抛出异常的原因。如果 cause (cause是产生异常的原因,通常产生异常的原因是另一个异常,所以cause也是一个异常)不存在或未知,则返回 null。
  • getMeage():返回异常的消息信息。
  • printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。

(6)一个例题:下面的代码打印结果是什么

public class TestException {      public TestException() {      }      boolean testEx() throws Exception {          boolean ret = true;          try {              ret = testEx1();          } catch (Exception e) {              System.out.println("testEx, catch exception");              ret = false;              throw e;          } finally {              System.out.println("testEx, finally; return value=" + ret);              return ret;          }      }      boolean testEx1() throws Exception {          boolean ret = true;          try {              ret = testEx2();              if (!ret) {                  return false;              }              System.out.println("testEx1, at the end of try");              return ret;          } catch (Exception e) {              System.out.println("testEx1, catch exception");              ret = false;              throw e;          } finally {              System.out.println("testEx1, finally; return value=" + ret);              return ret;          }      }      boolean testEx2() throws Exception {          boolean ret = true;          try {              int b = 12;              int c;              for (int i = 2; i >= -2; i--) {                  c = b / i;                  System.out.println("i=" + i);              }              return true;          } catch (Exception e) {              System.out.println("testEx2, catch exception");              ret = false;              throw e;          } finally {              System.out.println("testEx2, finally; return value=" + ret);              return ret;          }      }      public static void main(String[] args) {          TestException testException1 = new TestException();          try {              testException1.testEx();          } catch (Exception e) {              e.printStackTrace();          }      }  }

输出结果:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false
结果分析:
这个问题我做错了,还不知道答案,也找不到答案,等大神来解答.

我的错误答案是:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false

0 0