Java-异常体系

Java-异常体系

sschrodinger

2019/03/08


基于 JAVA API 11

参考 关于 Java 中 finally 语句块的深度辨析

参考 JLS 标准

参考 JVM 标准


异常体系的分类


在 Java 异常体系中,所有的异常都继承自 Throwable 类,具体又分为异常错误两种异常。

错误是继承了 Throwable 类的 Error 类及其子类的所有类,比较典型的是 OutOfMemoryErrorStackOverflowError 两种错误。所有的错误都会被 Java 虚拟机捕获,表示的是不可挽回的错误,程序员没有方法对这些错误进行处理。

异常是继承了 Throwable 类的 Exception 类及其子类的所有类,又分为检查型异常和非检查型异常,检查型异常(Checked Exception)指的是需要程序员对此异常进行处理,包括捕捉或抛出错误,非检查型异常(Unchecked Exception)指的是不需要程序员对此异常进行处理。

JVM 和 Java 编译器会对异常进行检查,所有RuntimeException 类都是属于非检查型异常,包括RuntimeExceptu+ionNullPointerException 等,其他的都属于检查型异常。


异常的使用


对于检查型异常,必须对其进行捕获或抛出到下一级函数,抛出用关键词 throws 申明,捕获使用 try{}catch{}finally{} 语句块申明。

代码如下:

public class Demo() {
    
    public function_1() {
        try {
            //使用 try catch 捕获错误
            function_2();
        } catch(IOException e) {
            
        } finally {
            //可选项
        }
    }
    //如果不捕获,则函数申明必须抛出错误
    public function_2() throws IOException {
        function_3();
    }
    
    //如果不捕获,则函数申明必须抛出错误
    public finction_3() throws IOException {
    
        //抛出一个非检查异常
        throw new IOException();
    }
    
}

对于检查型异常,如果没有抛出或者捕获错误,编译器会报错,但是对于非检查型错误,编译器不强制要求进行捕捉或者抛出,如下:

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Test tset = new Test();
        tset.testFunc();
    }
    
    public void testFunc() {
        //也可进行捕获
        try {
            testFunc1();
        } catch (DemoUnCheckedException e) {
            // TODO: handle exception
            System.out.println(e.getClass().getName());
        }
    }
    
    //在此处可以不进行捕获或者抛出
    public void testFunc1() {
        testFunc2();
    }
    
    public void testFunc2() {
        throw new DemoUnCheckedException();
    }
}

class DemoUnCheckedException extends RuntimeException {
    
}

try-catch-finally 语句块


问题一:当try中有return语句时,finally语句是否会运行?

答案是会。

oracle的官方文档解答了这个问题。

the finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handing - it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in finally blockis always a good practice, even when no exception are anticipated.

Note

  • If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, If the thread executing the try or catch code is interruped or killed, the finally block may not execute even though the application as a whole continues.

原文翻译如下:当try语句退出时肯定会执行finally语句。这确保了即使是发生了一个意想不到的异常也会执行finally语句块。但是finally的用处不仅是用来处理异常-它可以让程序员不会因为return、continue或者break语句而忽略了清理代码的工作。把清理代码放在finally语句块中是一个很好的做法,即使可能不会有异常发生也要这么做。

问题二:finally语句块执行的时机?

try-catch 语句块中,没有流程跳转指令时,如没有 returnbreakcontinue时,finally语句块的执行会紧跟在 try-catch 之后,但是当 try-catch 中有流程跳转指令时,finally 语句块的执行顺序就变得很复杂。

首先来看一个例子:

public class Test { 
    public static void main(String[] args) {  
        testFunc();
        System.out.println("last function");
    } 
    
    public static void testFunc() {
        try {  
            System.out.println("try block");  
            return;
        } finally {  
            System.out.println("finally block");  
        }  
    }
}
//output:
//try block
//finally block
//last function

可以看到,finally 语句块在返回之前执行。

更准确的说,finally 语句都是在控制转移语句之前执行。Java 语言规范如下:

