文章目录
1. 认识异常
1.1 初始异常
在我们刚开始接触Java SE的时候,也接触到了一些异常。如限免
(1)除以0
System.out.printLn(5/0);
执行结果如下:
(2)数组下标越界
int [] arr={1,2,3};
System.out.println(arr[10]);
(3)访问null对象
public class Text {
int num = 10;
public static void main(String[] args) {
Text text = null;
System.out.println(text.num);
}
}
那么我们在学习Java SE 的时候接触了这么多的异常,那么什么呢?异常就是程序在运行时 出现错误时通知调用者的一种机制。
关键字“运行时”
我们在写代码时,有时候会出现这样的错误,有些关键字写错。比如System.out.println写成system.out.println,此时编译过程中就会出错,这是“编译期”出错。
而运行时指的是程序已经编译通过得到了class文件了,再由JVM执行过程中出现的错误。
1.2 防御式编程
错误在代码中时客观存在的,因此我们要让程序出现问题的时候及时通知程序员。我们主要有两种主要的方式:
(1)LBYL :Look Before You Leap,在操作之前做好充分的检查;
(2) EAFP: It is Easier to Ask Forgiveness than Permission."事后获取原谅此事前获取许可更容易“,也就是先操作,遇到问题在处理。
1.3 异常的好处
我在这里用伪代码演示一局王者荣耀的过程:
LBYL 风格的代码(不使用异常)
boolean ret=false;
ret=登录游戏();
if(!ret){
处理登录游戏错误;
return;
}
ret=开始匹配();
if(!ret){
处理匹配错误;
return;
}
ret=游戏确认();
if(!ret){
处理游戏确认错误;
return;
}
ret=选择英雄():
if(!ret){
处理选择英雄错误;
return;
}
ret=载入游戏画面();
if(!ret){
处理载入游戏错误;
return;
}
EAFP 风格的代码
try{
登录游戏();
开始匹配();
游戏确认();
选择英雄();
载入画面游戏();
}catch(登录游戏异常){
处理登录游戏异常;
}catch(开始匹配异常){
处理匹配异常;
}catch(游戏确认异常){
处理游戏确认异常;
}catch(选择英雄异常){
处理选择英雄异常;
}catch(载入画面游戏异常){
处理载入画面游戏异常;
}
对于两种不同风格的代码,我们可以发现,使用第一种方式,正确流程和错误流程是混在一起的,代码整体显得比较混乱,而第二种方式正确流程和错误是分开的,更容易理解代码。
2.异常的基本语法
2.1捕获异常
基本语法:
try{
有可能出现异常的语句;
}[catch(异常类型 异常对象){
}...]
[finally{
异常的出口
}]
- try 代码块中的放的是可能出现代码的异常
- catch代码块中放的是出现异常后的处理行为
- finally代码块中的代码用于处理善后工作,会在最后执行
- 其中catch和finally都可以根据情况选择加或者不加。
2.2不处理异常
public class Text {
public static void main(String[] args) {
int [] arr={1,2,3};
System.out.println("before");
System.out.println(arr[10]);
System.out.println("after");
}
}
我们发现一旦出现异常,程序就终止了,after没有正确输出。
2.3使用try catch后的执行过程
public class Text {
public static void main(String[] args) {
int [] arr={1,2,3};
try{
System.out.println("before");
System.out.println(arr[10]);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
//打印出现异常的调用栈
e.printStackTrace();
}
System.out.println("after try catch");
}
}
执行结果:
我们发现,一旦try中出现异常,那么try代码块中的程序就不会继续执行,而是交给catch中的代码来执行,catch执行完毕后,会继续往下执行。
关于调用栈
方法之间是存在相互调用关系的,这种调用关系我们可以用"调用栈"来描述,在JVM中有一块内存空间称为"虚拟机栈"专门存储方法之间的调用关系。当代码发现异常的时候,我们就可以使用e.printStackTrace();的方式查看出现异常代码的调用栈。
2.4 catch只能处理对应种类的异常
public class Text {
public static void main(String[] args) {
int [] arr=null;
try{
System.out.println("before");
System.out.println(arr[10]);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
//打印出现异常的调用栈
e.printStackTrace();
}
System.out.println("after try catch");
}
}
执行结果:
此时,catch语句不能捕获到刚才的空指针异常,因为异常类型不匹配。
2.5 catch可以是多个
public class Text {
public static void main(String[] args) {
int [] arr=null;
try{
System.out.println("before");
System.out.println(arr[10]);
System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
//打印出现异常的调用栈
System.out.println("这是个数组下标越界异常");
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("这是个空指针异常");
e.printStackTrace();
}
System.out.println("after try catch");
}
}
执行结果:
一段代码可能会抛出多种不同的异常,不同的异常有不同的处理方式,因此可以搭配多个catch代码块,如果多个异常的处理方式完全相同,也可以写成这样:
catch(ArrayIndexOutOfBoundsException|NullPointerException e){
....
}
2.6 可以用一个catch捕获所有的异常
public class Text {
public static void main(String[] args) {
int [] arr=null;
try{
System.out.println("before");
System.out.println(arr[10]);
System.out.println("after");
}catch(Exception e) {
//打印出现异常的调用栈
e.printStackTrace();
}
System.out.println("after try catch");
}
}
执行结果:
2.7 finally完成最后的工作
public class Text {
public static void main(String[] args) {
int[] arr = null;
try {
System.out.println("before");
System.out.println(arr[10]);
System.out.println("after");
} catch (Exception e) {
//打印出现异常的调用栈
e.printStackTrace();
} finally {
System.out.println("finally code");
}
}
}
执行结果:
无论是否存在着异常,finally中的代码一定都会执行到,也可以用finally回收资源。
2.8使用try负责回收资源
上面的代码的代码的一种等价写法,将Scanner对象在try()中创建,就能保证在try执行完毕后自动调用的Scanner的close方法:
//此时当try catch代码块执行完毕之后,Scanner就直接关闭。
import java.util.Scanner;
public class Solution {
public static void main(String[] args) {
try(Scanner scanner=new Scanner(System.in)){
//使用scanner进行若干操作
int num=scanner.nextInt();
System.out.println("num = " + num);
}catch(Exception e){
e.printStackTrace();
}
}
}
2.9 如果本方法中没有合适的处理异常的方式,就会沿着调用栈向上传递
public static void main(String [] args){
func1();
}
public static void func1(){
try{
func2();
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("异常");
}
public static void func2(){
//try{
int [] a={1,2,3};
System.out.println(a[100]);
//}catch(ArrayIndexOutOfBoundsException e){
//System.out.println("异常");
//}
}
}
//该异常可以往fun1()向上传递
如果一直向上传递,一直到最上面也没有方法来try catch,此时就回到了最开始的JVM执行的异常。