关于JAVA异常

参考
https://blog.csdn.net/xialei199023/article/details/63251277
http://www.importnew.com/26613.html

JAVA异常体系

这里写图片描述

  • Throwable类是整个Java异常体系的超类,都有的异常类都是派生自这个类。包含Error和Exception两个直接子类。

  • Exception是应用层面上最顶层的异常类,包含RuntimeException(运行时异常)和 Checked Exception(受检异常)。

  • unckecked exception(非检查异常):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。

    • Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA虚拟机出现错误。在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。

    • RuntimeException是一种Unchecked Exception,即表示javac编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。一般来说,RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。常见的RuntimeException有NullPointException、ClassCastException、IllegalArgumentException、IndexOutOfBoundException等。 .

      • 在定义方法时不需要声明会抛出runtime exception;在调用这个方法时不需要捕获这个runtime exception;runtime exception是从java.lang.RuntimeException或java.lang.Error类衍生出来的。
  • Checked Exception(检查异常):是相对于Unchecked Exception而言的,它是除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。Java中并没有一个名为Checked Exception的类。它是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是Checked Exception。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。常用的Checked Exception有IOException、ClassNotFoundException等。
    • 定义方法时必须声明所有可能会抛出的checked exception;在调用这个方法时,必须捕获它的checked exception,不然就得把它的exception传递下去;checked exception是从java.lang.Exception类衍生出来的。

需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。

认识JAVA异常

下面的代码会演示2个异常类型:ArithmeticException 和 InputMismatchException。前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。

