异常的深入研究与分析
前言
本文是异常内容的集大成者,力求全面,深入的异常知识研究与分析。本文由金丝燕网独家撰写,参考众多网上资源,经过内容辨别取舍,文字格式校验等步骤编辑而成,以飨读者。对于本文的内容,建议小白需要多多思考力求掌握,对于老手只需意会温故知新。对于本文的内容,属于基础知识研究范畴,切勿以为读完此文就能将异常知识掌握到家。切记:操千曲而后晓声,观千剑而后识器,所以我觉得没有大量的源码阅读经验,你很难知道什么时候需要自定义异常,什么时候需要抛出异常。
异常机制概述
异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
异常处理的流程
当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,如果找到,将控制权交到catch块中的代码,然后继续往下执行程序,try块中发生异常的代码不会被重新执行。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被中止。
异常的结构
异常的继承结构:Throwable为基类,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception。Error和RuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)。
Error异常
Error表示程序在运行期间出现了十分严重、不可恢复的错误,在这种情况下应用程序只能中止运行,例如JAVA 虚拟机出现错误。Error是一种unchecked Exception,编译器不会检查Error是否被处理,在程序中不用捕获Error类型的异常。一般情况下,在程序中也不应该抛出Error类型的异常。
RuntimeException异常
Exception异常包括RuntimeException异常和其他非RuntimeException的异常。
RuntimeException 是一种Unchecked Exception,即表示编译器不会检查程序是否对RuntimeException作了处理,在程序中不必捕获RuntimException类型的异常,也不必在方法体声明抛出RuntimeException类。RuntimeException发生的时候,表示程序中出现了编程错误,所以应该找出错误修改程序,而不是去捕获RuntimeException。
Checked Exception异常
Checked Exception异常,这也是在编程中使用最多的Exception,所有继承自Exception并且不是RuntimeException的异常都是checked Exception,上图中的IOException和ClassNotFoundException。JAVA 语言规定必须对checked Exception作处理,编译器会对此作检查,要么在方法体中声明抛出checked Exception,要么使用catch语句捕获checked Exception进行处理,不然不能通过编译。
在声明方法时候抛出异常
语法:throws(略)
为什么要在声明方法抛出异常?
方法是否抛出异常与方法返回值的类型一样重要。假设方法抛出异常却没有声明该方法将抛出异常,那么客户程序员可以调用这个方法而且不用编写处理异常的代码。那么,一旦出现异常,那么这个异常就没有合适的异常控制器来解决。
为什么抛出的异常一定是已检查异常?
RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改;只有已检查异常才是程序员所关心的,程序应该且仅应该抛出或处理已检查异常。而已检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数;客户程序员自己使用throw语句抛出异常。
注意:
覆盖父类某方法的子类方法不能抛出比父类方法更多的异常,所以,有时设计父类的方法时会声明抛出异常,但实际的实现方法的代码却并不抛出异常,这样做的目的就是为了方便子类方法覆盖父类方法时可以抛出异常。
在方法中如何抛出异常
语法:throw(略)
抛出什么异常?
对于一个异常对象,真正有用的信息是异常的对象类型,而异常对象本身毫无意义。比如一个异常对象的类型是ClassCastException,那么这个类名就是唯一有用的信息。所以,在选择抛出什么异常时,最关键的就是选择异常的类名能够明确说明异常情况的类。
异常对象通常有两种构造函数:一种是无参数的构造函数;另一种是带一个字符串的构造函数,这个字符串将作为这个异常对象除了类型名以外的额外说明。
为什么要创建自己的异常?
当Java内置的异常都不能明确的说明异常情况的时候,需要创建自己的异常。需要注意的是,唯一有用的就是类型名这个信息,所以不要在异常类的设计上花费精力。
throw和throws的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
publicclassTestThrow
{
publicstaticvoidmain(String[]args)
{
try
{
//调用带throws声明的方法,必须显式捕获该异常
//否则,必须在main方法中再次声明抛出
throwChecked(-3);
}
catch(Exceptione)
{
System.out.println(e.getMessage());
}
//调用抛出Runtime异常的方法既可以显式捕获该异常,
//也可不理会该异常
throwRuntime(3);
}
publicstaticvoidthrowChecked(inta)throwsException
{
if(a>0)
{
//自行抛出Exception异常
//该代码必须处于try块里,或处于带throws声明的方法中
thrownewException("a的值大于0,不符合要求");
}
}
publicstaticvoidthrowRuntime(inta)
{
if(a>0)
{
//自行抛出RuntimeException异常,既可以显式捕获该异常
//也可完全不理会该异常,把该异常交给该方法调用者处理
thrownewRuntimeException("a的值大于0,不符合要求");
}
}
}
|
补充:throwChecked函数的另外一种写法如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
publicstaticvoidthrowChecked(inta)
{
if(a>0)
{
//自行抛出Exception异常
//该代码必须处于try块里,或处于带throws声明的方法中
try
{
thrownewException("a的值大于0,不符合要求");
}
catch(Exceptione)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
注意:此时在main函数里面throwChecked就不用try异常了。
应该在声明方法抛出异常还是在方法中捕获异常?
处理原则:捕捉并处理哪些知道如何处理的异常,而传递哪些不知道如何处理的异常
使用finally块释放资源
finally关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。
try块中System.exit(0);则不会执行finally代码块。
在以下三种情况下会进入finally块:
(1) try块中的代码正常执行完毕。
(2) 在try块中抛出异常。
(3) 在try块中执行return、break、continue。
因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些代码放入finally块中。当你的程序中使用了外界资源,如数据库连接,文件等,必须将释放这些资源的代码写入finally块中。
必须注意的是:在finally块中不能抛出异常。JAVA异常处理机制保证无论在任何情况下必须先执行finally块然后再离开try块,因此在try块中发生异常的时候,JAVA虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再向外抛出异常。如果在finally块中抛出异常,try块捕捉的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try块中发生的真正的异常堆栈信息则丢失了。
请看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Connection con=null;
try
{
con=dataSource.getConnection();
……
}
catch(SQLExceptione)
{
……
throwe;//进行一些处理后再将数据库异常抛出给调用者处理
}
finally
{
try
{
con.close();
}
catch(SQLExceptione)
{
e.printStackTrace();
……
}
}
|
运行程序后,调用者得到的信息如下
java.lang.NullPointerException
at myPackage.MyClass.method1(methodl.java:266)
而不是我们期望得到的数据库异常。这是因为这里的con是null的关系,在finally语句中抛出了NullPointerException,在finally块中增加对con是否为null的判断可以避免产生这种情况。
丢失的异常
请看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
publicvoidmethod2()
{
try
{
……
method1(); //method1进行了数据库操作
}
catch(SQLExceptione)
{
……
thrownewMyException("发生了数据库异常:"+e.getMessage);
}
}
publicvoidmethod3()
{
try
{
method2();
}
catch(MyExceptione)
{
e.printStackTrace();
……
}
}
|
上面method2的代码中,try块捕获method1抛出的数据库异常SQLException后,抛出了新的自定义异常MyException。这段代码是否并没有什么问题,但看一下控制台的输出:
MyException:发生了数据库异常:对象名称'MyTable' 无效。
at MyClass.method2(MyClass.java:232)
at MyClass.method3(MyClass.java:255)
原始异常SQLException的信息丢失了,这里只能看到method2里面定义的MyException的堆栈情况;而method1中发生的数据库异常的堆栈则看不到,如何排错呢,只有在method1的代码行中一行行去寻找数据库操作语句了。
JDK的开发者们也意识到了这个情况,在JDK1.4.1中,Throwable类增加了两个构造方法,public Throwable(Throwable cause)和public Throwable(String message,Throwable cause),在构造函数中传入的原始异常堆栈信息将会在printStackTrace方法中打印出来。但对于还在使用JDK1.3的程序员,就只能自己实现打印原始异常堆栈信息的功能了。实现过程也很简单,只需要在自定义的异常类中增加一个原始异常字段,在构造函数中传入原始异常,然后重载printStackTrace方法,首先调用类中保存的原始异常的printStackTrace方法,然后再调用super.printStackTrace方法就可以打印出原始异常信息了。可以这样定义前面代码中出现的MyException类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
importjava.io.PrintStream;
importjava.io.PrintWriter;
publicclassMyExceptionextendsException
{
privatestaticfinallongserialVersionUID=1L;
//原始异常
privateThrowablecause;
//构造函数
publicMyException(Throwablecause)
{
this.cause=cause;
}
publicMyException(Strings,Throwablecause)
{
super(s);
this.cause=cause;
}
//重载printStackTrace方法,打印出原始异常堆栈信息
publicvoidprintStackTrace()
{
if(cause!=null)
{
cause.printStackTrace();
}
super.printStackTrace();
}
publicvoidprintStackTrace(PrintStreams)
{
if(cause!=null)
{
cause.printStackTrace(s);
}
super.printStackTrace(s);
}
publicvoidprintStackTrace(PrintWriters)
{
if(cause!=null)
{
cause.printStackTrace(s);
}
super.printStackTrace(s);
}
}
|
练习:
题目一:考察异常类的继承结构
那个类是所有异常的基础类?
A String
B Error
C Throwable
D RuntimeException
参考答案:
C
试题分析:
在Java的lang包里面有一个Throwable类,它是所有异常的父类或者间接父类,它有两个直接子类:Error和Exception。Error及其子类是处理系统内部及程序运行环境的异常,一般与硬件有关,由系统直接处理,不需要程序员在程序中处理。Exception又分两大类,运行时异常(RuntimeException)和非运行时异常。其中类RuntimeException代表运行时由Java虚拟机产生的异常,例如算术运算异常ArithmeticException,数组越界异常arrayIndexOutOfBoundsException等;非运行时异常,例如输入输入异常IOException等,Java编译器要求Java程序必须捕获或声明所有非运行时异常,但对运行时异常可以不做处理,因此,在编程时非运行时异常如果不处理,编译时会出错。
题目二:考察异常捕获的结构
分析下面给出的Java代码,编译运行后,输出的结果是什么?
public class print_message {
public static void main(String[] args) {
print();
}
static void print(){
try{
System.out.println("thank you !");
}finally{
System.out.println("I am sorry !");
}
}
}
A thank you !
B I am sorry !
C thank you !
I am sorry !
D 代码不能编译
参考答案:
C
试题分析:
在Java中,try和catch可以连用,try-catch-finally可以连用,但是try,catch,finally却不能单独使用,如果在程序中只想使用try而不想使用catch也可以,但是try的后面必须有finally。
在本题中,try中的打印语句并没有异常发生所以正常输出,对于finally无论有没有异常发生,总是要执行的。
题目三:考察异常声明与抛出的区别
TimedOutException不是一个RuntimeException,下面的那些选项载入程序中,使程序可以正常运行?
A public void final()
B public void final() throws Exception
C public void final() throws TimedOutException
D public void final() throw TimedOutException
E public throw TimedOutException void final()
参考答案:
BC
试题分析:
如果一个程序在运行时候有异常发生,而这个异常又不是RuntimeException或者Error,那么程序必须对这个异常进行捕获处理或者声明抛出该异常。捕获异常使用try-catch-finally,而声明异常则是在声明方法的同时将会发生的异常进行声明,使用关键字throws。
A项没有使用关键字声明异常,所以是错误的。由于Exception是所有异常的父类,当然也可以代表TimedOutException,所以B项是正确的。C项符合声明异常的格式,是正确的。在D项中,throw是抛出异常,而不是声明异常,关键字使用错误,所以D项是错的。E项的语法格式是错误的。
异常的深入研究与分析相关推荐
- linux fat32 乱码,FAT32文件系统乱码的研究和分析
刘宏斌 摘 要 FAT32文件分配表是Windows操作系统磁盘分区格式的一种.采用32位的文件分配表,使其对磁盘的管理能力增强.本文通过文件系统出现乱码对FAT32的文件分配表进行研究和分析 关键词 ...
- 智能变电站时间同步系统的研究与分析
1 .引言 智能变电站时间同步是智能变电站运行的基本组成部分,它利用时间同步系统为智能变电站中的所有智能电子设备提供可靠稳定的时间同步信息,目前基本采用PPS.PPM.IRIG-B等单向时间信号或基于 ...
- 面向移动支付过程中网络安全的研究与分析
目 录 1.前言......................................................................................... ...
- 前端异常监控解决方案研究
摘要: 异常监控不复杂也不简单啊... 原文:前端异常监控解决方案研究 作者:frustigor 前端监控包括行为监控.异常监控.性能监控等,本文主要讨论异常监控.对于前端而言,和后端处于同一个监控系 ...
- matlab segy文件,利用matlab实现segy格式数据的读写研究和分析.ppt
利用matlab实现segy格式数据的读写研究和分析 2.SEGY数据格式 地震数据一般以地震道为单位进行组织,采用SEG-Y文件格式存储.SEG-Y格式是由SEG (Society of Explo ...
- 群组密钥交换的新方法研究与分析【会议】
群组密钥交换的新方法研究与分析 写在前面的话 会议记录 写在前面的话 <网络空间安全青年科学家长安论坛>,本篇博客为南京信息工程大学沈剑老师的报告内容. 会议记录
- 基于公开网站挖掘敏感信息的研究与分析- Fofa 搜索
基于公开公开网站挖掘敏感信息的研究与分析- Fofa 搜索 一.引言 1.1项目概述 基于公开网站的敏感信息挖掘研究与分析:针对目前网络安全整体的趋势我们从google等搜索引擎.Github等代码库 ...
- 【电力电子】【2007.05】三相多电平逆变器的研究与分析
本文为印度Rourkela国立技术研究所(作者:SANJEEV BALACHANDRAN)的学士论文,共54页. 本课题主要研究和分析三相多电平逆变器及其不同的拓扑结构.本文的主要目的是研究调制技术, ...
- 【个人研究】21世纪西方运用脑电(EEG)的音乐研究动向分析(二)
21世纪西方运用脑电(EEG)的音乐研究动向分析 接上条推文,ppt的后半部分~ 参考文献: 赵钰茜.21世纪西方运用脑电(EEG)的音乐研究动向分析[J].星海音乐学院学报,2021(03):112 ...
最新文章
- 使用C++实现的一个五子棋系统(基于Socket编程)
- python怎么使用训练好的模型设计_tensorflow训练好的模型怎么调用?
- php BC高精确度函数库
- 从字符串中删除HTML标签
- linux内核数据结构之链表
- qt 文件 时间 倒序排列_win7电脑敬业签便签软件分类标签里的内容如何按提醒时间排列?...
- 在Android手机上配置office 365邮箱
- 前端学习(2000)vue之电商管理系统电商系统之绘制基本面板的结构
- 慕课网仿去哪儿项目笔记--(一)-初始化准备
- 第五章 二叉树(a)树
- Gin框架Go Web项目实战
- 在vue中使用Animate.css
- 2021年7月20日我国暴雨趋势遥感监测与评估
- c语言jink exe出错,jlink 错误 :Can not connect to J-Link via USB
- python Excel xlsx file; not supported
- 高德地图定位 地图比例缩放
- 函数重载导致的二义性
- 在Home Assistant中配置天气信息
- Material Design的基础知识
- [DFS]最简题-搜索字母排列
热门文章
- 二次判别分析(QDA)和Python实现
- 百度飞桨AI抠图+图片合成
- 使用JavaScript 中的Math对象和勾股定理公式,计算鼠标的位置与页面图片中心点的距离,根据距离对页面上的图片进行放大或缩小处理。距离远时图片放大,距离近时图片缩小
- git撤销已经提交push到master或者已经合并到master的mr
- 从零入门云计算(1):云计算究竟是个啥?
- 湖南师范学院大学计算机等级考试,湖南师范大学计算机等级考试时间
- 功耗大好还是小好_热设计功耗高好还是低好 - 卡饭网
- 【2022年度总结与2023展望】---22年故事不长,且听我来讲
- MAIGO的同济题解2
- 从旧金山到瑞典的开发者的福利