目录
一,异常的理解
二,Throwable类的常用方法
①try...catch
②三种方法的代码示例
③finally
三,throw关键字
语法格式:
示例:
四,运行时异常与编译时异常
1.编译时异常
2.运行时异常
五,自定义异常
自定义类示例示例:
案例:
完整代码:
写了这么多代码其实对异常已经不陌生了,比如哪个关键字不小心拼写错了,分号不小心漏了,函数传参时漏参、多参等等,这些都是编程的常见异常,针对这种情况, Java语言引入了异常,以异常类的形式对这些非正常情况进行封装,通过异常处理机制对程序运行时发生的各种问题进行处理
下面是一个很常见的,0作为除数的异常警告
从运行结果可以看出,程序发生了算术异常(ArithmeticException),该异常是由于代码第4行的除法运算出现0作除数。异常发生后,程序会立即结束,无法继续向下执行。
上述程序产生的ArithmeticException异常只是Java异常类中的一种,Java提供了大量的异常类,这些类都继承自java.lang.Throwable类。 接下来通过一张图展示Throwable类的继承体系。
Throwable有两个直接子类Error和Exception,其中,Error代表程序中产生的错误,Exception代表程序中产生的异常。
在使用这三个方法前,先得认识一下try...catch一一异常捕获
语法格式:
try{
//想要捕获异常的程序代码块
}catch( Exception(或其子类) e(对象名) ){
//对异常的处理
}
在try代码块中编写可能发生异常的Java语句,catch代码块中编写针对异常进行处理的代码。当try代码块中的程序发生了异常,系统会将异常的信息封装成一个异常对象,并将这个对象传递给catch代码块进行处理。catch代码块需要一个参数指明它所能够接收的异常类型,这个参数的类型必须是Exception类或其子类
注意:catch代码块对异常处理完毕后,程序仍会向下执行,而不会终止程序!
public class Main {
public static void main(String[] args) {
try {
int res = 4 / 0;
System.out.println(res);
} catch (Exception e) {
System.out.println("返回异常的消息字符串->" + e.getMessage());
System.out.println("返回异常的简单消息描述->" + e.toString());
e.printStackTrace();
}
System.out.println("我猜代码现在会执行到这里");
}
}
在程序中,有时候会希望有些语句无论程序是否发生异常都要执行,这时就可以在try…catch语句后,加一个finally代码块
来看两个示例的对比:
也就是说不论程序是发生异常还是使用return语句结束,finally中的语句都会执行。因此,在程序设计时,通常会使用finally代码块处理完成必须做的事情,如释放系统资源
但是要区别理解的是,finally中的代码块在有一种情况下是不会执行的,那就是在try...catch中执行了System.exit(0)语句。System.exit(0)表示退出当前的Java虚拟机,Java虚拟机停止了,任何代码都不能再执行了!
在实际开发中,大部分情况下我们会调用别人编写的方法,但并不知道别人编写的方法是否会发生异常。针对这种情况,Java允许在方法的后面使用throws关键字对外声明该方法有可能发生的异常,这样调用者在调用方法时,就明确地知道该方法有异常,并且必须在自己的程序中对异常进行处理,否则编译无法通过。
修饰符 返回值类型 方法名(参数1,参数2...) throws 异常类1, 异常类2...{
//方法体...
}
从上述语法格式中可以看出,throws关键字需要写在方法声明的后面,throws后面需要声明方法中发生异常的类型。
在上述代码中,第3行代码调用divide()方法时传入的第二个参数不管是不是0,由于定义divide()方法时声明了抛出异常,调用者在调用divide()方法时就必须进行处理,否则就会发生编译错误。
至于处理办法,其实就是本篇第二大节介绍的try...catch...finally捕获异常并处理
示例代码:
public class Main {
public static void main(String[] args) {
try {
divide(4, 0);
} catch (Exception e) {
System.out.println("返回异常的消息字符串->" + e.getMessage());
System.out.println("返回异常的简单消息描述->" + e.toString());
e.printStackTrace();
return;
} finally {
System.out.println("catch里面只要没有exit(0),java虚拟机还没结束,finally模块的代码就会执行");
}
}
static int divide(int x, int y) throws Exception {
int res = x / y;
return res;
}
}
在Exception类中,除了RuntimeException类及其子类,Exception的其他子类都是编译时异常。编译时异常的特点是Java编译器会对异常进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。 处理编译时期的异常有两种方式,具体如下: (1)使用try…catch语句对异常进行捕获处理。 (2)使用throws关键字声明抛出异常,调用者对异常进行处理。
RuntimeException类及其子类都是运行时异常。运行时异常的特点是Java编译器不会对异常进行检查。也就是说,当程序中出现这类异常时,即使没有使用try..catch语句捕获或使用throws关键字声明抛出,程序也能编译通过。运行时异常一般是由程序中的逻辑错误引起的,在程序运行时无法恢复。
例如,通过数组的角标访问数组的元素时,如果角标超过了数组范围,就会发生运行时异常,代码如下所示:
int[] arr=new int[5];
System.out.println(arr[6]);
在上面的代码中,由于数组arr的length为5,最大角标应为4,当使用arr[6]访问数组中的元素就会发生数组角标越界的异常。
JDK中定义了大量的异常类,虽然这些异常类可以描述编程时出现的大部分异常情况,但是在程序开发中有时可能需要描述程序中特有的异常情况
例如,现在设计一个程序进行除法运算,且不允许除数是负数(意思就是假设我们主观地认为负数作为除数是一个异常)
显然Java不认为除数作为负数是异常,这时候就需要自定义一个异常类,但自定义的异常类必须继承自Exception或其子类。
(涉及前面继承的知识点,可移步专栏查找观看->传送门):
在实际开发中,如果没有特殊的要求,自定义的异常类只需继承Exception类,在构造方法中使用super()语句调用Exception的构造方法即可。
从运行结果可以看出,程序在编译时就发生了异常。因为在一个方法内使用throw关键字抛出异常对象时,需要使用try…catch语句对抛出的异常进行处理,或者在divide()方法上使用throws关键字声明抛出异常,由该方法的调用者负责处理。但是程序没有这样做。 为了解决上面的问题,对程序进行修改,在divide()方法上,使用throws关键字声明抛出DivideByMinusException异常,并在调用divide()方法时使用try…catch语句对异常进行处理。
public class Main {
public static void main(String[] args) {
try {
int res = divide(4, -2);
System.out.println(res);
} catch (DivideByMinusException e) {
System.out.println(e.getMessage());
}//try...catch捕获并处理异常
}
static int divide(int x, int y) throws DivideByMinusException {//方法这里不要忘了用throws关键字声明要抛出异常
if (y < 0) {
throw new DivideByMinusException("除数是负数");
}
int res = x / y;
return res;
}
}
class DivideByMinusException extends Exception {//自定义异常子类继承Exception类
public DivideByMinusException() {
super(); // 调用Exception无参的构造方法
}
public DivideByMinusException(String message) {
super(message); // 调用Exception有参的构造方法
}
}
上述代码中的main()方法中,第3~8行代码使用try…catch语句捕获处理divide()方法抛出的异常。在调用divide()方法时,如果传入的除数不能为负数,程序会抛出一个自定义的DivideByMinusException异常,该异常最终被catch代码块捕获处理,并打印出异常信息。