package com.example;
import java. util .Scanner ;
public class AllDemo
{
      public static void main (String [] args )
      {
            System . out. println( "----欢迎使用命令行除法计算器----" ) ;
            CMDCalculate ();
      }
      public static void CMDCalculate ()
      {
            Scanner scan = new Scanner ( System. in );
            int num1 = scan .nextInt () ;
            int num2 = scan .nextInt () ;
            int result = devide (num1 , num2 ) ;
            System . out. println( "result:" + result) ;
            scan .close () ;
      }
      public static int devide (int num1, int num2 ){
            return num1 / num2 ;
      }
}
/*****************************************

----欢迎使用命令行除法计算器----
0
Exception in thread "main" java.lang.ArithmeticException : / by zero
     at com.example.AllDemo.devide( AllDemo.java:30 )
     at com.example.AllDemo.CMDCalculate( AllDemo.java:22 )
     at com.example.AllDemo.main( AllDemo.java:12 )

----欢迎使用命令行除法计算器----
r
Exception in thread "main" java.util.InputMismatchException
     at java.util.Scanner.throwFor( Scanner.java:864 )
     at java.util.Scanner.next( Scanner.java:1485 )
     at java.util.Scanner.nextInt( Scanner.java:2117 )
     at java.util.Scanner.nextInt( Scanner.java:2076 )
     at com.example.AllDemo.CMDCalculate( AllDemo.java:20 )
     at com.example.AllDemo.main( AllDemo.java:12 )

这里写图片描述

异常最先发生的地方,叫做异常抛出点。
从上面的例子可以看出,当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,因此调用他的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
上面的代码不使用异常处理机制,也可以顺利编译,因为2个异常都是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。

代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢?因为FileNotFoundException是IOException的子类,在处理范围内。

@Test
public void testException() throws IOException
{
    //FileInputStream的构造函数会抛出FileNotFoundException
    FileInputStream fileIn = new FileInputStream("E:\\a.txt");

    int word;
    //read方法会抛出IOException
    while((word =  fileIn.read())!=-1) 
    {
        System.out.print((char)word);
    }
    //close方法会抛出IOException
    fileIn.clos
}

异常处理的基本语法

在编写代码处理异常时,对于检查异常,有2种不同的处理方式:使用try…catch…finally语句块处理它。或者,在函数签名中使用throws 声明交给函数调用者caller去解决。

  • try…catch…finally语句块
try{
     //try块中放可能发生异常的代码。
     //如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
     //如果发生异常,则尝试去匹配catch块。

}catch(SQLException SQLexception){
    //每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
    //catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
    //catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
    //如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
    //如果try中没有发生异常,则所有的catch块将被忽略。

}catch(Exception exception){
    //...
}finally{

    //finally块通常是可选的。
   //无论异常是否发生,异常是否匹配被处理,finally都会执行。
   //一个try至少要有一个catch块,否则, 至少要有1finally块。但是finally不是用来处理异常的,finally不会捕获异常。
  //finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。 
}

需要注意的地方

1.try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2.每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
3.每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
4.有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做:resumption model of exception handling(恢复式异常处理模式 )
而Java则是让执行流恢复到处理了异常的catch块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)

1.public static void main(String[] args){
 -        try {
 -            foo();
 -        }catch(ArithmeticException ae) {
 -            System.out.println("处理异常");
 -        }
 - }
 - public static void foo(){
 -        int a = 5/0;  //异常抛出点
 -        System.out.println("为什么还不给我涨工资!!!");  
 - }

异常抛出后,执行流会通过异常的冒泡跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。也就是说执行顺序为3 8 9 4 5 (10未执行)。

  • throws 函数声明
    • throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
    • throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
    • 采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{ 
     //foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
}
  • finally块
    • finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。
    • 良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。
    • 需要注意的地方:
      1、finally块没有处理异常的能力。处理异常的只能是catch块。
      2、在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
      3、在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。

这是正常的情况,但是也有特例。关于finally有很多恶心,偏、怪、难的问题,我在本文最后统一介绍了,电梯速达->:finally块和return

  • throw 异常抛出语句
    • 程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。
    • throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
public void save(User user)
{
      if(user  == null) 
          throw new IllegalArgumentException("User对象为空");
      //......

}

Java中的异常栈轨迹

异常的冒泡上传机制:当一个异常对象产生了以后,其会按照调用层次(一般是方法的调用层次)进行冒泡,直到被try-catch处理,或上报至main()方法,有编译器进行提示。
例子
这里写图片描述

Eclipse提供了两种方式帮助fix这个程序,这两种方式常用的异常应对手段:

1、本方法不给予处理,将异常上报给上一层。

public class TestExceptionChain {   
    TestExceptionChain() throws MyException{
        secondThrow();
    }   
    void firstThrow() throws MyException
    {
          System.out.println("Oringinally creat a MyException and throw it out");
          throw new MyException();        
    }
    void secondThrow() throws MyException
    {
        firstThrow();
    }
    public static void main(String[] args) {
          try{     
              TestExceptionChain testExceptionChain=new TestExceptionChain();
          }
          catch(MyException e)
          {
              e.printStackTrace();
              System.out.println("Catch a myexception!");
          }  
    }    
}

输出为
这里写图片描述

从异常栈的记录信息可以发现,与代码相对应的异常抛出机制和次序:

firstThrow()产生MyException对象->异常冒泡至调用其的secondThrow()->异常冒泡至调用secondThrow()的TestExceptionChain的构造方法->冒泡至printtry的main()方法。

注意到:异常对象一直被抛出,直至在printtry的mian()方法中被try-catch捕获!

2、try-catch方式,捕捉上报的异常,而后进行相应处理或抛出另一异常。

2、1捕获异常后,进行相应处理。

Example:

public class TestExceptionChain {

    TestExceptionChain() throws MyException{
        secondThrow();
    }

    void firstThrow() throws MyException
    {
          System.out.println("Oringinally creat a MyException at firstThrow and throw it out");
          throw new MyException();

    }

    void secondThrow() throws MyException
    {
        try
        {
            firstThrow();
        }
        catch (MyException e)
        {
             System.out.print("I have just caught a MyExcepton at secondThrow,but i want to do nothing for it");
             e.printStackTrace();
        }
    }

    public static void main(String[] args) throws MyException {
                        //此处不声明MyException会报错,不太理解,
              TestExceptionChain testExceptionChain=new TestExceptionChain();

    }    
}

输出
这里写图片描述
从图中可以发现,异常在secondThrow() 中被try-catch模块捕获,并执行了相应的处理操作,所以其函数声明中无需添加异常声明,异常不会被上报。但是我必须要再main里面声明一个myexception否则会报错 不理解

注意此处异常栈的信息,表示的是异常产生的层次信息,并非异常信息的上报层次,因为其已经在secondThorow()中被捕获处理。

2.2 捕获异常后,抛出另一个异常。

public class TestExceptionChain {

    TestExceptionChain() throws MyException,YouException{
        secondThrow();
    }

    void firstThrow() throws MyException
    {
          System.out.println("Oringinally creat a MyException at firstThrow and throw it out");
          throw new MyException();

    }

    void secondThrow() throws MyException,YouException
    {
        try
        {
            firstThrow();
        }
        catch (MyException e)
        {
             System.out.print("I have just caught a MyExcepton at secondThrow,and throw a youexception at second throw");
             e.printStackTrace();
             throw new YouException();
        }
    }

    public static void main(String[] args) throws MyException,YouException {

              TestExceptionChain testExceptionChain=new TestExceptionChain();

    }    
}

输出
这里写图片描述
从异常栈信息中可以发现,新抛出的YouException对象是从secondThrow()中开始的。

JVM捕获并处理未被应用程序捕获的异常
无论是受检异常(Checked Exception)还是运行时异常(Runtime Exception),如果异常没有被应用程序捕获,那么最终这个异常会交由JVM来进行处理,会明显出现下面两个结果:
1. 当前线程会停止运行,异常触发点后面的代码将得不到运行。
2. 异常栈信息会通过标准错误流输出。

public class UncatchedException {

    public static void main(String[] args) throws Exception {
        throwException();
        System.out.println("这一行不会被打印出来");
    }

    public static void throwException() throws Exception {
        int i = 0;
        if (i == 0) {
            throw new Exception();
        }
    }
}

输出
这里写图片描述
与上面的形成对比,由于上面的例子有try catch模块,所以捕捉到了异常,异常栈信息就不会输出,需要你手动输出,即

e.printStackTrace();

才会输出异常栈信息

异常被吃掉

如果在finally中返回值,那么在程序中抛出的异常信息将会被吞噬掉。这是一个非常值得注意的问题,因为异常信息是非常重要的,在出现问题时,我们通常凭它来查找问题。如果编码不小心而导致异常被吞噬,排查起来是相当困难的,这将是一个大隐患。

public class FinallySwallowException {

    public static void main(String[] args) throws Exception {
        System.out.println(swallowException()); // 打印出2,而不是打印出异常栈
    }

    public static int swallowException() throws Exception {
        try {
            throw new Exception();
        } finally {
            return 2;
        }
    }
}

输出 2

重写Exception的fillInStackTrace()方法

使用自定义异常时,可以重写fillInStackTrace()方法来控制Exception的异常栈信息。默认情况下,在程序抛出异常时,最终会通过调用private native Throwable fillInStackTrace(int dummy)这个本地方法来获取当前线程的堆栈信息,这是一个非常耗时的操作。如果我们仅仅需要用到异常的传播性质,而不关系异常的堆栈信息,那么完全可以通过重写fillInStackTrace()方法来实现。

public class MyException extends Exception {

    public MyException(String message) {
        super(message);
    }

    /*
     * 重写fillInStackTrace方法会使得这个自定义的异常不会收集线程的整个异常栈信息,会大大
     * 提高减少异常开销。
     */
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }

    public static void main(String[] args) {
        try {
            throw new MyException("由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。");
        } catch (MyException e) {
            e.printStackTrace(); // 在控制台的打印结果为:demo.blog.java.exception.MyException: 由于MyException重写了fillInStackTrace方法,那么它不会收集线程运行栈信息。
        }
    }
}

关于JAVA异常链

猜你喜欢

转载自blog.csdn.net/weixin_38719347/article/details/80941325