-
Java基本概念:异常
一、简介
描述:
-
异常(Exception)指不期而至的各种状况,异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
- 异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。
- Java语言在设计的当初就考虑到这些问题,提出异常处理的框架的方案,所有的异常都可以用一个异常类来表示,不同类型的异常对应不同的子类异常(目前我们所说的异常包括错误概念),定义异常处理的规范,在 JDK1.4 版本以后增加了异常链机制,从而便于跟踪异常。
- Java异常是一个描述在代码段中发生异常的对象,当发生异常情况时,一个代表该异常的对象被创建并且在导致该异常的方法中被抛出,而该方法可以选择自己处理异常或者传递该异常。
- Java异常处理本质:抛出异常和捕获异常。
分类:
- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
二、异常体系
描述:
- Java把异常当作对象来处理,并定义一个'java.lang.Throwable' 类作为所有异常的父类。
- 在Java API中已经定义了许多异常类,这些异常类分为两大类,错误(Error)和异常(Exception)。
- 'Error'表示不希望被程序捕获或者是程序无法处理的错误。
- 'Exception'表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常,'Exception'又分为运行时异常(RuntimeException)和非运行时异常。
- Java异常(Exception)又可以分为不受检查异常(Unchecked Exception)和检查异常(Checked Exception)。
图示:
三、异常的区别与联系
Ⅰ、Error与Exception
Error:
- 'Error'类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
- 例如 Java虚拟机运行错误(VirtualMachineError),当JVM不再有继续执行操作所需的内存资源时,将出现'OutOfMemoryError'。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
- 对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。
- 在Java中,错误通常是使用'Error'的子类描述。
Exception:
- 在 Exception 分支中有一个重要的子类'RuntimeException'运行时异常,该类型的异常会自动为你所编写的程序定义异常('ArrayIndexOutOfBoundsException'数组下标越界、'NullPointerException'空指针、'ArithmeticException'算术异常、'MissingResourceException'丢失资源、'ClassNotFoundException'找不到类等),这些异常是不受检查异常,程序中可以选择捕获处理,也可以不处理。
- 'RuntimeException'一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而'RuntimeException'之外的异常(Exception)我们统称为非运行时异常,类型上属于'Exception'类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过,因此又称为检查异常。如'IOException'、'SQLException'等以及用户自定义的'Exception'异常,一般情况下不自定义检查异常。
Ⅱ、 Unchecked Exception与Checked Exception
Unchecked Exception:
- 不受检查异常包括'RuntimeException'及其子类和'Error'。
- 不受检查异常为编译器不要求强制处理的异常。
Checked Exception:
- 除了'RuntimeException'及其子类以外,其他的'Exception'类及其子类都属于检查异常。
- 当程序中可能出现检查异常,要么使用'try-catch'语句进行捕获,要么用'throws'子句抛出,否则编译无法通过。
- 检查异常是编译器要求必须处置的异常。
四、异常处理机制
Ⅰ、抛出异常
描述:
-
要理解抛出异常,首先要明白什么是异常情形(Exception Condition):
-
异常情形是指阻止当前方法或作用域继续执行的问题。
-
异常情形和普通问题不同,普通问题是指在当前环境下能得到足够的信息,总能处理这个错误。
-
-
对于异常情形,已经无法继续下去了,因为在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境中跳出,并把问题提交给上一级环境,这就是抛出异常时所发生的事情。
-
抛出异常后,会有几件事随之发生:
- 首先,是像创建普通的java对象一样,将使用'new'在堆上创建一个异常对象。
- 然后,当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。
- 此时,异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序,这个恰当的地方就是异常处理程序或者异常处理器,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
示例:
public class Test {
public void test1(Student student) {
if (student == null) {
/* 抛出一个不受检查异常时,方法声明上不需要强制声明该异常 */
throw new RuntimeException();
}
System.out.println(student.name);
}
public void test2(Student student) throws Exception {
if (student == null) {
/* 抛出一个受检查异常时,方法声明必须声明该异常,否则编译报错 */
throw new Exception();
}
System.out.println(student.name);
}
public static void main(String[] args) throws Exception {
new Test().test1(null);
/* main方法调用声明了异常的方法,必须对该异常进行处理,否则编译报错 */
new Test().test2(null);
}
}
class Student {
String name;
}
Ⅱ、捕获异常
描述:
- 在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。
- 潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
- 运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。
- 当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
注意:
-
对于运行时异常(RuntimeException)、错误(Error)和检查异常(Checked Exception),Java技术所要求的异常处理方式有所不同。
- 由于运行时异常及其子类的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
- 对于方法运行中可能出现的错误,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
- 对于所有的检查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉检查异常时,它必须声明将抛出异常。
Ⅲ、异常处理关键字
-
try
- 用于监听。将要被监听的代码(可能抛出异常的代码)放在'try'语句块之内,当'try'语句块内发生异常时,异常就被抛出。
-
catch
- 用于捕获异常。'catch'用来捕获'try'语句块中发生的异常。
-
finally
- 'finally'语句块总是会被执行。它主要用于回收在'try'块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有'finally'块,执行完成之后,才会回来执行'try'或者'catch'块中的'return'或者'throw'语句,如果'finally'中使用了'return'或者'throw'等终止方法的语句,则就不会跳回执行,直接停止。
-
throw
- 用于在方法体中抛出异常。
-
throws
- 用在方法签名中,用于声明该方法可能抛出的异常。
五、处理异常
Ⅰ、try-catch
描述:
- 要明白异常捕获,还要理解监控区域(guarded region)的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
- 'try-catch'所描述的即是监控区域,关键词'try'后的一对大括号将一块可能发生异常的代码包起来,即为监控区域。Java方法在运行过程中发生了异常,则创建异常对象。
- 将异常抛出监控区域之外,由Java运行时系统负责寻找匹配的'catch'子句来捕获异常。若有一个'catch'语句匹配到了,则执行该'catch'块中的异常处理代码,就不再尝试匹配别的'catch'块了。
- 匹配原则:如果抛出的异常对象属于'catch'子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与'catch'块捕获的异常类型相匹配。
多重'catch'语句:
- 很多情况下,由单个的代码段可能引起多个异常。
- 处理这种情况,我们需要定义两个或者更多的'catch'子句,每个子句捕获一种类型的异常,当异常被引发时,每个'catch'子句被依次检查,第一个匹配异常类型的子句执行,当一个'catch'子句执行以后,其他的子句将被跳过。
- 编写多重'catch'语句块时注意'catch'子句捕获的异常的顺序:先小后大,即先子类后父类。否则,捕获底层异常类的'catch'子句将可能会被屏蔽。
嵌套'try'语句:
- 'try'语句可以被嵌套。也就是说,一个'try'语句可以在另一个'try'块的内部。
- 每次进入'try'语句,异常的前后关系都会被推入堆栈。如果一个内部的'try'语句不含特殊异常的'catch'处理程序,堆栈将弹出,下一个'try'语句的'catch'处理程序将检查是否与之匹配。这个过程将继续直到一个'catch'语句被匹配成功,或者是直到所有的嵌套'try'语句被检查完毕。如果没有'catch'语句匹配,Java运行时系统将处理这个异常。
- 当有方法调用时,'try'语句的嵌套可以很隐蔽的发生。例如,我们可以将对方法的调用放在一个'try'块中,在该方法的内部,有另一个'try'语句。在这种情况下,方法内部的'try'语句仍然是嵌套在外部调用该方法的'try'块中的。
语法:
try {
/* 监控区域,可能产生异常的代码 */
} catch (FirstExceptionType e1) {
/* 指定异常类型的异常e1的处理代码 */
} catch (SecondExceptionType e2) {
/* 指定异常类型的异常e2的处理代码 */
}
示例:
class Test {
static void nestTry(int a) {
try {
/* 当命令行编译运行该java文件时有一个参数的话,使此方法产生算术运算异常 */
if (a == 1) {
a = a / (a - a);
}
/* 当命令行编译运行该java文件时有两个参数的话,使此方法产生数组越界异常 */
if (a == 2) {
int c[] = {1};
c[42] = 99;
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndexOutOfBounds :" + e);
}
}
public static void main(String[] args) {
try {
/* 当命令行编译运行该java文件时没有参数的话,使此方法产生算术运算异常 */
int a = args.length;
int b = 42 / a;
System.out.println("a = " + a);
nestTry(a);
/* 多重'catch'语句块捕获的异常的顺序:先小后大,即先子类后父类。 */
} catch (ArithmeticException e) {
System.out.println("Divide by 0 :" + e);
} catch (Exception e) {
/* Throwable类重载了Object类的toString()方法,所以打印e调用其toString()方法将返回一个包含异常描述的字符串。 */
System.out.println(e);
}
}
}
CMD测试:
D:\studyworkspace\test\src\main\java\com\conyoo\test>javac Test.java
D:\studyworkspace\test\src\main\java\com\conyoo\test>cd ../../..
D:\studyworkspace\test\src\main\java>java com.conyoo.test.Test
Divide by 0 :java.lang.ArithmeticException: / by zero
D:\studyworkspace\test\src\main\java>java com.conyoo.test.Test 1
a = 1
Divide by 0 :java.lang.ArithmeticException: / by zero
D:\studyworkspace\test\src\main\java>java com.conyoo.test.Test 1 1
a = 2
ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException: 42
D:\studyworkspace\test\src\main\java>
Ⅱ、throw
描述:
- 除了Java运行时系统自动创建的异常对象,我们还可以用'throw'语句抛出明确的异常。
- 'throw'语句抛出的一定是'Throwable'类类型或者'Throwable'子类类型的一个对象。
- 'throw'语句抛出的异常对象有两种来源:使用'catch'子句中的参数,或者使用'new'操作符创建。
- 程序执行完'throw'语句之后立即停止,'throw'后面的任何语句不会执行,最邻近的'try'块会用来检查是否含有一个与异常类型匹配的'catch'语句。如果发现了匹配的'catch'块,转向执行该'catch'块里的语句;如果没有发现,次包围的'try'块来检查,以此类推。如果没有发现匹配的'catch'块,默认异常处理程序会中断程序的执行,并且打印堆栈轨迹。
语法:
throw ThrowableInstance;
示例:
/*
运行结果为在控制台依次打印:
异常对象在test方法里被catch。
异常对象在main方法里被catch。java.lang.NullPointerException: 一个空指针异常对象
*/
class Test {
static void test() {
try {
/*
所有的Java内置的运行时异常有两个构造方法:一个没有参数,一个带有一个字符串参数。
当用第二种形式时,该参数应当为描述异常的字符串。
该字符串会被赋值给Throwable类的属性detailMessage。
这样Throwable类定义的getMessage()和Throwable类重写的toString()等方法中,都会使用该字符串。
*/
throw new NullPointerException("一个空指针异常对象");
} catch (NullPointerException e) {
System.out.println("异常对象在test方法里被catch。");
throw e;
}
}
public static void main(String[] args) {
try {
test();
} catch (NullPointerException e) {
System.out.println("异常对象在main方法里被catch。" + e);
}
}
}
Ⅲ、throws
描述:
- 如果一个方法可以导致一个异常,但不在该方法中处理此异常,则该方法应当指定这种不处理的行为(若异常为检查异常则必须指定),以使该方法的调用者可以保护自身而不发生异常。要做到这点,我们可以在该方法的声明中包含一个'throws'子句。
- 一个'throws'子句可以列举一个方法可能引发的所有异常类型。
注意:
- 如果是不受检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
- 必须声明方法可抛出的任何检查异常(checked exception)。即如果一个方法可能出现检查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
- 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
- 若重写一个方法,子类方法声明的异常类型,不能为'被重写方法声明的异常类型'的父类,可以为同一类型或其子类。也可以另外再声明,不在'被重写方法声明的异常类型'的直系继承关系中的,其他类型异常。
语法:
/* Throwable是该方法可能引发的异常,也可以是异常列表,中间以逗号隔开。 */
public void function() throws Throwable {}
示例:
class Test1 {
void test() throws IllegalAccessException {
throw new IllegalAccessException("IllegalAccessException1");
}
public static void main(String[] args) {
try {
new Test2().test();
} catch (Exception e) {
System.out.println(e);//java.lang.IllegalAccessException: IllegalAccessException2
}
}
}
class Test2 extends Test1 {
/* throws可以另外再声明,不在'被重写方法声明的异常类型'的直系继承关系中的,其他类型异常。 */
void test() throws NegativeArraySizeException, IllegalAccessException {
throw new IllegalAccessException("IllegalAccessException2");
}
}
Ⅳ、finally
描述:
- 当异常发生时,通常方法的执行将做一个陡峭的非线性的转向,它甚至会过早地导致方法返回。例如,如果一个方法打开了一个文件并关闭,然后退出,你不希望关闭文件的代码被异常处理机制所跳过,就可以使用'finally'关键字。
- 一个方法,将从内部的'try-catch'块返回到其调用者处前,即经过一个未捕获的异常或者是一个明确的返回语句前,'finally'子句会执行。这在关闭文件句柄,和释放任何在方法开始时被分配的其他资源时很有用。
注意:
- 'finally'代码块在所属'try-catch'块将完成之时,其他'try-catch'块出现之前执行。
- 'finally'块无论所在监控区域有没有异常抛出都会执行。如果抛出异常,即使没有'catch子句匹配,'finally'块也会执行。
- 'finally'子句是可选项,可以有也可以无,但是每个'try'语句至少需要一个'catch'或者'finally'子句。
- 如果'finally'块与一个'try'块(没有'catch'块)联合使用,finally块将在'try'块结束之前执行。
- 如果存在'finally'代码块,'try'中的'return'语句不会立马返回调用者,而是记录下返回值待'finally'代码块执行完毕之后再向调用者返回其值,这样如果在'finally'中修改了返回值,就会返回修改后的值。
- 不要在'finally'中进行'return'操作或者修改返回值,可能会导致一些意想不到的逻辑错误,例如方法的调用者捕获不到异常。
示例:
/*
运行结果为在控制台依次打印:
inside proc1
proc1's finally
java.lang.RuntimeException
inside proc2
proc2's finally
inside proc3
proc3's finally
*/
class Test {
static void proc1() {
try {
System.out.println("inside proc1");
throw new RuntimeException();
} finally {
System.out.println("proc1's finally");
}
}
static void proc2() {
try {
System.out.println("inside proc2");
return;
} finally {
System.out.println("proc2's finally");
}
}
static void proc3() {
try {
System.out.println("inside proc3");
} finally {
System.out.println("proc3's finally");
}
}
public static void main(String[] args) {
try {
proc1();
} catch (Exception e) {
System.out.println(e);
}
proc2();
proc3();
}
}
Ⅴ、try、catch、finally、return执行顺序
- 执行'try'块里的代码。
- 若'try'块里出现异常,检查该异常是否能被'catch'子句捕获,若与'catch'子句声明的异常相匹配,则执行'catch'块里的代码。
- 执行'try-catch'块里的代码,直到遇到向外抛出异常(该异常无法被当前'catch'子句捕获)或'return'的语句,这时若有'finally'块,则先去执行'finally'块中的代码。
- 向外抛出异常或执行'return'语句。
六、自定义异常
描述:
- 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常,自定义异常类只需继承'Exception'类即可。
- 在程序中使用自定义异常类,与使用Java内置的异常类基本相同,由于是自定义的异常,所以需要在代码里主动创建该异常对象并'throw'。
-
使用自定义异常类大体可分为以下几个步骤:
- 创建自定义异常类,继承'Exception'类或其子类。
- 在方法中通过'throw'关键字抛出异常对象。
- 如果要在当前抛出异常的方法中处理异常,可以使用'try-catch'语句捕获并处理;否则在方法的声明处,通过'throws'关键字指明要抛出给方法调用者的异常。
- 继续在出现异常的方法的调用者中捕获处理异常或继续抛出异常。
示例:
/*
运行结果为在控制台依次打印:
Called compute(1)
Normal exit!
Called compute(20)
Caught MyException [20]
*/
public class Test {
static void compute(int a) throws MyException {
System.out.println("Called compute(" + a + ")");
if (a > 10) {
throw new MyException(a);
}
System.out.println("Normal exit!");
}
public static void main(String[] args) {
try {
compute(1);
compute(20);
} catch (MyException me) {
System.out.println("Caught " + me);
}
}
}
class MyException extends Exception {
private int detail;
MyException(int a) {
detail = a;
}
public String toString() {
return "MyException [" + detail + "]";
}
}
七、总结
图示:
实际应用:
- 处理运行时异常时,采用逻辑去合理规避的同时,辅助'try-catch'进行处理。
- 在多重'catch'块后面,可以加一个'catch (Exception e)'来处理可能会被遗漏的异常。
- 对于不确定的代码,也可以加上'try-catch'处理潜在的异常。
- 尽量去处理异常,切忌只是简单地调用'printStackTrace()'去打印输出。
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定。
- 尽量添加'finally'语句块去释放占用的资源。
来源:https://www.cnblogs.com/conyoo/p/14173231.html
最新更新
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
Python初学者友好丨详解参数传递类型
如何有效管理爬虫流量?
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
关于JS定时器的整理
JS中使用Promise.all控制所有的异步请求都完
js中字符串的方法
import-local执行流程与node模块路径解析流程
检测数据类型的四种方法
js中数组的方法,32种方法
前端操作方法
数据类型
window.localStorage.setItem 和 localStorage.setIte
如何完美解决前端数字计算精度丢失与数