Java异常机制
什么是异常
- 异常指程序运行中出现的不期而至的各种状况,如文件找不到、网络连接失败、非法参数等
- 异常发生在程序运行期间,它影响了正常的程序执行流程
掌握以下三种类型的异常
- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略
- 运行时异常:运行时异常是可以被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时被忽略
- 错误(ERROR):错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略。例如当栈溢出时,一个错误就发生了,它们在编译也检查不到
异常体系结构
- Java把异常当做对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类
- 在JavaAPI中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

图片来源于:https://blog.csdn.net/qq_15349687/article/details/82811581
Java所有异常类都是 Throwable的子类。它包括Java异常处理的两个重要子类:Error和Exception。
Error
- Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关,而是表示代码运行时 JVM出现的问题。
- Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止
- 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误都是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况
Exception
在Exception分支中有一个重要的子类叫RuntimeException(运行时异常),常有的异常有:
- ArrayIndexOutOfBoundsException(数组下标越界)
- NullPointerException(空指针异常)
- ArithmeticException(算数异常)
- MissingResourceException(丢失资源)
- ClassNotFoundException(找不到类)
这些异常是不检查异常,而当程序中可能出现这类异常,即使没有用try-catch 语句捕获它,也没有用throws 语句声明抛出它,也会编译通过。程序中可以选择捕获处理,也可以不处理
这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这些异常的发生
注意:RuntimeException 异常会由JVM自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!)出现异常,不要紧张,把异常的简单类名,拷贝到API中去查,然后看是什么异常,看是什么原因,找到我们程序出错的地方并进行修改就可以正常运行了。
Error和Exception的区别
- Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程
- Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常
异常处理机制
在 Java 应用程序中,异常处理机制为:抛出异常,捕获异常。

图片来源于:https://zhuanlan.zhihu.com/p/450346339
-
异常处理五个关键字:try、catch、finally、throw、throws
-
捕获异常:try、catch、finally
-
抛出异常:throw
-
声明异常:throws
try:用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
catch():括号内输入的是想要捕获的异常类型,最高的异常类型是Throwable。catch用来捕获try语句块中发生的异常。
finally:无论结果怎样,都会执行finally中的代码,可以不用写finally,但是遇到IO流,资源调用时,可以通过finally最后来关闭close运行
捕获异常:try...catch...finally
例子:运算1除以0

运行后出现异常:算数异常

可以通过try、catch、finally捕获异常:
try-catch-finally 形式的定义格式:
try {
// 可能会发生异常的程序代码
} catch (异常类型A e){
// 捕获并处置try抛出的异常类型A
} finally {
// 无论是否发生异常,都将执行的语句块
}

再次运行:

注意:如果try字块部分的语句出现异常,会跳过而不会执行该部分语句,直接执行catch部分的语句;如果try部分语句没有出现异常的话,就执行try部分语句,不会执行catch部分语句;finally部分语句不管怎样都会执行
但也存在以下4种特殊情况,finally块不会被执行:
- 在前面的代码中使用了System.exit()退出程序;
- 在finally语句块中发生异常;
- 程序所在的线程死亡;
- 关闭CPU。
而如果有多个异常使用捕获我们又该如何处理呢?
- 多个异常分别处理。
- 多个异常一次捕获,多次处理。
一般我们是使用一次捕获多次处理方式,格式如下:
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
...
}

运行结果:

注意:一次捕获,多次处理的异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,异常从小到大地写,最后一个catch()要写最大一个异常,否则会报错,而且只执行一个catch。
小技巧:可以在idea使用快捷键Ctrl + Alt + T快速生成捕获异常,选中所需要捕获异常的语句,然后按下快捷键

栈轨迹
Stacktrace直译过来就是堆栈跟踪的意思。
printStackTrace方法所提供的信息可以通过getStackTrace方法来直接访问,这个方法将返回一个由栈轨迹中的元素构成的数组。其中每一个元素都表示栈中的一帧。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。

抛出异常throw 和 声明异常throws
throw:通常用在方法体中或者用来抛出用户自定义异常,并且抛出一个异常对象。程序在执行到throw语句时,后面的语句块不再执行,如果要捕捉throw抛出的异常,则必须使用try-catch语句块或者try-catch-finally语句。
定义格式:
throw new 异常类名(参数);
throws:通常被用在声明方法时,用来声明方法可能抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分隔。throws关键字将异常抛给上一级,如果不想处理该异常,可以继续向上抛出,谁调用我我就抛给谁,但最终要有能够处理该异常的代码。
定义格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1[,异常类名2…]{ }

像上面这个例子,如果不用try..catch的话,程序就停止了,用了的话,程序会正常执行,这是个好处,能在知道有异常时先正常执行程序
throws和throw的区别?
throws:用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁调用我我就抛给谁。
- 用在方法声明后面,跟的是异常类名
- 可以跟多个异常类名,用逗号隔开
- 表示抛出异常,由该方法的调用者来处理
- throws表示出现异常的一种可能性,并不一定会发生这些异常
throw:则是用来抛出一个具体的异常类型。
- 用在方法体内,跟的是异常对象名
- 只能抛出一个异常对象名
- 表示抛出异常,由方法体内的语句处理
- throw则是抛出了异常,执行throw则一定抛出了某种异常
如何选择异常类型
可以根据下图来选择是捕获异常,声明异常还是抛出异常

我们在日常处理异常的代码中,应该遵循的原则:
- 不要捕获类似Exception 之类的异常,而应该捕获类似特定的异常,方便排查问题,而且也能够让其他人接手你的代码时,会减少骂你的次数。
- 不要生吞异常。这是异常处理中要特别注重的事情。如果我们不把异常抛出来,或者也没有输出到日志中,程序可能会在后面以不可控的方式结束。有时候需要线上调试代码。
自定义异常
- 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常,只需继承Exception类即可
- 在程序中使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常
- 在方法中通过throw关键字抛出异常
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理,否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作
- 在出现异常方法的调用者中捕获并处理异常
例如,自定义一个异常

再写一个可能会出现异常的一个方法

输出的结果为自定义异常

实际应用中的经验总结
- 处理运行时异常时,采用逻辑去合理规避,同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
- 对于不确定的代码,也可以加上try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出错误的栈信息
- 具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源。比如IO,Scanner等,用完要记得关闭
此学习资料参考于

浙公网安备 33010602011771号