认识异常 ---java

目录

一. 异常的概念

二. 异常的体系结构

三. 异常的分类

三. 异常的处理

3.1 异常的抛出throw

3.2. 异常声明throws

3.3 捕获并处理try-catch

finally

 3.4异常的处理流程

四. 自定义异常类


一. 异常的概念

Java 中,将程序执行过程中发生的不正常行为称为异常。比如之前写代码时经常遇到的:
1. 算术异常
System . out . println ( 10 / 0 );
// 执行结果
Exception in thread "main" java . lang . ArithmeticException : / by zero

2. 数组越界异常

int [] arr = { 1 , 2 , 3 };
System . out . println ( arr [ 100 ]);
// 执行结果
Exception in thread "main" java . lang . ArrayIndexOutOfBoundsException : 100

3. 空指针异常

int [] arr = null ;
System . out . println ( arr . length );
// 执行结果
Exception in thread "main" java . lang . NullPointerException

点进去任意一个异常, 我们发现:

认识异常 ---java_第1张图片

从上述代码中可以看到,java中不同类型的异常,都有与其对应的类来进行描述 

二. 异常的体系结构

认识异常 ---java_第2张图片

从上图中可以看到:
1. Throwable 是异常体系的顶层类,其派生出两个重要的子类 , Error Exception
2. Error 指的是 Java 虚拟机无法解决的严重问题,比如: JVM 的内部错误、资源耗尽等 ,典型代表: StackOverflowError OutOfMemoryError ,一旦发生回力乏术。
3. Exception 异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception

三. 异常的分类

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:

1. 编译时异常

在程序编译期间发生的异常,称为编译时异常,也称为受检查异常(Checked Exception)  

例: 

认识异常 ---java_第3张图片

我们可以通俗的来理解, 在编写代码的过程中, 出现了红色的波浪线, 此时的异常就叫做编译时异常或受查异常.

2. 运行时异常

在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常 (Unchecked Exception)
RunTimeException 以及其子类对应的异常,都称为运行时异常 。例如上述我们举例的之前见过的异常: NullPointerException 、 ArrayIndexOutOfBoundsException、 ArithmeticException
注意:编译时出现的语法性错误,不能称之为异常。例如将 System.out.println 拼写错了 , 写成了
system.out.println. 此时编译过程中就会出错 , 这是 " 编译期 " 出错。而运行时指的是程序已经编译通过得到 class 文件了 , 由 JVM 执行过程中出现的错误.

三. 异常的处理

异常处理主要的 5 个关键字: throw try catch finally throws

3.1 异常的抛出throw

 Java中,可以借助throw关键字,由程序猿抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:

throw new XXXException("异常产生的原因");

一般情况下, 通过throw抛出的是 自定义的异常, 可以自己通过类定义异常并抛出

例:实现一个获取数组中任意位置元素的方法。 

public static int getElement ( int [] array , int index ){
        if ( null == array ){
                throw new NullPointerException ( " 传递的数组为 null" );
        }
        if ( index < 0 || index >= array . length ){
                throw new ArrayIndexOutOfBoundsException ( " 传递的数组下标越界 " );
        }
        return array [ index ];
}
public static void main ( String [] args ) {
        int [] array = { 1 , 2 , 3 };
        getElement ( array , 3 );
}
注意事项
1. throw 必须写在方法体内部
2. 抛出的对象必须是 Exception 或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给 JVM 来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5. 异常一旦抛出,其后的代码就不会执行

3.2. 异常声明throws

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助 throws 将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常
语法格式:
修饰符 返回值类型 方法名 ( 参数列表 ) throws 异常类型 1 ,异常类型 2 ...{
}

意思为:在这个方法中, 可能会包含这些异常, 告诉调用这个方法的人也就是程序猿去处理这些异常

如果程序猿不对异常进行处理, 那么就会交给JVM处理, 程序就会立刻停止.

认识异常 ---java_第4张图片

认识异常 ---java_第5张图片

 注意事项

1. throws 必须跟在方法的参数列表之后
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常, throws 之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。

因为ArithmeticException是RuntimeException的子类, 所以只用写父类即可, 即:

4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出 认识异常 ---java_第6张图片

把光标方法波浪线处, 使用Alt+Enter 可以使用快捷键

认识异常 ---java_第7张图片 5. 如果throw Exception

认识异常 ---java_第8张图片

这里的Exception是包含两种类型, 编译时异常和运行时异常, 所以编译器默认调用func()引发的是编译时异常  在main方法后也加上throw Exception, 此时才可能会变成运行时异常, 上JVM处理

认识异常 ---java_第9张图片

3.3 捕获并处理try-catch

throws 对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch
语法格式:
try {
        // 将可能出现异常的代码放在这里
} catch ( 要捕获的异常类型 e ){
        // 如果 try 中的代码抛出异常了,此处 catch 捕获时异常类型与 try 中抛出的异常类型一致时,或者是 try 中抛出异常的基类时,就会被捕获到
        // 对异常就可以正常处理,处理完成后,跳出 try-catch 结构,继续执行后序代码
}[ catch ( 异常类型 e ){
        // 对异常进行处理
        // 异常的处理方式
        //System.out.println(e.getMessage()); // 只打印异常信息
        //System.out.println(e); // 打印异常类型:异常信息
        e . printStackTrace (); // 打印信息最全面
} ]
// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行
注意:
1. [] 中表示可选项,可以添加,也可以不用添加
2. try 中的代码可能会抛出异常,也可能不会

