认识异常
什么是异常?
所谓异常指的就是程序在 运行时 出现错误时通知调用者的一种机制.
例如:将 System.out.println 拼写错了, 写成了 system.out.println. 此时编译过程中就会出错, 这是 “编译期” 出错.
而运行时指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误.
1.除以 0
System.out.println(10 / 0);
// 执行结果
Exception in thread "main" java.lang.ArithmeticException: / by zer
2.数组下标越界
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
3.访问 null 对象
public class Test {
public int num = 10;
public static void main(String[] args) {
Test t = null;
System.out.println(t.num);
}
}
// 执行结果
Exception in thread "main" java.lang.NullPointerException
防御式编程
错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时通知程序猿. 我们有两种主要的方式
LBYL: Look Before You Leap. 在操作之前就做充分的检查.
EAFP: It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理.
异常的核心思想就是 EAFP.
异常
1捕获异常
ry{
有可能出现异常的语句 ;
}[catch (异常类型 异常对象) {
} ... ]
[finally {
异常的出口
}]
使用处理异常:
int[] arr = {1, 2, 3};
try {
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
// 打印出现异常的调用栈
e.printStackTrace();
}
System.out.println("after try catch");
// 执行结果
before
java.lang.ArrayIndexOutOfBoundsException: 100
at demo02.Test.main(Test.java:10)
after try catch
一旦 try 中出现异常, 那么 try 代码块中的程序就不会继续执行, 而是交给 catch 中的代码来执行. catch 执行完毕会继续往下执行.
在异常处理中,如释放资源,关闭数据库、关闭文件应由finally语句来完成。无论是否存在异常, finally 中的代码一定都会执行到.
但是注意:finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果
finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
2.关于异常的处理方式
方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为 “虚拟机栈” 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.
一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式. 因此可以搭配多个 catch 代码块.
如果多个异常的处理方式是完全相同, 也可以写成这样
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}
注:由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.但如果不用不同的异常来捕捉,只知道在哪里出错,但不能很快知道是因为什么原因报错了。
还有一种情况:
public void getCustomerInfo() {
try {
// do something that may cause an Exception
} catch (java.io.FileNotFoundException ex) {
System.out.print("FileNotFoundException!");
} catch (java.io.IOException ex) {
System.out.print("IOException!");
} catch (java.lang.Exception ex) {
System.out.print("Exception!");
}
}
//IOException!
当用多个catch语句时,catch语句块在次序上有先后之分。从最前面的catch语句块依次先后进行异常类型匹配,这样如果父异常在子异常类之前,那么首先匹配的将是父异常类,子异常类将不会获得匹配的机会,也即子异常类型所在的catch语句块将是不可到达的语句。所以,一般将父类异常类即Exception老大放在catch语句块的最后一个。
public static void main(String[] args) {
func();
System.out.println("after try catch");
}
public static void func() {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at demo02.Test.func(Test.java:14)
at demo02.Test.main(Test.java:8)
如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止
2.异常处理流程
1: 程序先执行 try 中的代码 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
2:如果找到匹配的异常类型, 就会执行 catch 中的代码 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
3:无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行). 如果上层调用者也没有处理的了异常,
4:就继续向上传递. 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
3.抛出异常
除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作.
public static void main(String[] args) {
System.out.println(divide(10, 0));
}
public static int divide(int x, int y) {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
}
return x / y;
}
// 执行结果
Exception in thread "main" java.lang.ArithmeticException: 抛出除 0 异常
at demo02.Test.divide(Test.java:14)
at demo02.Test.main(Test.java:9)
我们可以使用 throws 关键字, 把可能抛出的异常显式的标注在方法定义的位置. 从而提醒调用者要注意捕获这些异常.
public static int divide(int x, int y) throws ArithmeticException {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
}
return x / y;
}
下图表示 Java 内置的异常类之间的继承关系:
其中 Error 指的是 Java 运行时内部错误和资源耗尽错误. 应用程序不抛出此类异常. 这种内部错误一旦出现,除了告知用户并使程序终止之外, 再无能无力. 这种情况很少现.
Exception 是我们程序猿所使用的异常类的父类.
Java语言规范将派生于 Error 类或RuntimeException 类的所有异常称为 非受查异常, 所有的其他异常称为 受查异常.