Java高级主题(二)——异常

一、什么是异常

异常:程序在运行过程中发生由于硬件设备问题、软件设计错误等导致的程序异常事件。(在Java等面向对象的编程语言中)异常本身是一个对象,产生异常就是产生了一个异常对象。

二、异常体系

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。 在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。 其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为非受检查异常(Unchecked Exception)和受检查异常(Checked Exception)。
java异常体系结构:


image.png

1、Error与Exception

Errorr往往是很严重的错误,是程序无法处理的异常,可以捕获但是最好不要捕获,因为捕获了也解决不了,这个不是由程序产出的,底层出现问题就让他它挂了吧,比如内存溢出(OutOfMemoryError)、线程死亡(ThreadDeath)等。这些异常发生时, Java虚拟机(JVM)一般会选择线程终止。
下图为程序模拟的内存溢出情景:


image.png

Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。 程序中应当尽可能去处理这些异常。

2、运行时异常和非运行时异常

运行时异常都是RuntimeException类及其子类异常,常见的异常有:空指针异常(NullPointerException)、下标越界异常(IndexOutOfBoundsException)、算术异常(ArithmaticException)、类型转换异常(ClassCastException) 等,这些异常一般是由程序逻辑错误引起的, 程序应该从逻辑角度尽可能避免这类异常的发生。
Exception 异常主要分为两类

  1. 一类是 IOException(I/O 输入输出异常),其中 IOException 及其子类异常又被称作「受查异常」
  2. 另一类是 RuntimeException(运行时异常),RuntimeException 被称作「非受查异常」。
    常见的非检查性异常
    image.png

    常见的检查性异常
    image.png

受查异常就是指,编译器在编译期间要求必须得到处理的那些异常,你必须在编译期处理了。

作者:凯玲之恋
链接:https://www.jianshu.com/p/49d2c3975c56
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

下面将模拟这几个运行时异常发生的场景:

数组下标越界异常
image.png

以数组下标越界异常为例分析发生的现象,在程序运行时,JVM能够检测到没有a[3]这个索引,当检测到数组下标越界异常后,JVM做了两件事:

  1. 创建出异常对象 new ArrayIndexOutOfBoundsException(3);
  2. 将异常对象抛出,抛给它的调用者。注:一旦异常被抛出,后面的程序就不再执行。

当它的调用者没有对异常进行处理时,它的调用者继续将异常向上抛出。以此类推,当main接收到这个异常也没有对数组下标越界异常进行处理时,main就将异常抛给了JVM,JVM收到异常信息后又做了两件事:

  1. 将异常信息以红色字体输出到控制台上;
  2. 停止程序的运行。
空指针异常
image.png

三、自定义异常类型

Java 的异常机制中所定义的所有异常不可能预见所有可能出现的错误,某些特定的情境下,则需要我们自定义异常类型来向上报告某些错误信息。
在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。
所有异常都必须是 Throwable 的子类。
如果希望写一个检查性异常类,则需要继承 Exception 类。
如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

四、异常的处理方式

1、try...catch关键字

使用 try 和 catch 关键字可以捕获异常。
try/catch 代码块放在异常可能发生的地方。
try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

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

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

2、throws/throw 关键字

如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。
下面方法的声明抛出一个 RemoteException 异常:

