Java基础-异常
1. 异常概述
1.1. 什么是异常
Java程序在编译或运行过程中出现的问题就称为异常。
1.2. 异常的继承体系
Throwable
Error:错误
- 错误:一出现就是致命的,服务器宕机,数据库崩溃等
Exception:
- 运行时异常
- 编译时异常
无论是错误还是异常,它们都有具体的子类体现每一个问题,它们的子类都有一个共性,就是都以父类名才作为子类的后缀名
1.3. 异常的分类
1.3.1. 运行时异常
RuntimeException(运行时异常):一般都是程序员犯得错误,需要修改源码的
1.3.2. 编译时异常
编译时异常:在编译时必须进行处理,不处理无法通过编译
1.3.3. 四个常见的运行时异常
- ArithmeticException
- NullPointerException
- ArrayIndexOutOfBoundsException
- StringIndexOutOfBoundsException
1.4. 异常与错误的区别
异常
- 在编译或运行过程中出现的问题就称为异常
- 可以针对异常进行处理,处理后,后续的代码可以继续运行。如果不处理,否则程序直接崩溃
错误
- 程序在运行过程中出现的问题。
- 错误一般是由系统产生并反馈给JVM。
- 没有具体的处理方式,只能修改错误的代码。否则程序直接崩溃,无法运行
如何判断程序出现的问题是错误还是异常?
- 根据控制台输入的错误信息,判断类名是以Error还是Exceptiong结尾。
- 如果是Error则是错误,否是就是Exception。
2. 异常相关的类
2.1. Throwable 类与常用方法
Throwable 类是 Java 语言中所有错误或异常的超类。直接已知子类:Error
, Exception
2.1.1. Throwable 常用方法
public String getMessage()
- 获得创建 Throwable 异常对象构造方法中指定的消息字符串。
public String toString()
- 获得 Throwable 异常信息的类全名和消息字符串。
public void printStackTrace()
- 打印异常的栈信息,追溯异常根源。
- 将此 throwable 及其追踪输出至标准错误流。此方法将此 Throwable 对象的堆栈跟踪输出至错误输出流,作为字段
System.err
的值。输出的第一行包含此对象的toString()
方法的结果。剩余行表示以前由方法fillInStackTrace()
记录的数据。
public string getLocalizedMessage()
- 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与
getMessage()
返回的结果相同
2.2. 自定义异常
- 命名规范:
XxxException
- 继承关系:继承
Exception
或RuntimeException
- 构造方法:提供有参和无参构造方法
定义异常处理时,什么时候定义try,什么时候定义throws呢?
- 功能内部如果出现异常,如果内部可以处理,就用try;
- 如果功能内部处理不了,就必须声明出来,让调用者处理。
什么时候使用抛出处理?什么时候使用捕获处理?
- 如果当前代码是直接跟用户打交道的,则千万不要抛出异常,要捕获处理。
- 如果需要将异常报告给上一层(方法调用者)则要使用抛出处理。
父类方法没有声明抛出异常,子类覆盖方法时发生了异常,怎么办?
- 子类重写方法时,可以抛出异常,但是只能抛出运行时异常或其子类。
3. 异常处理
3.1. 异常的处理方式
jvm的默认处理方式
- 将异常的类名,位置,原因等信息输出在控制台。
- 终止程序的运行。后续的代码没有办法执行。
手动处理异常方式
- 捕获处理
- 抛出处理
3.2. 异常处理之捕获处理 - try catch
- 在开发时,如果定义功能时,发现该功能会出现一些问题,应该将问题在定义功能时标示出来,这样调用者就可以在使用这个功能的时候,预先给出处理方式。
- 如何标示呢?通过throws关键字完成,格式:
throws 异常类名,异常类名...
- 这样标示后,调用者,在使用该功能时,就必须要处理,否则编译失败。
3.2.1. 捕获处理总体格式
try {
// 需要检测的异常;
} catch(异常类名 变量名) {
// 异常处理代码
// 通常我们只使用一个方法:printStackTrace 打印异常信息
} finally {
// 不管是否有异常发生,都会执行该代码块中的代码。
}
3.2.2. 异常捕获处理-单 catch 处理
try{
// 需要检测的异常;
} catch (异常类名 异常变量名) {
// 异常处理代码
// 可以调用异常的方法
// 通常我们只使用一个方法:printStackTrace 打印异常信息
}
格式说明
- try 代码块中存放可能出现异常的代码。
- catch 代码块中存放处理异常的代码。
执行流程
- 先执行try代码块中的代码,如果try代码块中的代码没有出现异常,则不会执行catch代码块的代码。
- 如果try代码块中的代码出现异常,则会进入catch代码块执行里面的代码。
3.2.3. 异常捕获处理-多 catch 处理
try {
// 需要检测的异常;
} catch(异常类名1 异常变量1) {
// 异常处理代码
// 可以调用异常的方法
// 通常我们只使用一个方法:printStackTrace 打印异常信息
} catch(异常类名2 异常变量2) {
// 异常处理代码
// 可以调用异常的方法
// 通常我们只使用一个方法:printStackTrace 打印异常信息
} …… // 可以有无数个catch
多 catch 处理注意事项
- 多个catch 外理异常时,异常类名要有先后顺序。
- 如果多个异常类之间是平级关系(没有继承关系),则没有先后顺序要求
- 如果多个异常类之间是上下级关系(有继承关系),越高级的父类要写在越下面。
- 特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。
在实际开发中是不是只捕获Exception异常吗?因为实际开发中,需要针对不同的异常有不同的处理方式。
3.2.4. finally 代码块
try {
// 需要检测的异常;
} catch(异常类名1 异常变量1) {
// 异常处理代码
// 可以调用异常的方法
// 通常我们只使用一个方法:printStackTrace 打印异常信息
} catch(异常类名2 异常变量2) {
// 异常处理代码
// 可以调用异常的方法
// 通常我们只使用一个方法:printStackTrace 打印异常信息
} finally {
// 不管是否有异常发生,都会执行该代码块中的代码。
}
finally 代码块的特点:
- 只要进入了try的代码块,不管是否有异常发生,都会执行该代码块中的代码。
finally 作用:
- 用来释放资源,比如关闭IO流或数据库相关资源。
3.2.5. try-catch-finally 的几种结合方式:
组合1 | 组合2 | 组合3 |
---|---|---|
try catch finally |
try catch |
try finally |
- 这种情况,如果出现异常,并不处理,但是资源一定关闭,所以
try finally集合只为关闭资源
。 - 记住:finally很有用,主要用户关闭资源。无论是否发生异常,资源都必须进行关闭。
System.exit(0);
此方法是退出jvm,只有这种情况finally不执行。
3.3. 异常处理之抛出处理 – throws 和 throw关键字
- 这个体系中的所有类和对象都具备一个独有的特点;就是可抛性。
- 可抛性的体现:就是这个体系中的类和对象都可以被throws和throw两个关键字所操作
3.3.1. throws 和 throw的作用
- throws:将异常类名标识出类,报告给方法调用者
- throw:将异常对象抛出,抛给方法的调用者。
3.3.2. throws 和 throw的位置
- throws:使用在方法声明上
- throw:使用在方法体内部
3.3.3. throws 格式
修饰符 返回值类型 方法名 (参数类型 参数名1, 参数类型 参数名2, ……) throws 异常类名1,异常类名2,…… {
......
}
3.3.4. throw 格式
throw new 异常类名(“异常信息”);
3.3.5. throws 和 throw注意事项
throw
关键字之后的对象一定是异常对象,不能是其他对象。throws
后面跟多个异常,使用“,”隔开。
3.3.6. 异常声明抛出处理流程
- 在某个方法的声明上,让它(这个方法)抛出这个产生的异常,自己不处理,谁调用这个方法,谁处理
方法声明上加
throws
异常类名- 如:方法a 中产生了异常,声明抛出
- 方法b 调用方法a,则该异常由a 转向了b
- 一般情况下,main 方法是不会声明抛出异常的,因为该方法抛出,直接抛出给了JVM,JVM 的默认处理机制是终止程序运行,则这样处理,相当于没有处理!
3.4. 方法重写时异常处理(针对编译时异常)
- 父类方法声明上没有使用throws声明抛出异常,子类重写方法时不能声明抛出异常。
- 父类方法声明上使用throws声明一个异常时,子类重写方法时可以声明和父类一样的异常或其异常的子类。
- 父类方法声明上使用throws声明多个异常时,子类重写方法可以声明和父类多个异常的子集异常。
总结:子类不能声明抛出比父类大的异常。
注意:
- 如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类重写的方法中出现了异常,只能try不能throws。
- 如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。
4. 运行时异常和编译时异常(理解)
运行时异常
- 只要是RuntimeException或其子类异常都是运行时异常。
编译时异常
- 除了运行时异常以外的所有异常都是编译时异常。
运行时异常特点
- 方法声明上如果声明的异常是运行时异常,则方法调用者可以处理,也可以不处理。
- 方法体内部抛出的异常是运行时异常,则方法声明上可以声明,也可以不声明。
编译时异常特点
- 方法声明上如果声明的异常是编译时异常,则要求方法调用者一定要处理。
- 方法体内部抛出的异常是编译时异常,则要求对该异常进行处理(捕获处理或者声明抛出处理)。
为什么java编译器对运行时异常管理如何松散?因为运行时异常一般通过程序员良好的编程习惯避免。