1. 异常概述
在Java语言中,将程序执行中发生的不正常情况称为“异常”,开发过程中的语法错误和逻辑错误不是异常。Java程序在执行过程中所发生的异常事件可分为两类:
- Error:Java 虚拟机无法解决的严重问题。如 JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError 和 OOM。一般不编写针对性的代码进行处理。
- Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。这些问题常见如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组下标越界
Error 和 Exception 的结构如下图所示:
其中 checked 为编译时异常,unchecked 为运行时异常。常见的 Error 和两种 Exception 如下所示
2. 异常处理概述
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x/y 运算时,要检测分母为0、数据为空等可能错误。如果用过多的 if-else 分支会导致程序的代码冗长,可读性差,因此都采用异常处理机制。
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
Java的异常处理机制,用的是一个“抓抛模型”
【抛:】
- 程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象(Java万物皆类,每个异常都对应一个类),并将此对象抛出,抛给程序的调用者。
- 一旦抛出对象后,其后的代码就不再执行。
【抓:】
- 也就是异常处理方式,有两种:
- try-catch-finally
- throws
3. 处理机制一:try-catch-finally
try{
// 可能出现的异常
}catch(异常类型1 变量名1){
// 处理异常1的方式
}catch(异常类型2 变量名2){
// 处理异常2的方式
}
....
finally{
// 一定会执行的代码
}
3.1 try-catch
当 try 中代码任意一行出现异常后,try内部该行代码之后就都不会执行了,跳转到对应catch处进行处理,直到执行 finally(可选)。之后,继续执行整个 try-catch-finally 结构后面的代码。<br />举个例子,用 String 向 Int 型转换异常来演示:
package pkg8;
public class Test {
public static void main(String[] args) {
String str = "abc" ;
try {
int num = Integer.parseInt(str);
System.out.println("try内部代码");
} catch (NumberFormatException exception){
System.out.println(exception);
}
System.out.println(" try-catch-finally 结构外的代码");
}
}
但是,如果 catch 里的异常类型与发生的异常类型不匹配,则依然会出错。
所有的异常类都继承于 Exception,因此只要 catch 中异常类型用 Exception,就能够匹配。
为了更直观的显示错误信息,catch 中一般都会调用 Exception 中的 getMessage() 方法,它会返回一个 String 型的异常信息。
try {
int num = Integer.parseInt(str);
System.out.println("try内部代码");
} catch (Exception exception){
System.out.println(exception.getMessage());
}
3.2 finally
finally指一定会执行的代码,可选。看上去很鸡肋,但是在一些情况下是必要的,他的一定执行性体现如下:
- 即使 try 中没有异常,finally 依然执行
- 即使 try 中没有异常且有 return,finally 照样依然执行
- 如果 catch 中出现了异常,finally 依然执行
- 即使 catch 中没有异常且有 return,finally 照样依然执行
举个例子:
package pkg8;
public class Test {
public static void main(String[] args) {
String str = "abc" ;
int num ;
try {
num = Integer.parseInt(str);
System.out.println("try内部代码");
} catch (Exception exception){
System.out.println(exception.getMessage());
num = Integer.parseInt(str) ; // catch 中有异常
} finally {
System.out.println("finally 处代码");
}
System.out.println(" try-catch-finally 结构外的代码");
}
}
运行结果为:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2643809/1626677474113-6745a5b4-cfcf-4f77-87af-1d49976ae20a.png#clientId=uaab51236-861e-4&from=paste&height=226&id=u2b6530c3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=226&originWidth=831&originalType=binary&ratio=1&size=39246&status=done&style=none&taskId=u18ec2043-c1f1-4ce2-9fad-b0b1158d781&width=831)<br />可以看到,由于 catch 中的异常没有处理,程序出错,try-catch-finally 结构外的代码不再执行,但是 finally 内部的代码依然被执行了,这就是 finally 的“一定会被执行”特性。
【什么时候用 finally?】
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。此时资源释放的操作,就需要放在 finally 中执行。
4. 处理机制二:throws + 异常类型
- “throw + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此类对象满足 throws 后的类型的话,就会被抛出。异常代码后续的代码就不再执行。
- 所谓抛出,就是交给其调用者处理,但是调用者到底有没有处理异常就不一定了。
- throws 只是将异常抛给调用者了,也就是甩锅,并没有真正处理掉异常。只有 try+catch+finally 才算真正处理掉异常了。
举个例子:
package pkg8;
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.method2();
}
public void method2(){
try {
method1();
}catch (Exception e){
System.out.println("method2 处理了 method1 的异常");
}
}
public void method1 () throws Exception {
String str = "abc";
int num;
num = Integer.parseInt(str);
}
}
上述代码的处理机制就是:异常存在于 method1 中,但是 method1 并没有处理掉该异常,而是将异常通过 throws 抛出给了其调用者。而 method2 作为 method1 的调用者,就要通过 try-catch-finally 来处理掉该异常。
5. throw 关键字
throw 关键字也用来抛出异常(不处理),它和 throws 的功能是一样的。只不过 throws 要跟在方法的声明之后,而 throw 可以单独作用。
throws 抛出一个异常类,而 throw 直接抛出一个异常对象,其使用格式为:
throw 异常类型对象 ;
比如,先定义一个自定义异常类,继承于运行时异常 RuntimException,构造器选择String单参数构造器:
package pkg8;
public class MyException extends RuntimeException{
public MyException(String message) {
super(message);
}
}
然后,method1 中,如果try体内发生了NumberFormatException,通过 throw 就可以抛给调用者处理,如果发生了NullPointerException,就自己处理。另外,还自定义了种逻辑异常,如果 a!= b,则抛出自定义的异常对象给调用者处理。
package pkg8;
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.method2();
}
public void method2(){
try {
method1(10,20);
}catch (Exception e){
System.out.println("method2 处理了 method1 的异常");
}
}
public void method1(int a,int b) {
String str = "abc";
int num ;
try{
num = Integer.parseInt(str);
}catch(NullPointerException e){
}
if(a!=b)
throw new MyException("自定义异常");
}
}