public class className {
  public void deposit(double amount) throws RemoteException {
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}

一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

3、finally关键字

finally 关键字用来创建在 try 代码块后面执行的代码块。
无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
finally 代码块出现在 catch 代码块最后,语法如下:

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

4、try-catch-finally 的执行顺序

try-catch-finally 执行顺序的相关问题可以说是各种面试中的「常客」了,尤其是 finally 块中带有 return 语句的情况。我们直接看几道面试题:
面试题一:

public static void main(String[] args){
    int result = test1();
    System.out.println(result);
}

public static int test1(){
    int i = 1;
    try{
        i++;
        System.out.println("try block, i = "+i);
    }catch(Exception e){
        i--;
        System.out.println("catch block i = "+i);
    }finally{
        i = 10;
        System.out.println("finally block i = "+i);
    }
    return i;
}

输出结果如下:

try block, i = 2
finally block i = 10
10

这算一个相当简单的问题了,没有坑,下面我们稍微改动一下:

public static int test2(){
    int i = 1;
    try{
        i++;
        throw new Exception();
    }catch(Exception e){
        i--;
        System.out.println("catch block i = "+i);
    }finally{
        i = 10;
        System.out.println("finally block i = "+i);
    }
    return i;
}

输出结果如下:

catch block i = 1
finally block i = 10
10

面试题二

public static void main(String[] args){
    int result = test3();
    System.out.println(result);
}

public static int test3(){
    //try 语句块中有 return 语句时的整体执行顺序
    int i = 1;
    try{
        i++;
        System.out.println("try block, i = "+i);
        return i;
    }catch(Exception e){
        i ++;
        System.out.println("catch block i = "+i);
        return i;
    }finally{
        i = 10;
        System.out.println("finally block i = "+i);
    }
}

输出结果如下:

try block, i = 2
finally block i = 10
2

是不是有点疑惑?明明我 try 语句块中有 return 语句,可为什么最终还是执行了 finally 块中的代码?

我们反编译这个类,看看这个 test3 方法编译后的字节码的实现:

0: iconst_1         //将 1 加载进操作数栈
1: istore_0         //将操作数栈 0 位置的元素存进局部变量表
2: iinc          0, 1   //将局部变量表 0 位置的元素直接加一(i=2)
5: getstatic     #3     // 5-27 行执行的 println 方法                
8: new           #5                  
11: dup
12: invokespecial #6                                                     
15: ldc           #7 
17: invokevirtual #8                                                     
20: iload_0         
21: invokevirtual #9                                                     24: invokevirtual #10                
27: invokevirtual #11                 
30: iload_0         //将局部变量表 0 位置的元素加载进操作栈(2)
31: istore_1        //把操作栈顶的元素存入局部变量表位置 1 处
32: bipush        10 //加载一个常量到操作栈(10)
34: istore_0        //将 10 存入局部变量表 0 处
35: getstatic     #3  //35-57 行执行 finally中的println方法             
38: new           #5                  
41: dup
42: invokespecial #6                  
45: ldc           #12                 
47: invokevirtual #8                  
50: iload_0
51: invokevirtual #9                
54: invokevirtual #10                 
57: invokevirtual #11                 
60: iload_1         //将局部变量表 1 位置的元素加载进操作栈(2)
61: ireturn         //将操作栈顶元素返回(2)
-------------------try + finally 结束 ------------
------------------下面是 catch + finally,类似的 ------------
62: astore_1
63: iinc          0, 1
.......
.......

从我们的分析中可以看出来,finally 代码块中的内容始终会被执行,无论程序是否出现异常的原因就是,编译器会将 finally 块中的代码复制两份并分别添加在 try 和 catch 的后面。

可能有人会所疑惑,原本我们的 i 就被存储在局部变量表 0 位置,而最后 finally 中的代码也的确将 slot 0 位置填充了数值 10,可为什么最后程序依然返回的数值 2 呢?

仔细看字节码,你会发现在 return 语句返回之前,虚拟机会将待返回的值压入操作数栈,等待返回,即使 finally 语句块对 i 进行了修改,但是待返回的值已经确实的存在于操作数栈中了,所以不会影响程序返回结果。

面试题三

public static int test4(){
    //finally 语句块中有 return 语句
    int i = 1;
    try{
        i++;
        System.out.println("try block, i = "+i);
        return i;
    }catch(Exception e){
        i++;
        System.out.println("catch block i = "+i);
        return i;
    }finally{
        i++;
        System.out.println("finally block i = "+i);
        return i;
    }
}

运行结果:

try block, i = 2
finally block i = 3
3

其实你从它的字节码指令去看整个过程,而不要单单四记它的执行过程。


image.png

你会发现程序最终会采用 finally 代码块中的 return 语句进行返回,而直接忽略 try 语句块中的 return 指令。

最后,对于异常的使用有一个不成文的约定:尽量在某个集中的位置进行统一处理,不要到处的使用 try-catch,否则会使得代码结构混乱不堪。

参考:
https://www.cnblogs.com/williamjie/p/9103658.html
https://www.jianshu.com/p/49d2c3975c56
https://www.xuebuyuan.com/3248044.html
https://www.jb51.net/article/128611.htm

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

推荐阅读更多精彩内容

  • 八、深入理解java异常处理机制 引子try…catch…finally恐怕是大家再熟悉不过的语句了, 你的答案是...
    壹点零阅读 1,530评论 0 0
  • 异常分类 Java将异常分为两种,Checked异常和Runtime异常。Java认为Checked异常都是可以在...
    LLorenzo阅读 703评论 0 1
  • Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制。Java异常机制可以使程序中异常处理...
    JourWon阅读 1,249评论 0 5
  • 2.JAVA异常 异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程...
    青城楼主阅读 549评论 0 0
  • 感恩爸妈的付出,感恩妈妈的后备箱,妈妈是把我能带的几乎都给装上车了。 感恩老公连续开了四个多小时的车后,安全到家 ...
    莀宝贝阅读 237评论 0 0