目录
一、什么是异常
二、异常处理
1、如何捕捉异常?
2、finally语句
3、finally语句中的throw和return
三、常见异常
四、自定义异常
那么具体如何自定义异常类呢?
throw关键字
throws关键字
五、运行时异常
六、异常处理流程
️️多多支持
前言:
在程序设计和运行的过程中,发生错误是不可避免的。为此java提供了异常的处理机制,来帮助程序员检查可能出现的错误。保证程序的可读性和可维护性。java将异常封装到一个类中,出现错误时就会抛出异常,程序终止。
通过学习,您可以了解到:
1、什么是异常
2、如何捕捉异常
3、java中有哪些常见的异常
4、如何自定义异常
5、如何在方法中抛出异常
6、运行时有哪些异常
7、异常处理的使用原则
错误产生于没有实现预料到的各种情况,或是超过了程序员可控制范围的环境因素。
就比如我们经常遇到的遇到除数为0的情况,但是自身又没发现,于是程序运行到这一样是就会让程序异常终止:
① 算术异常
程序运行时发生了算数异常ArithmeticException,根据异常提示可以知道是在Test2的main方法中发生了算术异常,异常为 / by zero,意为0被作为了除数而报错。
可以看到,异常前的程序都会正常执行,当执行到异常,系统就不会再执行下去,提前结束并提示:程序异常退出
Process finished with exti code 1
异常类为ArithmeticException
还有很多例子,例如
②数组越界:
代码案例:
public class Main {
public static void main(String[] args) {
int[] arr = new int[]{0,1,2,3,4,5};
System.out.println(arr[6]);
}
}
运行结果:
异常类为ArrayIndexOutOfBoundsException
③空引用的使用(空指针):
代码案例:
public class Main {
public static void main(String[] args) {
String tem = null;
int stringLength = tem.length();
System.out.println(stringLength);
}
}
执行结果:
异常类为NullPointerException
java是面向对象的计算机语言,所以在java中异常都是作为类的实例的形式出现的。当某一方法中发生错误时,这个方法会创建一个对象,并且把它传递给正在运行的系统。 这个对象就是异常对象,通过异常处理机制,可以将非正常情况下的处理代码与程序的主逻辑分离,即在编写代码主流程时在其他地方处理异常。
这些异常种类和异常处理机制将会在下面的篇章中逐渐渗透。
为了保证程序能正常的运行,我们需要对出现的异常错误进行及时的处理。在java 的异常处理机制当中,当某个方法出现异常时,可以在该方法里进行捕捉,然后处理异常,也可以将异常向上抛出,交给该方法的调用者处理。
如果对产生的异常不做任何的处理,程序就会异常终止。所以需要对该异常进行捕捉然后处理。
java语言的异常捕捉结构为:
try{
// 可能产生异常错误的语句
}catch( ExceptionTypeOne e){
// 对异常 ExceptionTypeOne 的处理语句
}catch ( ExceptionTypeTwo e) {
// 对ExceptionTypeTwo 的处理语句
}
...........
finally {
// 语句块
}
此结构由 try,catch, finally 3部分组成。其中:
① try语句中的是可能发生异常的代码
② catch用来捕捉异常,{}中为异常处理的语句
③ finally为异常处理的最后执行部分,无论前面的catch是否被捕捉,finally语句都会被执行
其中的 Exception 是try代码块传递给catch代码块的变量类型,e是变量名。
我们使用这种try - catch语句来对上面的 / by zero异常来进行一个修改:
public class Test2 {
public static void main(String[] args) {
try {
int digit = 1;
int zero = 0;
System.out.println("value of digit is:"+digit);
System.out.println("value of zero is:"+zero);
int result = digit / zero;
System.out.println("value of result is:" + result);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("hello world!");
}
}
catch代码块中的语句e.printStackTrace() 语句时用于输出错误性质,通常,异常处理用下面三个函数来获取异常有关信息:
getMessage() // 输出错误性质
toString() // 给出一场的性质和类型
printStackTrace() // 指出异常类型、性质、栈层次及出现在程序的位置
可以发现,digit 和 zero 的值都被打印,但是result的值没有被打印,try - catch 语句外的打印
hello world 的语句也被打印。紧接着的一行就是 Process finished with exit code 0
表示程序正常退出。
当有多个catch 语句的情况:
public class Main {
private static int Test(int x, int y) throws MyException{
if(y < 0){
throw new MyException("除数不能为负数");
}
return x/y;
}
public static void main(String[] args) {
try {
int ret = Test(2,-1);
} catch (MyException e) {
System.out.println(e.getMessage()); // 进行捕捉
} catch (ArithmeticException e){
System.out.println(e.getMessage());
} catch (Exception e){
System.out.println("发生了其他异常");
}
}
}
上面实例使用了很多个catch语句来捕捉异常, 其中
① 调用Test (2 , -1 ) 将会发生MyException异常,进而转入catch (MyException e)执行
② 调用Test (3 , 0) 将会发生ArithmeticException异常,转入对应的catch语句执行
③ 如果还有除了 0 和 负数之外的异常,那么将会被Exception e捕捉然后转入catch语句执行
但是由于 Exception是所有异常类的父类,如果将其与MyException调换位置如果图:
那么 前面的 ArithmeticException异常和 MyException 异常都会被 第一个catch语句捕捉,
那么后面的catch语句就不会执行,最后就只会收到一个发生了其他异常的字符串提示,对于异常的处理没有任何的帮助,try - catch语句也就是去了它的意义。
通过try - catch 语句,程序没有异常退出,而是将错误抛出后,继续执行try - catch 异常捕捉后面的语句,而不至于因为一个异常从而影响整个程序的运行。
一个完整的try - catch 语句一定要包含finally语句,无论程序是否有异常发生,并且无论之间的try - catch语句是否顺利执行完毕,都会执行finally语句。
但是也存在意外,finally语句不会被执行的情况,如下:
① 在finally 语句中发生了异常
② 在前面的代码中使用了System.exit()语句,程序提前结束
③ 程序所有的线程死亡
④ 关闭CPU
无论有没有异常都会执行finally语句,那么如果在try-catch语句块中遇到了return和throw语句,后面的finally语句是否还会执行呢?代码案例如下:
public class Test {
public static void main(String[] args) {
System.out.println("return value of getValue(): " +
getValue());
}
public static int getValue() {
try {
return 0;
} finally {
return 1;
}
}
}
程序的运行结果是什么?
解析:当Java程序执行ty块、catch块时遇到了retun语句, 并不会立马结束这个方法,而是去寻找该异常处理流程中是否包含finally语句块,如果没有finally块,方法终止,返回相应的返回值。如果有finally块, 系统立即开始执行finally块中的语句,只有当finally块执行完成后,系统才会再次回到try-catch语句中来根据return语句结束方法。如果finally块里使用了return语句,则finally块会直接执行return语句并返回,使方法结束,系统将不会跳回去执行try块、 catch块里的任何代码。
结果如下:
return value of getValue(): 1
java内部提供了一些异常,用来描述经常发生的错误,其中,有的需要程序员极性捕捉处理或声明抛出。有的是由java虚拟机自动进行捕捉
java常见的异常类如表:
异常类 | 说明 |
ClassCastException | 类型转换异常 |
ClassNotFoundException | 未找到相应的类异常 |
ArithmeticException | 算术异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
ArrayStoreException | 数组中包含不兼容的值抛出的异常 |
SQLException | 操作数据库异常类 |
NullPointerException | 空指针异常 |
NoSuchFieldException | 未找到字段异常 |
NoSuchMethodException | 方法未找到抛出的异常 |
NumberFormatException | 字符串转化为数字抛出的类异常 |
NegativeArraySizeException | 数组元素个数为负数抛出的异常 |
StringIndexOutOfBoundsException | 字符串索引超出范围抛出的异常 |
IOException | 输入输出异常 |
IllegalAccessException | 不允许访问某类异常 |
InstantiationException | 当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常 |
EOFException | 文件已结束异常 |
FileNotFoundException | 文件未找到异常 |
以上异常的出现场景希望读者可以自行研究。
使用java内置的异常类可以处理在编程期间出现的大多数异常情况,但是仍有部分比较有特性的异常是java内置异常没有覆盖到的。例如:对于需要传入整数的一个方法,你可能会觉得,传入 一个负数是不符合你的期望,认为负数作为这个方法的参数为一个错误的值,但是对于java来说,它不会将其视为一个错误或者异常。
所以为了能个性化地去解决这些在特殊情况才能出现的异常,java引入了自定义异常类,用户只需要继承Exception类即可以自定义异常类。
① 首先,在java中异常都是作为类的实例的形式出现的,所以我们应该先定义自定义的类,类名设为MyException(自定义类名视使用场景况而定),然后由该类继承Exception类:
代码案例:
public class MyException extends Exception{ // 声明这个MyException类并让其继承Exception类
public MyException (String ErrorMessage){ // 调用构造方法
super(ErrorMessage); // 给父类Exception传参
}
}
字符串ErrorMessage是要输出的错误信息。
② 使用throw关键字,在需要的时候抛出异常,就比如我们经常使用ArrayList表里面的set方法:
里面就是使用了rangeCheck()方法来检查index是否越界,点击查看rangeCheck方法可以看到如下:
在这里通过使用throw 关键字抛出对应的异常信息,里面的outOfBoundMsg(index)返回一个错误 的异常信息的字符串,对应我们自定义异常的 ErrorMessage :
然后使用构造方法构造new 出一个IndexOutOfBoundsException 的实例并抛出异常。
我们使用自定义异常配合try- catch语句来模拟这个过程:
public class Main {
private static void checkMySet(int pos,int len) throws MyException{
if(pos >= len){
throw new MyException("指定的下标超过已有的最大下标位置");
} else if (pos < 0 ) {
throw new MyException("指定下标小于0!");
}else {
System.out.println("下标数值正确!");
}
}
public static void set(int[] arr,int pos,int element) throws MyException {
checkMySet(pos,arr.length);
arr[pos] = element;
}
public static void main(String[] args) {
int[] arr = new int[10];
try {
set(arr,-4,10); // 可能抛出异常
} catch (MyException e) {
System.out.println(e); // 进行捕捉
}
}
}
结果为:
程序正常退出。
throw关键字通常用于方法体重,并且抛出一个异常的实例化对象,对象被抛出后程序终止:
可以看到,如果在方法体内抛出对象的语句后面加上语句,那么后面的语句将永远不会执行到
代码案例如下:
public class Main {
private static void Test(int pos) throws MyException{
if(pos >= 0){
throw new MyException("pos >= 0");
}
System.out.println("checkEnded");
}
public static void main(String[] args) {
int[] arr = new int[10];
try {
System.out.println("TestStart");
Test(0);
System.out.println("TestEnd");
} catch (MyException e) {
System.out.println(e); // 进行捕捉
}
}
}
可以看出来,在方法抛出异常后,该方法后面的语句也不会执行
如果要捕捉异常,就必须使用try - catch语句
此外,throw必须写在方法体内部 ,抛出的对象必须是Exception 或者 Exception 的子类对象。如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理 如果抛出的是编译时异常,用户必须处理,否则无法通过编译 。异常一旦抛出,其后的代码就不会执行
throws关键字通常被用来在声明方法的时候,指定其方法可能抛出的异常,多个异常可以用“,”逗号分割:
public static void Test () throws Exception1 , Exception2 , Exception3 {
//可能会抛出Exception1 , Exception2 , Exception3异常的方法体
}
从上面的代码案例中我们可以看到:checkMySet()方法中加入了if 语句来来判断是否抛出异常,有可能会抛出,也可能不会抛出,只要有抛出的可能,就需要在其方法后用throws关键字声明可能会抛出的异常类。
异常被throws 不断向上抛出,最终被catch 捕获并处理。
需要注意的是:
① throws必须跟在方法的参数列表之后
② 声明的异常必须是 Exception 或者 Exception 的子类
RuntimeException 异常是程序运行过程中产生的异常。它是Exception类的子类之一。
java类库的每个包中都定义了异常类,所有的类都是Throwable的子类,Throwable派生出两个子类,分别是Exception 和 Error 类。 Error 类及其子类用来描述java运行系统中的内部错误以及资源消耗的错误。
Exception类异常通过捕捉后程序可以继续正常执行。
其中RuntimeException的异常种类
从图中可以看出来,Throwable 是异常处理体系的顶层类,派生除了Error 和 Exception 两个重要的子类。Error 指的是java虚拟机无法解决的严重问题,例如jvm内部错误,资源耗尽等等,其代表为: StackOverflowError 和 OutOfMemoryError。
Exception 异常类为程序员可以正常捕捉并处理的异常。
NullPointerException | 空指针异常 |
ArrayIndeOutOfBoundsException | 数组下标越界异常 |
ArithmeticException | 算术异常 |
SecurityException | 安全性异常 |
NegativeArrayException | 数组长度为负异常 |
IllegalArgumentException | 非法参数异常 |
ArrayStoreException | 数组中包含不兼容参数异常 |