一、Java异常的简介

Java异常是Java提供的一种识别及响应错误的一致性机制。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。

Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。

二、Java异常的体系结构

Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。

Throwable包含两个子类: Error 和 Exception。它们通常用于指示发生了异常情况。

Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。

RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。RuntimeException及其子类都被称为运行时异常。

编译器不会检查RuntimeException异常。如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。

如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既没有通过throws声明抛出ArithmeticException异常,也没有通过try...catch...处理该异常,也能通过编译。这就是"编译器不会检查RuntimeException异常"!

public class Test {
public static void main(String[] args) {
int num = 10;
System.out.println(num/0);
}
}

如上代码,程序并未报错,运行结果出错:Exception in thread "main" java.lang.ArithmeticException: / by zero

Error和Exception一样,Error也是Throwable的子类。它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。和RuntimeException一样,编译器也不会检查Error。

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

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

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

需要明确的是:检查和非检查是对于javac来说的。

三、Java异常的处理

1、基本异常处理

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

首先对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...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块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}

需要注意的地方

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

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

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

有的编程语言当异常被处理后,控制流会恢复到异常抛出点接着执行,这种策略叫做:resumption model of exception handling(恢复式异常处理模式 )

而Java则是让执行流恢复到处理了异常的catch块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)

public class Test {
public static void main(String[] args) {
try {
div();
} catch (ArithmeticException e) {
System.err.println("异常处理");
} finally {
System.out.println("处理完成");
}
}
public static void div() {
int num = 10/0;
System.err.println("我是测试语句");
}
}

throws 函数声明

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

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

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

public void div() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN {
//内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
}

2、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块。

此外,也有特殊情况:在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。

public class Test {
public static void main(String[] args) {
int num = div();
System.out.println(num);
}
public static int div() {
try {
int num = 10/0;
} catch (ArithmeticException e) {
return 5;
} finally {
return 4;
}
}
}

try...catch...finally中的return 只要能执行,就都执行了,他们共同向同一个内存地址(假设地址是0x80)写入返回值,后执行的将覆盖先执行的数据,而真正被调用者取的返回值就是最后一次写入的。

finally中的return 会覆盖 try 或者catch中的返回值。见上一个代码示例:

finally中的return会抑制(消灭)前面try或者catch块中的异常,示例代码如下:

@SuppressWarnings("all")
public class Test {
public static void main(String[] args) {
int result;
try {
result = foo();
System.out.println(result); //输出100
} catch (Exception e){
System.out.println(e.getMessage()); //没有捕获到异常
} try{
result = bar();
System.out.println(result); //输出100
} catch (Exception e){
System.out.println(e.getMessage()); //没有捕获到异常
}
} //catch中的异常被抑制
public static int foo() {
try {
int a = 5/0;
return 1;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中使用了return");
}finally {
return 100;
}
} //try中的异常被抑制
public static int bar() {
try {
int a = 5/0;
return 1;
}finally {
return 100;
}
}
}

finally中的异常会覆盖(消灭)前面try或者catch中的异常

@SuppressWarnings("all")
public class Test {
public static void main(String[] args) {
int result;
try{
result = foo();
} catch (Exception e){
System.out.println(e.getMessage()); //输出:我是finaly中的Exception
} try{
result = bar();
} catch (Exception e){
System.out.println(e.getMessage()); //输出:我是finaly中的Exception
}
} //catch中的异常被抑制
public static int foo() throws Exception {
try {
int a = 5/0;
return 1;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
}finally {
throw new Exception("我是finaly中的Exception");
}
} //try中的异常被抑制
public static int bar() throws Exception {
try {
int a = 5/0;
return 1;
}finally {
throw new Exception("我是finaly中的Exception");
}
}
}

这些例子展示了一些不常见的编码方式,从而引发了各种奇怪的问题,因而在编码中做到:

  1. 不要在fianlly中使用return。
  2. 不要在finally中抛出异常。
  3. 减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
  4. 将尽量将所有的return写在函数的最后面,而不是try ... catch ... finally中。

3、异常的链化

在一些大型的,模块化的软件开发中,一旦一个地方发生异常,则如骨牌效应一样,将导致一连串的异常。假设B模块完成自己的逻辑需要调用A模块的方法,如果A模块发生异常,则B也将不能完成而发生异常,但是B在抛出异常时,会将A的异常信息掩盖掉,这将使得异常的根源信息丢失。异常的链化可以将多个模块的异常串联起来,使得异常信息不会丢失。

异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫他根源异常(cause)。异常的链化可以将多个模块的异常串联起来,使得异常信息不会丢失。

4、自定义异常

