1. 知识点总结
1.1. 异常分类
异常就是 java 中出现的不正常的现象(错误与异常), 按照继承的体系结构, 可以分类如下
Throwable: 它是所有错误与异常的超类(祖宗类)
|- Error 错误, 修改 java 源代码
|- Exception 编译期异常, javac.exe 进行编译的时候报错
|- RuntimeException 运行期异常, java 出现运行过程中出现的问题
1.2. 异常处理方式
1. 出现问题, 自己解决
try{
可能出现异常的代码
} catch(异常类名 对象名){
异常处理代码
} finally {
异常操作中一定要执行的代码
}
2. 出现问题, 别人解决
格式:
修饰符 返回值类型 方法名(参数) throws 异常类名 1, 异常类名 2,...{}
public void method() throws ATMException{}
2. 异常和错误的区别
异常: 程序在运行期间发生了异常, 通常可以有针对性的处理方式的.
错误: 程序在运行期间发生了错误, 通常不会有针对性的处理方式. 错误的的发生往往都是系统级别的问题, 都是 jvm 所在系统发生的并反馈给 jvm 的. 无法针对处理, 只能修正代码.
异常代码体现:
- class ExceptionDemo {
- public static void main(String[] args) {
- int[] arr = new int[3];
- System.out.println(arr[0]);
- System.out.println(arr[3]);// 该句运行时发生了 ArrayIndexOutOfBoundsException, 导致程序无法继续执行. 程序结束.
- System.out.println("over"); // 由于上面代码发生了异常, 此句代码不会执行
- }
- }
错误的体现:
int[] arr = new int[1024*1024*100];//OutOfMemoryError 开辟了过大的数组空间, 导致 JVM 在分配数组空间时超出了 JVM 内存空间, 直接发生错误.
3. 异常的发生过程
- class ArrayTools{
- /*
- 对给定的数组通过给定的角标获取元素.
- */
- int getElement(int[] arr,int index) {
- /*
- 没有找到 4 个角标. 运行时发生了问题, 这个问题 JVM 认识. 这个问题 java 本身有
- 描述: 描述内容有: 问题的名称, 问题的内容, 问题的发生位置. 既然有这么多的信
- 息. java 就将这些信息直接封装到对象中. ArrayIndexOutOfBoundsException
- */
- int element = arr[index];//throw new ArrayIndexOutOfBoundsException(index);
- return element;
- }
- }
- class ExceptionDemo2 {
- public static void main(String[] args) {
- ArrayToolsd = new ArrayTools();
- int[] arr = {34,12,67};
- int num = d.getElement(arr,4);// 收到 new ArrayIndexOutOfBoundsException(index); 抛出 jvm.jvm 进行最终处理. 将问题的名称, 信息, 位置都显示屏幕上.
- System.out.println("num="+num);
- System.out.println("over");
- }
- }
4. 异常的应用
在编写程序时, 必须要考虑程序的问题情况. 比如在写功能时, 需要接受参数, 在使用功能中使用接受到的参数时, 首先需要先对参数数据进行合法的判断, 数据若不合法, 应该告诉调用者, 传递合法的数据进来. 这时需要使用异常这种方法来告诉调用者. 所以定义程序需要考虑程序的健壮性.
- class ArrayTools{
- /*
- 对给定的数组通过给定的角标获取元素.
- */
- int getElement(int[] arr,int index) {
- /*
- jvm 出了问题, 自己打包对象并抛出. 但是它所提供的信息不够给力. 想要更清晰,
- 需要自己写. 它的抛出不满足我们的要求. 准备自己抛.
- */
- if(arr==null) {
- throw new NullPointerException("arr 指向的数组不存在");
- }
- if(index<0 || index>=arr.length){
- // 该条件如果满足, 功能已经无法继续运算. 这时就必须结束功能, 并将问题告知给调用者. 这时就需要通过异常来解决.
- // 怎么使用异常呢?
- //1, 创建一个异常对象. 封装一些提示信息(自定义).
- //2, 需要将这个对象告知给调用者. 怎么告知呢? 怎么将这个对象传递到调用者处呢? 通过关键字 throw 就可以完成. throw 异常对象;
- //3,throw 用在函数内, 抛出异常对象, 并可以结束函数.
- throw new ArrayIndexOutOfBoundsException("错误的角标,"+index+"索引在数组中不存在");
- }
- int element = arr[index];
- return element;
- }
- }
- class ExceptionDemo3 {
- public static void main(String[] args) {
- Demo d = new Demo();
- int[] arr = {34,12,67};
- int num = d.getElement(null,2);
- System.out.println("num="+num);
- System.out.println("over");
- }
- }
5. 异常的分类
在查阅 API 时, 发现异常有分类, 并且有些编辑报错, 有些编译不报错, 那到底是为什么呢?
在上述自定义异常中继承 Exception 和继承 RuntimeExcpetion 为什么差距这么大?
1,Exception 异常(非 RuntimeException)
在函数内抛出 Exception, 编译失败, 因为编译器在检查语法时发生了错误. 该程序已经出现问题, Java 认为这个程序本身存在隐患, 需要捕获或者声明出来(你要么把问题处理, 要么把问题标识出来让调用知道).
2,RuntimeException 异常
为什么抛出 RuntimeException, 不需要捕获, 不要声明呢? 不是功能本身发生的异常, 而是因为比如调用者传递参数错误而导致功能运行失败. 这也是问题, 需要通过异常来体现, 但是这个异常不要声明出来的. 声明的目的是为了让调用者进行处理. 不声明的目的是不让调用者进行处理, 就是为了让程序停止, 让调用者看到现象, 并进行代码的修正.
3, 异常分两种:
a, 编译时异常: 编译器会检测的异常.
b, 运行时异常: 编译器不会检测的异常. 不需要声明. 声明也可以, 如果声明了, 无外 乎就是让调用者给出处理方式.
6. 声明和捕获
在研究异常分类时, 发现有关于异常的声明和捕获, 到底啥是异常声明, 啥是异常捕获呢?
声明: 将问题标识出来, 报告给调用者. 如果方法内通过 throw 抛出了编译时异常, 而没有捕获, 那么必须通过 throws 进行声明, 让调用者去处理.
捕获: Java 中对异常有针对性的语句进行捕获.
捕获格式:
- try
- {
- // 需要被检测的语句.
- }
- catch(异常类 变量)// 参数.
- {
- // 异常的处理语句.
- }
- finally
- {
- // 一定会被执行的语句.
- }
- class Demo{
- /*
- 如果定义功能时有问题发生需要报告给调用者. 可以通过在函数上使用 throws 关键字进行声明.
- */
- void show(int x)throws Exception {
- if(x>0){
- throw new Exception();
- } else {
- System.out.println("show run");
- }
- }
- }
- class ExceptionDemo{
- public static void main(String[] args) {//throws Exception// 在调用者上继续声明.
- Demo d = new Demo();
- try {
- d.show(1);// 当调用了声明异常的方法时, 必须有处理方式. 要么捕获, 要么声明.
- }
- catch (Exception ex) { // 括号中需要定义什么呢? 对方抛出的是什么问题, 在括号中就定义什么问题的引用.
- System.out.println("异常发生了");
- }
- System.out.println("Hello World!");
- }
- }
声明和捕获的应用
- class NoAgeException extends RuntimeException{
- NoAgeException() {
- super();
- }
- NoAgeException(String message) {
- super(message);
- }
- }
- class Person{
- private String name;
- private int age;
- Person(String name,int age)//throws NoAgeException {
- // 加入逻辑判断.
- if(age<0 || age>200) {
- throw new NoAgeException(age+", 年龄数值非法");
- }
- this.name = name;
- this.age = age;
- }
- // 定义 Person 对象对应的字符串表现形式. 覆盖 Object 中的 toString 方法.
- public String toString() {
- return "Person[name="+name+",age="+age+"]";
- }
- }
- class ExceptionDemo{
- public static void main(String[] args) {
- try {
- Person p = new Person("xiaoming",20);
- System.out.println(p);
- }
- catch (NoAgeException ex){
- System.out.println("异常啦");
- }
- System.out.println("over");
- }
- }
构造函数到底抛出这个 NoAgeException 是继承 Exception 呢? 还是继承 RuntimeException 呢?
继承 Exception, 必须要 throws 声明, 一声明就告知调用者进行捕获, 一旦问题处理了调用者的程序会继续执行.
继承 RuntimeExcpetion, 不需要 throws 声明的, 这时调用是不可能编写捕获代码的, 因为调用根本就不知道有问题. 一旦发生 NoAgeException, 调用者程序会停掉, 并有 jvm 将信息显示到屏幕, 让调用者看到问题, 修正代码.
7. 自定义异常
在上述代码中, 发现这些异常都是 JDK 内部定义好的, 并且这些异常不好找. 书写时也很不方便, 那么能不能自己定义异常呢?
之前的几个异常都是 java 通过类进行的描述. 并将问题封装成对象, 异常就是将问题封装成了对象. 这些异常不好认, 书写也很不方便, 能不能定义一个符合我的程序要求的问题名称. 既然 JDK 中是使用类在描述异常信息, 那么我们也可以模拟 Java 的这种机制, 我们自己定义异常的信息, 异常的名字, 让异常更符合自己程序的阅读. 准确对自己所需要的问题进行类的描述.
- class NoAgeException
- {
- /*
- 为什么要定义构造函数, 看到 Java 中的异常描述类中有提供对问题对象的初始化方法.
- */
- NoAgeException()
- {
- }
- NoAgeException(String message)
- {
- }
- }
- class Person{
- private String name;
- private int age;
- Person(String name,int age) {
- // 加入逻辑判断.
- if(age<0 || age>200){
- throw new NoAgeException(age+", 年龄数值非法");
- }
- this.name = name;
- this.age = age;
- }
- // 定义 Person 对象对应的字符串表现形式. 覆盖 Object 中的 toString 方法.
- public String toString(){
- return "Person[name="+name+",age="+age+"]";
- }
- }
- class ExceptionDemo{
- public static void main(String[] args) {
- Person p = new Person("xiaoming",-20);
- System.out.println(p);
- }
- }
上述代码发生编译失败.
ExceptionDemo.java:19: 错误: 不兼容的类型
- throw new NoAgeException(age+", 年龄数值非法");
- ^
需要: Throwable
找到: NoAgeException
1 个错误
提示中说需要 Throwable, 啥东西? 搜索 API. 看到 Throwable 描述. 发现. 它是异常和错误的超类(父类), 原来它是异常体系的顶层类.
通过阅读: 自定义异常被抛出, 必须是继承 Throwable, 或者继承 Throwable 的子类. 该对象才可以被 throw 抛出. 原来这个异常体系具备一个特有的特性: 可抛性: 可以被 throw 关键字操作.
异常继承选择父类时, 更为确切是继承 Exception. 但是发现编译又一次失败了.
- class NoAgeException extends Exception{
- /*
- 为什么要定义构造函数, 因为看到 Java 中的异常描述类中有提供对问题对象的初始化方法.
- */
- NoAgeException() {
- }
- NoAgeException(String message) {
- }
- }
ExceptionDemo.java:19: 错误: 未报告的异常错误 NoAgeException; 必须对其进行捕获或声明以便抛出
- throw new NoAgeException(age+", 年龄数值非法");
- ^
1 个错误
通过这个编译失败提示, 发现自定义的异常和之前所使用的异常(空指针异常, 角标越界异常, 无效参数异常有不同), 抛出哪些异常没有这个失败提示? 那么之前的异常和自定义的异常有什么区别呢?
通过查看 API 的继承体系发现, 之前的异常都是 Exception 下面的 RuntimeException 子类的子类. 阅读 RuntimeException 描述中有明确说明, 这个运行时异常以及其子类都无需进行声明.
可以将自定义的异常继承 RuntimeException.
- class NoAgeException extends RuntimeException
- {
- /*
- 为什么要定义构造函数, 因为看到 Java 中的异常描述类中有提供对问题对象的初始化方法.
- */
- NoAgeException() {
- }
- NoAgeException(String message) {
- }
- }
发现编译运行的确正常运行, 但是异常提示信息没有了, 为什么呢, 查阅异常源码, 发现自己父类构造函数中有关于异常信息的操作, 那么在自己定义的异常中需要将这些信息传递给父类, 让父类帮我们进行封装即可.
- class NoAgeException extends RuntimeException{
- /*
- 为什么要定义构造函数, 因为看到 Java 中的异常描述类中有提供对问题对象的初始化方法.
- */
- NoAgeException() {
- super();
- }
- NoAgeException(String message) {
- super(message);// 如果自定义异常需要异常信息, 可以通过调用父类的带有字符串参数的构造函数即可.
- }
- }
8.throw,throws 和 finally
8.1.throw 和 throws 的区别
1,throw 用在函数内. throws 用在函数上.
2,thorw 抛出的是异常对象. throws 用于进行异常类的声明, 后面异常类可以有多个, 用逗号隔开.
8.2.finally 的使用
有一些特定的代码无论异常是否发生, 都需要执行. 因为异常会引发程序跳转, 导致有些语句执行不到. 无法满足这个需求. 异常捕获处理时 java 提供解决方案, finally 就是解决这个问题的, 这个代码块中存放的代码都是一定会被执行的.
- // 自定义异常
- class NoShowException extends Exception
- {
- NoShowException(String message)
- {
- super(message);
- }
- }
- // 模拟功能类
- class Demo
- {
- void show(int num) throws NoShowException
- {
- if(num < 5)
- {
- throw new NoShowException(num+"非法的数据");
- }
- System.out.println("show num"+num);
- }
- }
- // 测试类
- class ExceptionDemo
- {
- public static void main(String[] args)
- {
- Demo d = new Demo();
- try
- {
- d.show(4);
- }
- catch (NoShowException ex)
- {
- System.out.println(ex); // 打印异常信息
- // 如果异常发生, 处理完毕后, 希望功能结束;
- // 可以使用 return ;
- }
- finally
- {
- System.out.println("一直能执行到的代码");
- }
- System.out.println("Hello World!");
- }
- }
总结: finally 到底什么时候用?
只要程序中使用到了具体的资源 (数据库连接, IO 资源, 网络连接 socket 等) 需要释放, 都必须定义在 finally 中. 你在定义程序, 只要问题发生与否, 指定程序都需要执行时, 就定义 finally 中.
8.3. 异常细节
8.3.1.try catch finally 组合
try catch 组合 : 对代码进行异常检测, 并对检测的异常传递给 catch 处理. 异常捕获处理.
- void show(){ // 不用 throws
- try{
- throw new Exception();
- }catch(Exception e){
- // 处理方式
- }
- }
try finally 组合: 对代码进行异常检测, 检测到异常后因为没有 catch, 所以一样会被默认 jvm 抛出. 异常是没有捕获处理的. 但是功能所开启资源需要进行关闭, 所有 finally. 只为关闭资源.
- void show(){// 需要 throws
- try{
- throw new Exception();
- }finally {
- // 关闭资源
- }
- }
try catch finally 组合: 检测异常, 并传递给 catch 处理, 并定义资源释放.
8.3.2. 异常在方法复写中细节
异常在继承或者实现中的使用细节:
1, 子类覆盖父类方法时, 如果父类的方法声明异常, 子类只能声明父类异常或者该异常的子类, 或者不声明.
2, 当父类方法声明多个异常时, 子类覆盖时只能声明多个异常的子集.
3, 当被覆盖的方法没有异常声明时, 子类覆盖时无法声明异常的.
举例: 父类中会存在下列这种情况, 接口也有这种情况,
问题: 接口中没有声明异常, 而实现的子类覆盖方法时发生了异常, 怎么办?
答: 无法进行 throws 声明, 只能 catch 的捕获. 万一问题处理不了呢? catch 中继续 throw 抛出, 但是只能将异常转换成 RuntimeException 子类抛出.
参考:
java 编程思想
黑马视频
https://www.cnblogs.com/dz-boss/p/10260306.html
来源: https://juejin.im/post/5c75ea7051882547021c42e7