JLS

  • Afinallyclause can also be used to clean up forbreak,continue, andreturn, which is one reason you will sometimes see atryclause with nocatchclauses. When any control transfer statement is executed, all relevantfinallyclauses are executed. There is no way to leave atryblock without executing itsfinallyclause.

问题三:当 finally 语句块和 try-catch 语句块都有返回值时,返回值是什么?

返回值为 finally 语句块中的值。

我们看如下代码:

public class Test { 
public static void main(String[] args) { 
       System.out.println("return value of getValue(): " + getValue()); 
    } 
 
public static int getValue() { 
       int i = 1; 
       try { 
                return i; 
       } finally { 
                i++; 
       } 
    } 
}
//output:
//return value of getValue(): 1

原理比较简单,当执行到 return i 时,会将返回值缓存在变量空间中,返回时直接返回缓存的值。

这在 JVM 规范中也有规定,规定原文如下:

JVM Specification

  • If the try clause executes a return, the compiled code dose the folloing:
    • Saves teh return value(if any) in a local variable.
    • Executes a jsr to the code for the finally clause
    • Upon return from the finally clause, returns the value saves in the local variable.

即在第一次执行return语句时,就保存了返回值在本地变量中。

这主要是 Java 编译时实现的功能,看上面示例 class 文件如下:

public class Test extends java.lang.Object{ 
public Test(); 
 Code: 
  0:    aload_0 
  1:invokespecial#1; //Method java/lang/Object."<init>":()V 
  4:    return 
 
 LineNumberTable: 
  line 1: 0 
 
public static void main(java.lang.String[]); 
 Code: 
  0:    getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream; 
  3:    new     #3; //class java/lang/StringBuilder 
  6:    dup 
  7:    invokespecial   #4; //Method java/lang/StringBuilder."<init>":()V 
  10:   ldc     #5; //String return value of getValue(): 
  12:   invokevirtual   
  #6; //Method java/lang/StringBuilder.append:(
      Ljava/lang/String;)Ljava/lang/StringBuilder; 
  15:   invokestatic    #7; //Method getValue:()I 
  18:   invokevirtual   
  #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 
  21:   invokevirtual   
  #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
  24:   invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
  27:   return 
 
public static int getValue(); 
 Code: 
  0:    iconst_1 
  1:    istore_0 
  2:    iload_0 
  3:    istore_1 
  4:    iinc    0, 1 
  7:    iload_1 
  8:    ireturn 
  9:    astore_2 
  10:   iinc    0, 1 
  13:   aload_2 
  14:   athrow 
 Exception table: 
  from   to  target type 
    2     4     9   any 
    9    10     9   any 
}

我们看异常处理表,from to target 的含义时,如果在 formto 之间出现了异常,则从 taget 开始运行。

我们先看正常运行过程,正常程序在 code line 3 时,将 1 加入 正常本地变量缓存表,然后执行 i++ ,即 第 4 行语句,第 5 行语句取出缓存表然后返回。

不正常的运行流程如下,当从 2 到 4 这段指令出现异常时,将会产生一个 exception 对象,并且把它压入当前操作数栈的栈顶。接下来是 astore_2 这条指令,它负责把 exception 对象保存到本地变量表中 2 的位置,然后执行 finally 语句块,待 finally 语句块执行完毕后,再由 aload_2 这条指令把预先存储的 exception 对象恢复到操作数栈中,最后由 athrow 指令将其返回给该方法的调用者(main)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 1 异常的继承体系结构 Throwable 类是 Java 语言中所有错误或异常的超类。 只有当对象是此类(或其子...
    凯玲之恋阅读 20,140评论 5 18
  • 转自:https://blog.csdn.net/liuhenghui5201/article/details/1...
    mayiwoaini阅读 654评论 0 1
  • 首先,需要了解异常体系的结构: 看上面的结构,Throwable是所有异常的基类,有两个子类:Error和Exce...
    _Once1阅读 420评论 0 0
  • Error(错误): 是程序无法处理的错误,表示运行应用程序中较严重问题。例如,Java虚拟机运行错误(Virtu...
    奔跑吧李博阅读 729评论 0 10
  • My Chinese is not very good so send an English one for a ...
    愚公捕鱼阅读 145评论 0 0