【传智播客.黑马程序员训练营成都中心】
Java异常浅析
前言
异常二字,从字面上来理解就是:不正常。程序中的异常,就是程序在运行的过程中出现了不正常的情况。对于广大程序员来说,无论是菜鸟新手,还是编程大牛,程序出现各种各样的问题都是在所难免的事。Java语言作为一门成熟的语言,为了应对程序中可能出现的问题,也相应的提供了异常处理机制。本文将针对Java中异常的几个方面进行讨论分析。
- 异常出现的原因
异常出现的原因非常多,有的是因为用户操作上的错误引起,有的是程序错误引起的,还有其它一些是因为物理原因引起的。我们大致可以将异常出现的原因归为以下两种:
- 物理层面的原因:网络连接失败,要访问的文件找不到、内存溢出等。
- 代码层面的原因:传入的非法的数据值,引用类型变量为null调用方法导致空指针等。
注:不符合Java语法导致编译失败的问题不在今天异常的讨论范围
上述问题中,有些是作为程序员难以处理的,但是大部分都是可以利用Java给我们提供的异常处理机制对程序进行优化。接下来咱们来了解Java中的异常分类。
- Java异常分类
JAVA中万物皆对象,对于异常来说,也是对象,我们先来看Java异常的体系结构。
-
2.1 Java异常体系结构
Throwable 异常体系的根类
——Error "错误"类
——Exception "异常"类
—— ... 编译期异常类(太多了,省略)
—— RuntimeException 运行时异常类
解释:- Throwable是异常体系的根类,其类中有一些获取异常信息相关的方法
- Error类是一些诸如内存溢出、虚拟机错误等,程序不应该试图捕获的严重问题,不作重点讨论
. Exception是我们讨论的重点,因为其适合被程序处理。
. RuntimeException运行时异常类,其本身和其所有的子类都是运行时异常类。 - 在Exception中还有一部分类不是RuntimeException的子类,这些类则是编译期异常类
我们重点讨论Exception中的异常两大类:编译期异常和运行时异常。
-
2.2 编译期异常
- 众所周知的一件事:一个Java程序从源程序到运行起来要经历编译和运行两个步骤。那么如果在编译阶段出现的异常情况导致编译失败(语法错误不作讨论),我们就可以理解为此异常为编译期异常。
例如:
import java.io.FileWriter;
public class Demo {
public static void main(String[] args) {
FileWriter fw = new FileWriter("a.txt");
}
}
此段代码中,第四行出现编译时异常,提示此行代码必须捕获或者声明IOException。由于没有对编译期异常进行任何处理,所以编译失败了。
- 众所周知的一件事:一个Java程序从源程序到运行起来要经历编译和运行两个步骤。那么如果在编译阶段出现的异常情况导致编译失败(语法错误不作讨论),我们就可以理解为此异常为编译期异常。
-
2.3 运行时异常
- 如果程序在编译的时候没有出现任何问题,但是在运行的时候却出现了异常,这些异常大部分都是运行时异常。
例如:
public class Demo {
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr[5]);
}
}
- 如果程序在编译的时候没有出现任何问题,但是在运行的时候却出现了异常,这些异常大部分都是运行时异常。
控制台输出:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
此段代码编译期是没有出现问题的,不过在代码运行起来之后却出问题了,这就是一个典型的运行时异常。
由于此代码中数组的索引值最大为2,但是输出语句中却访问了数组的5索引,所以会产生数组索引越界异常。
-
2.4 两大类异常的区别
- 编译期异常和运行时异常其实最本质的区别就是:在代码编译期是否强制性要求给出处理方案
- 编译期异常在编译期必须处理才能使得代码编译通过。
- 运行时异常在编译期可以选择性处理,如果不处理也不会影响代码的编译。
注意- 编译期异常要求我们在编译期必须处理异常,但是不意味着这个时候异常已经产生了只是强制性要求给出预处理方案。
- 就像入职一家公司的时候要求签订合同,合同中的条款可能会写出违约的惩罚措施,
但是签订了合同并不意味着你已经违约了,而是为了防止违约行为造成损失。
我们已经大致清楚了Java异常的分类,接下来就来聊聊Java对于异常给出了怎样的处理机制。
- 编译期异常和运行时异常其实最本质的区别就是:在代码编译期是否强制性要求给出处理方案
- Java异常机制
- 首先我们要知道的是:
- 前面我们已经提到的一个问题,Java中万物皆对象,其实异常出现也是以对象的形式出现。
- 当代码出现问题的时候,JVM会帮我们产生一个异常对象。
- 我们所说的处理异常其实就是处理异常对象。
我们可以把异常认为是一只小老鼠,把出现异常的那个方法认为是一个房间,那么你的处理方案就只有两种:
- 自己把它捕捉起来
- 把它丢到别的房间让别人捕捉
- 3.1 捕获异常
- 捕获异常就是将异常这只小老鼠抓起来,Java中对于异常的捕获有特定的捕获格式:
try {
// 可能出现异常的代码
} catch(异常的类型 变量名) {
// 处理方案
}- 解释:
- 将可能出现异常的代码放在try关键字对应的代码块中
- 如果此段代码出现了异常对象,则会将异常对象和catch后小括号上的类型进行匹配,如果匹配成功则会将对象赋值给对应的变量
- catch对应的大括号中可以对出现的异常给出处理。
例如 :
try {
// 此段为可能出现异常的代码
int[] arr = new int[3];
System.out.println(arr[5]);
} catch (ArrayIndexOutOfBoundsException ae) { // catch小括号中为需要捕获异常的类型
// 此段代码为异常出现后给出的处理方案:对异常给出提示
System.out.println("出现了索引越界异常");
}
当出现索引越界异常后,会匹配上catch后面异常的类型,并将对象赋值给变量ae
程序的执行会走到catch大括号中,执行输出语句
注意: - 如果程序没有出现异常,那么catch中的代码是不会被执行的。
- 如果try大括号中的代码出现了异常,那么会立即产生异常对象和catch中异常类型匹配,此行异常代码到try右大括号中的这些代码是不会再执行了。
- 如果我们在一段代码中需要处理多个异常,那么可以通过添加多个catch块的方式进行处理
- 多个异常处理中,如果有子父类关系,父类异常类型应当最后处理,否则会让后续的子类处理永远执行不到。(由于篇幅限制此处不演示)
- 捕获异常就是将异常这只小老鼠抓起来,Java中对于异常的捕获有特定的捕获格式:
- 3.2 抛出异常
有的时候,异常出现了但是这个异常的处理逻辑不应该出现在当前方法中,那么这个时候我们可以选择将异常抛出到该方法以外,让方法的调用者去处理。
-
异常抛出的格式:
修饰符 返回值类型 方法名(参数列表) throws 异常的类型 {}
例如 :
public class Demo {
public static void main(String[] args) throws ArrayIndexOutOfBoundsException {
int[] arr = new int[3];
System.out.println(arr[5]);
}
}
当出现异常后,会产生一个异常对象,这个异常对象会被抛给方法的调用者
由于main方法是JVM调用的,所以此代码会被抛给JVM去处理。
注意:
如果是运行时异常,而我们又没有对异常进行任何手动处理,其实这个时候默认异常是会进行抛出处理的。
- 3.3 默认处理
- 在之前的案例中我们没有处理过出现的异常,其实JAVA已经对异常给出了默认的处理:
- 首先异常产生后JVM会帮我们创建一个异常对象
- 由于我们没有对异常进行任何处理,所以默认抛出异常对象,抛给方法的调用者
- 最终方法调用的源头会走向JVM,JVM给出了它默认的处理方案,具体如下:
- 终止此线程的代码执行。
- 并将异常对象的信息打印到控制台。这些信息包括:异常的位置,类型,原因等。
- 在之前的案例中我们没有处理过出现的异常,其实JAVA已经对异常给出了默认的处理:
- 3.4 小总结
最后我们来总结异常的处理:- 何时需要处理:
- 对于运行时异常:
- 一般对于容易出现和操作相关问题的代码——比如有可能传递错误数据等——我们应当给出异常处理方案
- 对于编译期异常:
- 必须处理,别无选择
- 对于运行时异常:
- 如何选择方案:
- 如果异常处理逻辑代码应该出现此方法中,那么该异常的处理应该选择捕获异常
- 如果此方法中不能处理,或者不应该在此处处理,那么应该选择抛出异常
- 何时需要处理:
- 自定义异常
最后我们来讨论一个可能引起人神共愤的问题:自定义异常
-
4.1 为什么要自定义异常
- 其实这个问题不应该人神共愤,我们在写代码的过程中确实因为异常的问题有些脑壳疼,但是大家想象一下,如果没有异常,你的代码出现了问题,你该如何去排查处理?
- 答案是:你可能会很难定位问题所在,进而你也没有办法去预防一些可能出现的问题。
- 而自定义异常则是可以让我们代码出现问题的时候及时给出程序员一些错误提示,以便更快的排查问题以及处理问题。
-
4.2 如何自定义异常
-
我们之前已经讨论了,其实异常本质就是一个异常类的对象,那么想产生自定义异常,我们就需要:
- 定义异常类。
- 并在合适的位置创建该异常对象,并抛出这个异常对象给方法的调用者。
案例演示 :
- 并在合适的位置创建该异常对象,并抛出这个异常对象给方法的调用者。
- 假设我们要定义一个方法判断传入的成绩是否合法,如果传入了非法数据则抛出异常。
- 那么我们先定义这个异常类
/**自定义异常类,当继承了RuntimeException,说明这个自定义异常是一个运行时异常
-
如果想自定义的异常是编译期异常,应该继承Exception
*/
public class IllegalScoreException extends RuntimeException {
// 无参构造
public IllegalScoreException() {}// 带一个字符串参数的构造,用于将异常产生的原因message赋值给父类的成员变量
public IllegalScoreException(String message) {
super(message);
}
}
- 在需要抛出异常的代码出用throw关键字抛出一个异常对象。(注意区分异常处理方案中抛出异常的关键字:throws)
public static void checkScore(int score) { if (score < 0 || score > 100) { throw new IllegalScoreException("成绩"+score+"不符合要求"); } }
注意:
如果想产生的异常属于编译期异常,除了用throw关键字产生异常外,还必须在方法声明上对异常进行抛出(throws)处理。
-
总结
到目前为止关于Java异常的大部分内容都已经展示给大家了,希望此文对大家学习异常会有所帮助,下次再见!