如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException。

按照惯例,自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

5、异常注意事项

1、当子类重写父类的带有 throws声明的函数时,其throws声明的异常必须在父类异常的可控范围内——用于处理父类的throws方法的异常处理器,必须也适用于子类的这个带throws方法 。这是为了支持多态。

2、Java程序可以是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码处理的异常 会导致程序终止。如果是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。

本文参考了:

本文大部分出自https://www.cnblogs.com/lulipro/p/7504267.html

https://www.cnblogs.com/skywang12345/p/3544168.html

理解Java异常的更多相关文章

  1. 加深理解Java异常概念并熟记5个最常见的运行时异常

    加深理解Java异常概念并熟记5个最常见的运行时异常 说明Error与Exception的联系和区别有哪些? 列举最常见的5个运用时异常. 1.Error和Exception的联系和区别: Error ...

  2. 全面理解Java异常的运行机制

    1. 引子 try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解.不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单.听话. ...

  3. 全面理解java异常机制

    在理想状态下,程序会按照我们预想的步骤一步一步的执行,但是即使你是大V,你也不可避免出错,所以java为我们提供了异常机制.本文将会从以下几个方面介绍java中的异常机制: 异常机制的层次结构 异常的 ...

  4. Java基础 -- 深入理解Java异常机制

    异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各种不同的异常. ...

  5. 深入理解java异常【绝对经典,推荐最少看五遍】

    http://blog.csdn.net/hguisu/article/details/6155636 补充:检查异常(checkedException)与运行异常,什么时候throw,什么时候thr ...

  6. Java基础系列5:深入理解Java异常体系

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 前言: Java的基 ...

  7. Java——深入理解Java异常体系

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 前言: Java的基 ...

  8. 深入理解 Java 异常

  9. java异常——捕获异常+再次抛出异常与异常链

    [0]README 0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java异常--捕获异常+再次抛出异常与异常链 的相关知识: [1]捕获异常相关 1.1)如果 ...

随机推荐

  1. [实战演练]蜻蜓FM2014年校招笔试题目 - 规则二叉树

    题目:某规则二叉树的定义是:对于树中任意两个叶结点A.B,他们与根结点的距离分别是d1和d2,|d1-d2|<=1.请写出函数 bool isRuledTree(Node *root)的代码实现 ...

  2. 有标号的DAG图计数1~4

    前言 我什么都不会,菜的被关了起来. 有标号的DAG图I Solution 考虑递推,设\(f_i\)表示i个点的答案,显然这个东西是可以组合数+容斥递推? 设\(f_i\)表示i个点的答案,我们考虑 ...

  3. Swift5 语言指南(二十三) 协议

    甲协议定义的该适合特定任务或片的功能的方法,属性和其他要求的蓝图.然后,可以通过类,结构或枚举来采用该协议,以提供这些要求的实际实现.任何满足协议要求的类型都被认为符合该协议. 除了指定符合类型必须实 ...

  4. 将 Microsoft Excel 导入至 MySQL

    将 Microsoft Excel 导入至 MySQL 一.前言 我得到了一份 Microsoft Excel 表格,里面记录了数据信息需要导入至 SQL 数据库. 如果只是导入数据,当然用 MSSQ ...

  5. canvas 实现签名效果

    效果图 概述 在线签名,现在在很多场景下都能看到,而且在移动端见的比较多. 用canvas和svg都可以实现,而且跨平台能力也很好. canvas基于像素,提供 2D 绘制函数,提供的功能更原始,适合 ...

  6. VSTO:C#获取文档控件的值

    基础知识准备: VSTO入门 创建Excel解决方案   string[] inputfileNames = { @"C:\1.xls", @"C:\2.xls" ...

  7. ES6中的元编程-Proxy & Reflect

    前言 ES6已经出来好久了,但是工作中比较常用的只有let const声明,通过箭头函数改this指向,使用promise + async 解决异步编程,还有些数据类型方法...所以单独写一篇文章学习 ...

  8. 5_Python OOP

    1. 实例属性和类属性        (1) 实例属性在构造函数__init__中定义,定义时以self作为前缀,只能通过实例名访问        (2) 类属性在类中方法之外单独定义,还可以在程序中 ...

  9. [Umbraco] xslt语言介绍及与umbraco的关系

    XSLT是扩展样式表转换语言(Extensible Stylesheet Language Transformations)的简称,这是一种对XML文档进行转化的语言,XSLT中的T代表英语中的“转换 ...

  10. 兼容IE9以下和非IE浏览器的原生js事件绑定函数

    事件绑定函数的demo如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "htt ...