例:

认识异常 ---java_第10张图片 try中的代码发生数组越界, 通过数组越界异常进行捕捉, 所以捕捉到了, 就可以对异常进行处理, 处理后跳出try-catch结构, 继续执行后续代码, 所以运行结果为:

认识异常 ---java_第11张图片

 若捕捉的是空指针异常:

认识异常 ---java_第12张图片

即并未捕捉到, 所以就没有处理数组越界异常, 则交给JVM处理, 程序将立刻停止, 什么也不会打印

认识异常 ---java_第13张图片

想要捕获数组越界异常, 继续catch, 自动匹配

认识异常 ---java_第14张图片

第二种写法:(不推荐)认识异常 ---java_第15张图片

第三种写法:(不推荐)

认识异常 ---java_第16张图片

注: 

1. 在try中发生异常后的代码不会被执行

认识异常 ---java_第17张图片

认识异常 ---java_第18张图片

2. 如果抛出异常类型与 catch 时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序 ---- 异常是按照类型来捕获的
3. try 中可能会抛出多个不同的异常对象,则必须用多个 catch 来捕获 ---- 即多种异常,多次捕获
4. 如果异常之间具有父子关系,一定是子类异常在前 catch ,父类异常在后 catch ,否则语法错误

 认识异常 ---java_第19张图片

finally

在写程序时, 有些特定的代码, 不论程序是否发生异常,都需要执行 ,比如程序中打开的资源 :网络连接、数据库连接、IO 流等, 在程序正常或者异常退出时,必须要对资源进进行回收 。另外,因为 异常会引发程序的跳转,可能 导致有些语句执行不到 finally 就是用来解决这个问题的。

 语法格式:

try {
        // 可能会发生异常的代码
} catch ( 异常类型 e ){
        // 对捕获到的异常进行处理
} finally {
        // 此处的语句无论是否发生异常,都会被执行到
}
        // 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行

未发生异常:

认识异常 ---java_第20张图片 发生异常:

认识异常 ---java_第21张图片

 体会资源回收:

认识异常 ---java_第22张图片

另一种写法:

光标放在try上, Alt+Enter

认识异常 ---java_第23张图片

认识异常 ---java_第24张图片

注意:finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作

// 下面程序输出什么?
public static void main ( String [] args ) {
        System . out . println ( func ());
}
public static int func () {
        try {
                return 10 ;
        } finally {
                return 20 ;
        }
}

//20

finally 执行的时机是在方法返回之前 (try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果finally 中也存在 return 语句 , 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
一般我们不建议在 finally 中写 return ( 被编译器当做一个警告 ).

 3.4异常的处理流程

首先了解一个概念:调用栈

调用栈:

方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述. 在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.  如果本方法中没有合适的处理异常的方式 , 就会沿着调用栈向上传递

 看下述代码:

认识异常 ---java_第25张图片

在main方法中调用了func(), 在func()中出现了数组越界异常, 但是在func()方法中并没有处理异常, 于是沿着沿着调用栈向上传递到main方法中, 发现在main方法中处理了该异常, 所以不会交给JVM处理, 输出的结果为:

认识异常 ---java_第26张图片

异常处理流程总结
1. 程序先执行 try 中的代码
2. 如果 try 中的代码出现异常 , 就会结束 try 中的代码 , 看和 catch 中的异常类型是否匹配 .
3. 如果找到匹配的异常类型 , 就会执行 catch 中的代码
4. 如果没有找到匹配的异常类型 , 就会将异常向上传递到上层调用者 .
5. 无论是否找到匹配的异常类型 , finally 中的代码都会被执行到 ( 在该方法结束之前执行 ).
6. 如果上层调用者也没有处理的了异常 , 就继续向上传递 .
7. 一直到 main 方法也没有合适的代码处理异常 , 就会交给 JVM 来进行处理 , 此时程序就会异常终止 .

 

四. 自定义异常类

Java 中虽然已经内置了丰富的异常类 , 但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我 们实际情况的异常结构.
例如 实现一个用户登陆功能
认识异常 ---java_第27张图片

 此时, 虽然我们可以很快的找到错误所在, 但代码一多, 就很难找到了, 所以, 我们要自定义异常类来帮助我们更快找到错误.

自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常

自定义异常类:

认识异常 ---java_第28张图片认识异常 ---java_第29张图片

 接下来, 通过throw自己抛异常

认识异常 ---java_第30张图片

 虽然, 我们抛出了异常, 但是为了使程序正常运行, 我们需要对异常进行处理:

认识异常 ---java_第31张图片

今天的分享就到这里, 谢谢大家的点赞支持!! 

你可能感兴趣的:(javaSE,java,开发语言)