异常简介
Java中的异常(Exception)又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效地处理程序中的运行错误,需要使用异常类。
java异常的出现为了识别和响应错误而出现的,它是一种一致性机制。
要理解异常处理是如何工作的,需要掌握三种类型的异常:
- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
体系结构
Throwable
Throwable 是 Java 语言中所有错误与异常的超类。提供printStackTrace()方法去输出异常信息。它包含两个子类:Error(错误)和 Exception(异常)。
Error(错误)
定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
特点:由JVM生成并抛出,大多数错误与代码编写者所执行的操作无关。通常有 Virtual MachineError(虚拟机运行错误)、 OutOfMemoryError(内存不足错误)、NoClassDefFoundError(类定义错误)、StackOverflowError(栈溢出错误)、LinkageError(链接错误)等错误。此类错误发生时,JVM一般会选择线程终止。错误属于非受检异常(unchecked exception)。对于Error,我们做简单了解,我们需要重点了解的是Exception。
Exception(异常)
异常分为两大类:运行时异常和编译时异常。
运行时异常
定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。也可称非受检异常(unchecked exception)。
特点:Java 编译器不会检查它。也就是说编译通过,但程序运行时可能出现的异常。一般是由程序逻辑错误引起的,此类异常一般需要我们主要改进代码。比如ArrayIndexOutBoundException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticExecption(算术异常)、ClassCastException(类型转换异常)等。
编译时异常
定义: Exception 中除 RuntimeException 及其子类之外的异常。也可称受检异常(checked exception)。
特点: Java 编译器会检查它。此类异常必须自己手动捕获或抛出异常,不然编译无法通过。比如 ClassNotFoundException(不能加载所需的类),IOException(IO流异常)等。
异常处理
异常处理通常分为:声明异常、抛出异常和捕获异常。
关键字
Java 的异常处理有5个关键词:try、catch、finally、throw、throws。
- try -- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
- catch -- 用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally -- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw -- 用于抛出异常。
- throws -- 用在方法签名中,用于声明该方法可能抛出的异常。
声明异常
通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
非受检异常不可使用 throws 关键字来声明要抛出的异常。 一个方法出现编译时异常,就需要 try-catch或者throws 处理,否则会导致编译错误。通过throws声明异常时,调用者需要使用try-catch处理或throws继续向上抛出异常。
throws处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛 给方法的调用者来处理。
格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型 {
}
抛出异常
使用throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。
if(obj==null){
throw new NullPointerException();
}
特点:
- throw必须写在方法体内部
- 如果抛出的是编译时异常,用户就必须要处理,否则无法通过编译
捕获异常
java中使用try -catch来捕获知道如何处理的异常,finally块可选,表示一般用在关闭io流,关闭数据库连接等方法。
try{
// 程序代码
}catch(异常类型1 异常的变量名1){
// 程序代码
}catch(异常类型2 异常的变量名2){
// 程序代码
}finally{
// 程序代码
}
对于所有的受检查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉检查异常时,它必须声明将抛出异常。而对于运行时异常,java运行时会由系统自动抛出,可以选择性得进行处理或忽略。
【注】
- try关键字用来包围可能会出现异常的逻辑代码,它单独无法使用,必须配合catch或者finally使用。Java编译器允许的组合使用形式只有以下三种形式:try...catch...; try....finally......; try....catch...finally...
- 当然catch块可以有多个,注意try块只能有一个,finally块是可选的(但是最多只能有一个finally块)。
- 三个块执行的顺序为try—>catch—>finally。
- 当然如果没有发生异常,则catch块不会执行。但是finally块无论在什么情况下都是会执行的(这点要非常注意,因此部分情况下,都会将释放资源的操作放在finally块中进行)。
- 在有多个catch块的时候,是按照catch块的先后顺序进行匹配的,一旦异常类型被一个catch块匹配,则不会与后面的catch块进行匹配。
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常,只需继承Exception类即可。
在程序中使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类。
- 在方法中通过throw关键字抛出异常对象。
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
- 在出现异常方法的调用者中捕获并处理异常。
代码示例
//自定义异常 继承Exception
public class MyException extends Exception {
private String msg;
MyException(String msg) {
this.msg = msg;
}
//重写toString方法
@Override
public String toString() {
return "MyException [" + msg + "]长度不能小于8位";
}
}
public class Test {
//通过throws声明异常,需要调用者处理
public static void valid(String msg) throws MyException{
System.out.println("【" + msg + "】");
if (msg.length() < 8) {
//抛出自定义异常
throw new MyException(msg);
}
System.out.println("正常");
}
//继续抛出异常
public static void main(String[] args) throws MyException{
valid("asdzzzqq");
valid("asdxxx");
}
}
输出结果
【asdzzzqq】
正常
【asdxxx】
Exception in thread "main" MyException [asdxxx]长度不能小于8位
at com.lsy.gui.exception.Test.valid(Test.java:9)
at com.lsy.gui.exception.Test.main(Test.java:16)
当把异常代码方法前面时,程序会提前终止,后续正常代码不会执行。
public static void main(String[] args) throws MyException{
valid("asdxxx");
valid("asdzzzqq");
}
输出结果
【asdxxx】
Exception in thread "main" MyException [asdxxx]长度不能小于8位
at com.lsy.gui.exception.Test.valid(Test.java:9)
at com.lsy.gui.exception.Test.main(Test.java:15)
我们使用try-catch来捕获异常
public class Test {
public static void main(String[] args){
try {
valid("asdxxx");
}catch (MyException e){
System.out.println(e.toString());
}finally {
System.out.println("测试finally代码块执行");
}
try {
valid("asdzzzqq");
}catch (MyException e){
System.out.println(e.toString());
return;//先执行finally再return
}finally {
System.out.println("测试finally代码块执行");
}
}
}
输出结果
【asdxxx】
MyException [asdxxx]长度不能小于8位
测试finally代码块执行
【asdzzzqq】
正常
测试finally代码块执行
【注】如果两个方法包含在同一个try-catch块,当先执行valid("asdxxx");时,程序会捕获异常并终止,不会再执行valid("asdzzzqq");
public class Test {
public static void main(String[] args){
try {
valid("asdxxx");
valid("asdzzzqq");
}catch (MyException e){
System.out.println(e.toString());
}finally {
System.out.println("测试finally代码块执行");
}
}
}
输出结果
【asdxxx】
MyException [asdxxx]长度不能小于8位
测试finally代码块执行
总结
1、处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
2、在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
3、对于不确定的代码,也可以加上try-catch,处理潜在的异常
4、尽量去处理异常,切记只是简单的调用printStackTrace()去打印
5、具体如何处理异常,要根据不同的业务需求和异常类型去决定
6、尽量添加finally语句块去释放占用的资源