目录
2. Exception(RuntimeException、CheckedException)
一、何为异常
所谓异常就是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。
在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:
异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。
类名Error、Exception和RuntimeException有时候容易引起混淆,这三种类都是异常。
二、异常处理机制
1. 简介
所谓的异常,其实就是各种“意外”,是指在程序执行期间发生的“意外事件”,它中断了正在执行程序的正常
指令流,没有产生我们预期中的结果。就好比我们的生活中,也会经常有一些意外发生。本来你计划今天和女朋
友去happy,结果你的领导非要你去加班,不加班就辞退,这就是预期外的异常事件。
Java给我们提供了专门的异常类来处理异常。在一个方法的运行过程中,如果发生了异常,这个方法会产生代表该
异常的一个对象,并把它交给运行时系统,运行时系统会寻找相应的代码来处理该异常。Java把异常提交给运行时
系统的过程称为拋出(throw)异常,运行时系统在方法的调用栈中查找到处理该异常对象的过程称为捕获(catch)
异常。
2. 产生原因
在Java中,一个异常的产生主要有以下几种原因:
- Java内部错误导致了异常,比如Java虚拟机自身出现了问题;
- 我们自己编写的代码有问题,例如空指针异常、数组越界异常等;
- 项目运行所依赖的外部数据、网络环境等有问题,导致项目产生了故障;
- 通过throw等语句主动生成异常,主要用来告知该方法的调用者一些必要信息。
三、异常类
1. Throwable
Throwable类是所有异常类的根,所有的Java异常类都直接或者间接地继承自Throwable。
可以通过继承Exception或者Exception的子类来创建自己的异常类。
2. Exception(RuntimeException、CheckedException)
Exception 又 有 两 个 分 支 , 一 个 是 运 行 时 异 常 RuntimeException , 一 个 是 CheckedException。
RuntimeException 如 :NullPointerException 、 ClassCastException ; 一 个 是 检 查 异 常
CheckedException,如 I/O 错误导致的IOException、SQLException。
RuntimeException 是 那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
如果出现 RuntimeException,那么一 定是程序员的错误.
检查异常 CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强 制程序去捕获此
类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一 般包括几个方面:
- 试图在文件尾部读取数据
- 试图打开一个错误格式的 URL
- 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在
四、异常类型
1. 系统错误
系统錯误(systemerror)就是由Java虚拟机抛出的,用Error类表示,Error类描述的是内部系统错误。
这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。
2. 编译时期异常
编译时期异常就是Exception类及子类(不包括 RuntimeException类及子类)表示的,它描述的是由程序和外部
环境所引起的错误,这些错误能被程序捕获和处理。
3. 运行时期异常
运行时异常(runtimeexception)就是用RuntimeException类表示的,它描述的是程序设计错误,
例如,错误的类型转换、访问一个越界数组或数值错误。运行时异常通常是由Java虚拟机抛出的。
4. 三种类型异常的区别
RuntimeException、Error以及它们的子类都称为免检异常(uncheckedexception)。
所有其他异常都称为必检异常(checkedexception)
意思就是指编译器会强制程序员检査并通过try-catch块处理它们,或者在方法头进行声明。
在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。
例如:
如果通过一个引用变量访问一个对象之前并未将一个对象陚值给它,就会抛出NullPointerException异常;
如果访问一个数组的越界元素,就会抛出IndexOutOfBoundsException异常。
这些都是程序中必须纠正的逻辑错误。
免检异常可能在程序的任何一个地方出现。为避免过多地使用try-catch块,Java语言不强制要求编写代码捕获或
声明免检异常。
五、链式异常
1. 简介
链式异常允许将一个异常与另一个异常联系起来,即一个异常描述另一个异常的原因。例如,考虑一种情况,其
中一个方法由于试图除以零而引发了一个算术异常,但是异常的实际原因是一个导致除数为零的输入/输出错误。
该方法将只向调用方抛出算术异常。所以打电话的人不会知道异常的真正原因。链式异常用于这种情况。
2. 支持
2.1. 支持 java 链式异常的可抛出类的构造函数
- 可抛出(可抛出原因):-其中原因是导致当前异常的异常。
- 可抛出(字符串消息,可抛出原因):-其中消息是异常消息,原因是导致当前异常的异常。
2.2. 支持 java 中链式异常的可抛出类的方法:
- getCause()方法:-该方法返回异常的实际原因。
- initCause(可抛出原因)方法:-该方法设置调用异常的原因。
3. 演示示例
// Java program to demonstrate working of chained exceptions
public class ExceptionHandling
{
public static void main(String[] args)
{
try
{
// Creating an exception
NumberFormatException ex =
new NumberFormatException("Exception");
// Setting a cause of the exception
ex.initCause(new NullPointerException(
"This is actual cause of the exception"));
// Throwing an exception with cause.
throw ex;
}
catch(NumberFormatException ex)
{
// displaying the exception
System.out.println(ex);
// Getting the actual cause of the exception
System.out.println(ex.getCause());
}
}
}
输出:
java.lang.NumberFormatException: Exception
java.lang.NullPointerException: This is actual cause of the exception
六、异常声明 & 捕获处理关键字
1. 简介
- try – 用于监听
将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出 - catch– 用于捕获异常
catch用来捕获try语句块中发生的异常 - finally – finally语句块总是会被执行
它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)
只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,
如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止 - throw – 用于抛出异常
- throws – 用在方法签名中,用于声明该方法可能抛出的异常
2. 抛出异常
2.1. 基本语法
当代码中发生了异常,程序会自动抛出一个异常对象,该对象包含了异常的类型和相关信息。
但是如果我们想主动抛出异常,则可以使用throw关键字来实现。
throw 某个Exception类;
这里的某个Exception类必须是Throwable类或其子类对象。
如果是自定义的异常类,也必须是Throwable的直接或间接子类,否则会发生错误。
2.2. 代码实现
public class Demo04 {
// throw的使用
public static void myMethod(boolean flag) throws Exception {
if (flag) {
//当flag为true时就抛出一个Exception对象
throw new Exception("主动抛出来的异常对象");
}
}
public static void caller() {
try {
//调用myMethod方法
myMethod(true);
} catch (Exception e) {
e.printStackTrace();
System.out.println("处理主动抛出来的异常:" + e.getMessage());
}
}
public static void main(String[] args) {
caller();
}
}
在上面这个案例中,myMethod方法产生了异常,但是自己却没有处理,而是进行了抛出。那么会抛给谁呢?
我们看到,此时caller方法调用了myMethod方法,即caller方法是myMethod方法的上层调用者,所以
myMethod方法中产生的异常就抛给了caller方法。但是如果在caller方法中对这个异常也没有进行处理,caller方
法也会把这个异常继续向上层传递。
这样逐层向上抛出异常,直到最外层的异常处理程序终止程序并打印出调用栈才会结束。
我们来看看异常信息栈的打印结果
从上面的异常栈信息中可以看出,Exception是在myMethod方法中被抛出的,从下往上看,调用层次依次是:
- main()调用caller();
- caller()调用myMethod();
- myMethod()抛出异常;
而且每层的调用都给出了源代码的行号,我们可以直接定位到产生异常的代码行,这样我们就可以根据异常信息
栈进行异常调试。
尤其是要注意,如果异常信息栈中有“Caused by: Xxx”这样的信息,说明我们捕获到了造成问题的根源。
但是有时候异常信息中可能并没有“Caused by”这样的信息,我们可以在代码中使用Throwable类的
getCause()方法来获取原始异常,
此时如果返回了null,说明已经是“根异常”了。这样有了完整的异常栈的信息,我们才能快速定位并修复代码的
问题。
另外我们还要注意,throw关键字不会单独使用,它的使用要符合异常的处理机制。
我们一般不会主动抛出一个新的异常对象,而是应该避免异常的产生。
2.3. 在try或catch中抛出异常
有些同学很善于思考,于是就提出了问题:如果我们在try或者catch语句块中抛出一个异常,那么finally语句还
能不能执行?
为了给大家解答清楚这个问题,再给大家设计一个案例。
public static void main(String[] args) {
try {
int a=100;
System.out.println("a="+a);
} catch (Exception e) {
System.out.println("执行catch代码,异常信息:"+e.getMessage());
//在catch中抛出一个新的运行时异常
throw new RuntimeException(e);
} finally {
System.out.println("非特殊情况,一定会执行finally里的代码");
}
}
我们直接来看上述代码的执行结果:
从上面的结果中可以看出,JVM会先进入到catch代码块中进行异常的正常处理,如果发现在catch中也产生了异
常,则会进入到finally中执行,finally执行完毕后,会再把catch中的异常抛出。所以即使我们在try或catch中抛
出了异常,也并不会影响finally的执行。
2.4. 异常屏蔽
但是这时有的小伙伴又提问了,如果我们在执行finally语句中也抛出一个异常,又会怎么样呢?
所以继续给大家进行论证,我们对上面的案例进行适当改造,在finally中也抛出一个异常。
public static void main(String[] args) {
try {
int a=100/0;
System.out.println("a="+a);
} catch (Exception e) {
System.out.println("执行catch代码,异常信息:"+e.getMessage());
//在catch中抛出一个新的运行时异常
throw new RuntimeException(e);
} finally {
System.out.println("非特殊情况,一定会执行finally里的代码");
//在finally中也抛出一个异常
throw new IllegalArgumentException("非法参数异常");
}
}
我们还是直接来看看执行结果:
从上面的结果中我们可以看出,在finally抛出异常后,原来catch中抛出的异常不见了。
这是因为默认情况下只能抛出一个异常,而之前那个没被抛出的异常称为“被屏蔽的异常(Suppressed
Exception)”。
2.5. 获取全部异常信息
但是有些较真的小伙伴就说,如果我就想知道所有的异常信息,包括catch中被屏蔽的那些异常信息,这该怎么
办?我们可以定义一个Exception的origin变量来保存原始的异常信息,然后调用Throwable对象中的
addSuppressed()方法,把原始的异常信息添加进来,最后在finally中继续抛出,并利用throws关键字抛出
Exception。
public class Demo07 {
//这里要利用throws关键字抛出Exception
@SuppressWarnings("finally")
public static void main(String[] args) throws Exception {
//定义一个异常变量,存储catch中的异常信息
Exception origin = null;
try {
int a=100/0;
System.out.println("a="+a);
} catch (Exception e) {
System.out.println("执行catch代码,异常信息:"+e.getMessage());
//存储catch中的异常信息
origin = e;
//抛出catch中的异常信息
throw e;
} finally {
System.out.println("非特殊情况,一定会执行finally里的代码");
Exception e = new IllegalArgumentException();
if (origin != null) {
//将catch中的异常信息添加到finally中的异常信息中
e.addSuppressed(origin);
}
//抛出finally中的异常信息,注意此时需要在方法中利用throws关键字抛出Exception
throw e;
}
}
}
此时的执行结果如下所示:
待更新...
从上面的结果中我们可以看出,即使catch和finally都抛出了异常时,catch异常被屏蔽,
但我们通过addSuppressed方法,最终仍然获取到了完整的异常信息。
但是我们也要知道,绝大多数情况下,都不应该在finally中抛出异常。
2.6. 重新抛出异常
在catch块内处理完后,可以重新抛出异常,异常可以是原来的,也可以是新建的,如下所示:
try{
//可能触发异常的代码
}catch(NumberFormatException e){
System.out.println("not valid number");
throw new AppException("输入格式不正确", e);
}catch(Exception e){
e.printStackTrace();
throw e;
}
对于Exception,在打印出异常栈后,就通过throw e重新抛出了。
而对于NumberFormatException,重新抛出了一个AppException,当前Exception作为cause传递给了
AppException,这样就形成了一个异常链,捕获到AppException的代码可以通过getCause()得到
NumberFormatException。
为什么要重新抛出呢?因为当前代码不能够完全处理该异常,需要调用者进一步处理。
为什么要抛出一个新的异常呢?当然是因为当前异常不太合适。不合适可能是信息不够,需要补充一些新信息;
还可能是过于细节,不便于调用者理解和使用,如果调用者对细节感兴趣,还可以继续通过getCause()获取到原
始异常。
3. throws声明异常
3.1. 简介
对于方法中不想处理的异常,除了可以利用throw关键字进行抛出之外,还有别的办法吗?
其实我们还可以在该方法的头部,利用throws关键字来声明这个不想处理的异常,把该异常传递到方法的外部进
行处理。
3.2. 基本语法
throws关键字的基本语法如下:
返回值类型 methodName(参数列表) throws Exception 1,Exception2,…{…}
通过这个语法,我们可以看出,在一个方法中可以利用throws关键字同时声明抛出多个异常:
Exception 1,Exception2,… 多个异常之间利用","逗号分隔。
如果被调用方法抛出的异常类型在这个异常列表中,则必须在该方法中捕获或继续向上层调用者抛出异常。
而这里继续声明抛出的异常,可以是方法本身产生的异常,也可以是调用的其他方法抛出的异常。
3.3. 代码实现
为了让大家理解throws关键字的用法,接下来继续设计一个案例进行说明。
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Demo08 {
public static void main(String[] args) {
try {
//在调用readFile的上层方法中进行异常的捕获
readFile();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// throws的用法--抛出了两个异常
public static void readFile() throws FileNotFoundException,IOException {
// 定义一个缓冲流对象,以后在IO流阶段壹哥会细讲
BufferedReader reader = null;
// 对接一个file.txt文件,该文件可能不存在
reader = new BufferedReader(new FileReader("file.txt"));
// 读取文件中的内容。所有的IO流都可能会产生IO流异常
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
}
}
在上面的案例中,我们在readFile方法中进行了IO流的操作,此时遇到了两个异常,但是我们不想在这里进行异常
的捕获,就可以利用throws关键字进行异常的声明抛出。
然后main()方法作为调用readFile的上层方法,就需要对异常进行捕获。
当然,如果main方法也不捕获这两个异常,该异常就会继续向上抛,抛给JVM虚拟机,由虚拟机进行处理。
但是我们在使用throws时要注意,子类在重写父类的方法时,如果父类的方法带有throws声明,子类方法声明中
的 throws异常,不能出现父类对应方法中throws里没有的异常类型,即子类方法拋出的异常范围不能超过父类定
义的范围。也就是说,子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或同类,子类方
法声明抛出的异常不能比父类方法声明抛出的异常多。
因此利用这一特性,throws也可以用来限制子类的行为。子类方法声明抛出的异常类型应该是父类方法声明抛出
的异常类型的子类或相同,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
3.4. throws执行逻辑
根据上面的案例执行结果,给大家总结一下throws关键字声明抛出异常的执行逻辑是:
- 如果当前方法不知道如何处理某些异常,该异常可以交由更上一级的调用者来处理,比如main()方法;
- 如果main()方法不知道该如何处理该异常,也可以使用throws关键字继续声明抛出,该异常将交给JVM去处理;
- 最终JVM会打印出异常的跟踪栈信息,并中止程序运行,这也是程序在遇到异常后自动结束的原因。
3.5. throw与throws的区别
由于throw和throws长得特别像,功能也有类似之处,为了不让大家产生迷惑,所以给大家总结一下throw和
throws的区别:
- throw关键字用来抛出一个特定的异常对象,可以使用throw关键字手动抛出异常,执行throw一定会抛出某种异常对象;
- throws关键字用于声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;
- throw需要用户自己捕获相关的异常,再对其进行相关包装,最后将包装后的异常信息抛出;
- throws通常不必显示地捕获异常,可以由系统自动将所有捕获的异常信息抛给上层方法;
- 我们通常在方法或类定义时,通过throws关键字声明该方法或类可能拋出的异常信息,
而在方法或类的内部通过throw关键字声明一个具体的异常信息。
3.6. 注意事项
我们在使用throws时也要注意如下这些事项:
- 只能在方法的定义签名处声明可能抛出的异常类型,否则编译器会报错;
- 如果一个方法声明了抛出异常,但却没有在上层的方法体中对抛出的异常进行处理或继续抛出该异常,编译器会报错;
- throws关键字只是声明方法可能抛出的异常类型,它并不一定真的会抛出异常;
- 如果一个方法中可能会有多个异常抛出,可以使用逗号将它们分隔,如throws Exception1, Exception2, Exception3等;
- 子类方法拋出的异常范围不能超过父类定义的范围。
4. 异常的捕获与处理
4.1. 认识异常
在程序开发中,程序的编译与运行是两个不同的阶段,编译主要针对的是语法检测,而在程序运行时却有可能出
现各种各样的错误,导致程序中断执行,那么这些错误在Java中统一称为异常。在Java中对异常的处理提供了非常
方便的操作。那么何为异常呢?
异常是指在程序执行时由于程序处理逻辑上的错误导致程序中断的一种指令流。
package edu.blog.test19.exception01;
public class ExceptionDemo01 {
public static void main(String[] args) {
System.out.println("程序开始");
System.out.println("10/2:" + (10 / 2));
System.out.println("程序结束");
System.out.println("===========================");
System.out.println("程序开始");
System.out.println("10/0:" + (10 / 0));
System.out.println("程序结束");
}
}
/*
执行结果:
程序开始
10/2:5
程序结束
===========================
程序开始
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception01.ExceptionDemo01.main(ExceptionDemo01.java:11)
*/
程序上半部分没有异常产生,程序会按照逻辑顺序执行完毕。
但是,程序后半部分产生了异常(ArithmeticException异常),由于程序没有对异常进行任何的处理,
所以默认情况下会进行异常信息的打印,同时终止执行对异常产生之后的代码(也就是说第二个“程序结束”不
会打印)。
4.2. 异常处理
由上我们发现,当程序产生异常后,若没有对其进行处理,程序则会出现中断执行的情况。
为了使程序在出现异常后,剩余代码依旧能正常执行,Java针对异常的处理提供有3个核心的关键词:
try、catch、finally,可以组成以下的异常处理格式。
try{
//有可能出现异常的语句
} catch {
//异常处理1
} catch {
//异常处理2
} ..... {
//异常处理n
} finally {
//异常的统一出口,无论是否产生异常,finally语句必执行
}
try语句中捕获可能出现的异常。若产生异常,程序自动跳转到catch语句中寻找到匹配的异常类进行相应的处理。
最后不管程序有没有产生异常,肯定会执行到finally语句。
注意:finally()块是可以省略的
若省略了finally()块,则程序在执行完catch()块结束后,程序将继续向下执行。
Tips:catch()块和finally()块都是可选的,但是不能同时省略。
所以异常的处理组合有以下的结构形式:try…catch、try…catch…finally、try…finally。
范例:异常处理
package edu.blog.test19.exception01;
public class ExceptionDemo02 {
public static void main(String[] args) {
System.out.println("程序开始");
try {
int result = 10 / 0;
System.out.println("数字计算:10 / 0 = " + result);
System.out.println("【try】---这里会执行吗?");
} catch (ArithmeticException e) {
System.out.println("【catch1】---这里会执行吗?");
e.printStackTrace();
System.out.println("【catch2】---这里会执行吗?");
} finally {
System.out.println("【finally】---这里会执行吗?");
}
System.out.println("程序结束");
}
}
/*
执行结果:
程序开始
【catch1】---这里会执行吗?
【catch2】---这里会执行吗?
【finally】---这里会执行吗?
程序结束
java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception01.ExceptionDemo02.main(ExceptionDemo02.java:7)
*/
本程序使用了try…catch…finally的异常处理格式,当程序中产生异常后,异常会被try语句捕获,而后交给catch
处理,通过异常类提供的printStackTrace()方法打印出异常信息,明确告诉用户异常在哪,为何有异常,最后将
finally代码块作为异常处理代码的出口。
由上程序范例我们可以发现:
- try语句块中,当出现异常后,异常后面的代码语句并不会执行(如,System.out.println("【try】—这里会执行吗?");)。
- catch语句块中,只要有匹配的异常处理,则里面所有的语句都会执行的。
- finally语句块中的语句都会执行,而且整个try…catch…finally结构后面的语句也都会执行。
Tips:finally语句的作用多余吗?
上述列子我们发现不管程序是否出现异常,异常处理后面的代码语句(如,System.out.println(“程序结束”);)
都会执行,那么finally语句的作用是不是有点多余?
答:当然不会啦,因为两者的执行机制本就不相同。因为此程序只是针对ArithmeticException异常,并不能对其
他异常进行处理。
倘若产生了其他的异常,程序依旧会中断执行,进而后面的代码也就不能正常执行,但是finally语句块中的语句依
然会执行。
我们对上面范例进行小修改,将ArithmeticException改为NullPointerException后再次测试。
package edu.blog.test19.exception01;
public class ExceptionDemo03 {
public static void main(String[] args) {
System.out.println("程序开始");
try {
int result = 10 / 0;
System.out.println("数字计算:10 / 0 = " + result);
System.out.println("【try】---这里会执行吗?");
} catch (NullPointerException e) {
System.out.println("【catch1】---这里会执行吗?");
e.printStackTrace();
System.out.println("【catch2】---这里会执行吗?");
} finally {
System.out.println("【finally】---这里会执行吗?");
}
System.out.println("程序结束");
}
}
/*
执行结果:
程序开始
【finally】---这里会执行吗?
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception01.ExceptionDemo03.main(ExceptionDemo03.java:7)
*/
通过两个范例的对比,是不是更能体会到两者的不同。
4.3. 异常的处理流程
Java中的异常处理流程下图1所示。
1、Java中只有在运行中产生的异常是可以处理的,当出现异常时,JVM会判断此异常的类型,并自动实例化相应
的异常类对象;
2、若程序中没有提供异常的处理方式,则会采用JVM的默认处理方式(打印异常信息),之后直接退出当前程序
(即程序中断执行);
3、try块语句会捕获产生异常的异常类的实例化对象,若是程序中存在异常处理,则将其与catch块中的异常类型
进行对比;
- 若类型相同,则使用此catch块语句对其进行异常处理;
- 若类型不相同,则继续匹配后续的catch类型,若是没有catch块匹配成功,则表示该异常无法进行处理。
4、不管程序有没有产生异常(或者说产生的异常是否被处理)都要执行finally语句; 5、执行完finally块后,程
序会再次判断当前的异常是否已经处理过了;
- 若处理过了,则继续执行后面的其他代码;
- 若没有处理过,则交由JVM进行默认处理。
Tips:一个小细节——处理多个异常时,捕获范围小的异常要放在捕获异常范围大的异常之前处理。
4.4. 异常类
Throwable这个Java类被用来表示任何可以作为异常被抛出的类。
Throwable对象可分为两种类型(指从 Throwable继承而得到的类型):
- Error用来表示编译时和系统错误(除特殊情况外,一般不用你关心);
- Exception是可以被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出 Exception型异常;
-
- RuntimeException:在编译期是不检查的,出现问题后,需要需要我们回来修改代码;
- 非RuntimeException:在编译期就必须处理的,否则程序不能通过编译。
所以我们一般关心的基类型通常是Exception。
4.5. throws和throw关键字
在Java中,抛出异常有三种形式,throws、throw以及系统自动抛异常。
- throws:方法可能抛出异常的声明
定义一个方法的时候可以使用throws关键字声明。
使用throws关键字声明的方法表示此方法不处理异常,而交给方法调用处进行处理。
package edu.blog.test19.exception02;
public class ThrowsTestDemo {
public static void main(String[] args) {
try {
Test.fun();
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
}
class Test {
public static void fun() throws ArithmeticException {
System.out.println(5 / 0);
}
}
/*
java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception02.Test.fun(ThrowsTestDemo.java:15)
at edu.blog.test19.exception02.ThrowsTestDemo.main(ThrowsTestDemo.java:6)
*/
本程序在主方法中调用了Test.fun()方法,由于此方法上使用了throws抛出了异常,这样在调用此方法时就必须明
确使用异常处理语句处理该语句可能发生的异常。
另外主方法本身也是方法,所以如果在主方法上使用了throws,则表示在主方法中可以不用强制进行异常处理。
如果出现了异常,将交给JVM进行默认处理,则此时会导致程序中断执行。
package edu.blog.test19.exception02;
public class ThrowsTestDemo {
public static void main(String[] args) throws ArithmeticException {
Test.fun();
System.out.println("我会执行吗??");
}
}
class Test {
public static void fun() throws ArithmeticException {
System.out.println(5 / 0);
}
}
/*
执行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at edu.blog.test19.exception02.Test.fun(ThrowsTestDemo.java:12)
at edu.blog.test19.exception02.ThrowsTestDemo.main(ThrowsTestDemo.java:5)
*/
1> throw:语句抛出一个异常
在默认情况下,所有的异常类的实例化对象都会由JVM默认实例化并且自动抛出。
但是Java提供了一个throw关键字,作用是抛出一个异常,抛出的时候是抛出的是一个异常类的实例化对象。
在异常处理中,try语句要捕获的是一个异常对象,那么此异常对象也可以自己抛出。
package edu.blog.test19.exception02;
public class ThrowTestDemo {
public static void main(String[] args) {
try {
throw new Exception("我就是自己抛异常,就是玩~");
} catch (Exception e) {
System.out.println("就是玩~");
e.printStackTrace();
}
}
}
/*
执行结果:
就是玩~
java.lang.Exception: 我就是自己抛异常,就是玩~
at edu.blog.test19.exception02.ThrowTestDemo.main(ThrowTestDemo.java:6)
*/
throw与throws的区别:
- throw:是在代码块中使用的,主要是手动进行异常对象的抛出;
- throws:是在方法中定义使用的,表示将此方法中可能产生的异常明确告诉调用处,由调用处进行处理。
4.6. assert关键字
断言(assert)是一种常见的软件功能,assert关键字是在JDK 1.4的时候引入的,其主要的功能是进行断言。
断言是执行到某行之后,其结果一定是预期的结果。
范例:断言的使用
package edu.blog.test19.exception04;
public class AssertTestDemo {
public static void main(String[] args) {
int x = 10;
//经过某些操作使x的值发生改变
assert x == 100 : "x的值并不是100";
System.out.println(x);
}
}
/*
Exception in thread "main" java.lang.AssertionError: x的值并不是100
at edu.blog.test19.exception04.AssertTestDemo.main(AssertTestDemo.java:7)
*/
注意:Java默认情况下是不开启断言的。
//增加“-ea”参数即可
java -ea edu.blog.test19.exception04.AssertTestDemo
Tips:IDEA开发工具专门增加参数呢?
第一步
第二步
4.7. try语句块中有return语句
之前我偶尔看到一道题,此题如下:
package edu.blog.test19.exception05;
public class TestDemo {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
}
public static int getValue() {
try {
return 0;
} finally {
return 1;
}
}
}
/*
return value of getValue():1
*/
再来举个例子:
package edu.blog.test19.exception05;
public class TestDemo {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
}
public static int getValue() {
int i = 0;
try {
return ++i;
} finally {
System.out.println("我还是会执行的");
}
}
}
/*
执行结果:
我还是会执行的
return value of getValue():1
*/
4.8. 总结
1> 如果try语句块中有return语句(finally语句块中没有),返回的是try语句块中的变量值。
执行过程大致如下:
- 若try语句块中有返回值,先把返回值保存到局部变量中(可以理解为打了断点进行标记);
- 执行jsr指令跳到finally语句中执行;
- 执行完finally语句块后,返回之前保存在局部变量中的值(可以理解为回到标记的断点处)。
2> 若try语句块、finally语句块中都有return语句,则忽略try的return,而使用finally的return。
3> 从1、2我们也看出,finally语句块中的语句是一定要执行的。
5. finally子句
5.1. 简介
有时候,不论异常是否出现或者是否被捕获,都希望执行某些代码。
Java有一个finally子句,可以用来达到这个目的。
也就是说,无论异常是否产生,finally子句总是会被执行的。
在任何情况下,finally块中的代码都会执行,不论try块中是否出现异常或者是否被捕获。
一般考虑下面三种可能出现的情况:
① 如果try块中没有出现异常,执行finalStatements,然后执行try语句的下一条语句。
② 如果try块中有一条语句引起异常,并被catch块捕获,然后跳过try块的其他语句,执行catch块和finally子句。
执行try语句之后的下一条语句。
③ 如果try块中有一条语句引起异常,但是没有被任何catch块捕获,就会跳过try块中的其他语句,执行finally子
句,并且将异常传递给这个方法的调用者。
即使在到达finally块之前有一个return语句,finally块还是会执行,而且finally 中的 return 会覆盖前面的 return.
使用finally子句时可以省略掉catch块。
5.2. 常见问题
问题一:try-catch-finally
当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,
则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决
这个问题
private static void readFile(String filePath) throws MyException {
File file = new File(filePath);
String result;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
} catch (IOException e) {
System.out.println("readFile method catch block.");
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
} finally {
System.out.println("readFile method finally block.");
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
问题二:try-with-resource
上面例子中,finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常
JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
}
try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,
若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。
被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,
可以调用 getSuppressed 方法来获取
七、异常API
1. 异常打印输出
异常对象包含关于异常的有价值的信息,可以利用下面这些java.lang.Throwable类中的实例方法获取有关异常的
信息。
- getMessage(): String:输出异常的描述信息
- getLocalizedMessage():输出本地化的描述信息,一般此方法可被子类所覆盖,缺省实现与getMessage()输出信息一致
- printStackTrace():将异常栈打印到输出流中,此为一类方法,默认打印到console控制台,也可以显式指定输出流。
- fillInStackTrace():将当前的异常栈保存到一个Throwable中,返回这个Throwable。大部分情况下,用在保留异常栈嵌套调用的情况,尝试保留完整的异常栈,无需使用该方法。
八、自定义异常
1. 示范1
这里我以分母为0异常举例 自定义一个分母为0异常类
1、首先,创建一个类,继承编译时异常类Exception,在这个类里面写一个无参的构造函数(这里我的这个例子
没必要传参,所以无参的就够了,有的例子需要参数,就需要构造有参的构造函数了)
代码如下:
public class ByZeroException extends Exception{
public ByZeroException(){
//super中的字符串代表抛出异常时打印的信息
super("分母不能为0!");
}
}
2、创建一个测试类,写一个计算分子除以分母的方法,这里要传入两个参数,分别代表分子和分母,在方法中判
断分母是否为0,如果不为零,则打印分子除以分母的结果。如果分母等于0,则需要抛出异常。
public void FenShuOperation(int fenzi,int fenmu) throws ByZeroException {
if (fenmu > 0 || fenmu < 0){
System.out.println(fenzi / fenmu);
}else {
throw new ByZeroException();
}
}
3、现在就要测试了,在测试类中写一个main方法,在main方法中测试。首先要实例化我们上面的计算分子除以
分母的方法,这时会出现异常,因为我们要调用这个方法,所以我们必须要对这个异常进行处理,就是用try-
catch来捕获异常,剩下的就很简单了,从键盘输入分子和分母,然后用刚刚实例化的对象调用计算分子除以分母
的方法,并将从键盘输入的分子和分母传进去就OK啦。
代码如下:
public static void main(String[] args) {
//用try-catch语句捕获异常
ByZeroTest byZeroTest = new ByZeroTest();
try {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入分子:");
int fenzi = scanner.nextInt();
System.out.println("请输入分母:");
int fenmu = scanner.nextInt();
byZeroTest.FenShuOperation(fenzi,fenmu);
} catch (ByZeroException e) {
e.printStackTrace();
}
}
2. 示范2
Java本身已经提供了大量的异常类,但是这些异常类也许并不够用,这时你就需要自定义异常类了。
自定义异常类只需要继承Exception(强制性处理异常)或者RuntimeException(选择性异常处理)父类即可。
范例:实现自定义异常
package edu.blog.test19.exception03;
public class ScoreException extends Exception{
public ScoreException(){}
public ScoreException(String msg){
super(msg);
}
}
package edu.blog.test19.exception03;
public class Teacher {
public void checkSore(int score) throws ScoreException {
if (score < 0 || score > 100) {
throw new ScoreException("你给的分数有误,分数应该在0~100之间");
} else {
System.out.println("成绩正常");
}
}
}
package edu.blog.test19.exception03;
public class CustomExceptionTestDemo {
public static void main(String[] args) {
int score = 120;
Teacher teacher = new Teacher();
try {
teacher.checkSore(score);
} catch (ScoreException e) {
e.printStackTrace();
}
}
}
/*
执行结果:
edu.blog.test19.exception03.ScoreException: 你给的分数有误,分数应该在0~100之间
at edu.blog.test19.exception03.Teacher.checkSore(Teacher.java:6)
at edu.blog.test19.exception03.CustomExceptionTestDemo.main(CustomExceptionTestDemo.java:8)
*/
3. 示范3
Java 本身已经提供了大量的异常,但是这些异常在实际的工作中可能并不够使用,例如:当你要执行数据增加操
作时,有可能会出现一些错误的数据,而这些错误的数据一旦出现就应该抛出异常(如 AddException), 但是这样
的异常 Java 并没有,所以就需要由用户自己去开发一个自己的异常类。
如果要想实现自定义异常类,只需要继承 Exception (强制性异常处理) 或 RuntimeException (选择性异常处理)
父类即可。
// 范例 4:定义及使用AddException
package com.xiaoshan.demo;
class AddException extends Exception { //此异常类要强制处理
public AddException(String msg){
super(msg); //调用父类构造
}
}
public class TestDemo {
public static void main(String args[]){
int num = 20;
try{
if (num>10){ //出现了错误,应该产生异常
throw new AddException("数值传递的过大!");
}
} catch (Exception e){
e.printStackTrace();
}
}
}
程序执行结果:
com.xiaoshan.demo.AddException: 数值传递的过大!
at com.xiaoshan.demo.TestDemo.main(TestDemo. java:13)
此程序使用一个自定义的 AddException 类继承了 Exception, 所以此类为一个异常表示类,因此用户就可以在程
序中使用 throw 进行异常对象的抛出。
如果用户要自己做一个项目的开发架构,肯定会使用到自定义异常类的操作。例如:现在要求用户自己输入注册
信息,但是注册的用户名长度必须是 6~15位,超过此范围就要抛出异常,然而这样的异常肯定不会由Java默认提
供,那么就需要用户自己进行定义,像以后大家学习Mybatis、Spring 等框架时会遇见大量的新的异常类,都是
按此格式定义出来的。
九、面试题
1. 什么时候使用异常?什么时候使用断言?
1.1. 简介
断言表示程序写错了,只要发生断言(更正:此处应为断言失败),意味着至少有一个人得修改代码。它的性质
如同编译错误。
例如一个函数规定某输入参数非空,来个断言。
如果调用者送了空参数触发断言失败,要么调用方改代码不传空参数,要么被调用方改代码允许空参数处理。
如果代码书写完全正确,但因外界环境或者用户操作仍然可能发生的事件,都不适合用断言,可以使用异常,或
者条件判断处理。
1.2. 总结
非致命错误用异常,致命错误用断言
2. Java异常-Error和Exception的区别
2.1. Error
Error类对象由 Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
例如,
Java虚拟机运行错误(VirtualMachineError),当JVM不再有继续执行操作所需的内存资源时,将出现
OutOfMemoryError。
这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误
(NoClassDefFoundError)、链接错误(LinkageError)。
这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状
况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。
在Java中,错误通常是使用Error的子类描述。
2.2. Exception
在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序
定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、
ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException
(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程
序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度
讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定
义的Exception异常,一般情况下不自定义检查异常。
- Error:程序无法处理系统异常,编译器不做检查。
- Exception:程序可以处理的异常,捕获后可能恢复。
- 总结:前者是程序无法处理的错误,后者是可以通过程序处理的异常。
package com.kuangstudy.kuangstudyjavajob;
import java.io.IOException;
public class ErrorAndException {
public void throwError(){
throw new StackOverflowError();
}
// 运行时异常
public void throwRumtimeException(){
throw new RuntimeException();
}
// 检查时异常
public void throwCheckException(){
try {
throw new IOException();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 常见Error以及Exception
RuntimeException
- NullPointerException:空指针异常
- ArithmeticException算术异常类
- ArrayIndexOutOfBoundsException数组下标越界异常
- NegativeArraySizeException数组负下标异常
- ClassCastException类型强制转换异常
- StringIndexOutOfBoundsException
# 指示索引或者为负,或者超出字符串的大小,抛出异常;
"hello".indexOf(-1);
- IllegalArgumentException参数异常
抛出的异常表明向方法传递了一个不合法或不正确的参数 - NumberFormatException数字格式异常
- ClassNotFoundException找不到类异常
- dNoSuchMethodException 方法未找到异常
- FileNotFoundException 文件未找到异常
Error
- NoClassDefFoundError 找不到class定义异常类
- StackOverflowError 深度递归导致栈被耗尽而抛出的异常
- OutOfMemoryError 内存异常异常
4. Java异常处理的机制
1、抛出异常:创建异常对象,交由运行时系统处理
2、捕获异常:寻找合适的异常处理器处理异常,否则终止运行
5. Java异常处理的原则
1、具体明确:抛出的异常应能通过异常类名和Message准备说明异常的类型和产生异常的原因
2、提早抛出:应尽可能早的发现并抛出异常,便于精准定位问题
3、延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常。
6. Java异常中return和finally的关系
eturn的位置\允许return出现的位置(能否同时出现) | return | return | return |
catch | √ | √ | |
finally | √ | ||
finally后 | √ |
1. finally块在return语句执行之后,return语句返回之前执行
2. finally块中的return语句返回结果会覆盖try块的return语句返回结果
即如果try中有return语句,finally中也有return语句,最终执行的是finally中的return语句(无论是基本数据类
型or引用数据类型)
即如果try中有return语句,finally中也有return语句,最终执行的是finally中的return语句(无论是基本数据类
型or引用数据类型)
3. finally块中的return语句返回结果会覆盖catch块中的return语句返回结果
即如果try中有return语句,catch中也有return语句,finally中也有return语句,
最终执行的是finally中的return语句(无论是基本数据类型or引用数据类型)
try中发生异常之后,进入catch块,return语句执行完之后进入finally块,finally块执行完之后,执行return返回
语句
4. try块中发生异常,try块中异常语句后的内容不会执行(return语句自然不会执行),执行的是捕获到异常的
catch语句块和finally语句块
5. 如果finally块没有return语句,那么原来的返回值因为finally里的修改可能改变也有可能不变
对于基本数据类型来说,finally中对返回值的修改不会影响try中return返回变量的值
(解释:try或catch中的return要返回的值在finally执行之前会拷贝一份)
对于引用数据类型来说,finally中对返回值的修改(修改引用变量指向对象中的值)会影响try中return返回变量
的值
对于引用数据类型来说,finally中对返回值的修改(修改引用变量的地址值)不会影响try中return返回变量的值
(解释:try或catch中的return要返回的值在finally执行之前会拷贝一份)
6. try中发生异常时,return语句写在catch块中
try中发生异常之后,进入catch块,return语句执行完之后进入finally块,finally块执行完之后,执行return返回
语句
对于基本数据类型来说,finally中对返回值的修改不会影响try中return返回变量的值
(解释:try或catch中的return要返回的值在finally执行之前会拷贝一份)
对于引用数据类型来说,finally中对返回值的修改(修改引用变量指向对象中的值)会影响try中return返回变量
的值
对于引用数据类型来说,finally中对返回值的修改(修改引用变量的地址值)不会影响try中return返回变量的值
(解释:try或catch中的return要返回的值在finally执行之前会拷贝一份)
7. finally语句一定会被执行吗?
当程序进入try语句之前就出现异常时,会直接结束
try语句块中有强制退出时也不会执行finally语句块中的代码
3. 企业开发中,如何处理异常?
待更新...