Java 异常的捕获与处理详解
一、异常的产生
异常是程序之中导致程序中断的一种指令流,异常一旦出现并且没有进行合理处理的话,那么程序就将中断执行。
下面,通过两个程序来进行异常产生问题的对比。
(1)不产生异常的程序:
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
int result = 10 / 2;
System.out.println("2、除法计算结果:" + result);
System.out.println("3、除法计算结束。");
}
}
运行结果:
1、除法计算开始。
2、除法计算结果:5
3、除法计算结束。
(2)产生异常的程序
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
int result = 10 / 0; // 会出现错误
System.out.println("2、除法计算结果:" + result);
System.out.println("3、除法计算结束。");
}
}
运行结果:
1、除法计算开始。Exception in thread "main"
java.lang.ArithmeticException: / by zero
at Test.main(Test.java:4)
一旦产生异常,我们发现产生异常的语句以及之后的语句将不再执行,默认情况下是进行异常信息的输出,而后自动结束程序的执行。
现在,我们要做的是:即使程序出现了异常,也要让程序正确的执行完毕。
二、异常的处理
如果希望程序出现异常之后程序依然可以正常的完成的话,那么就可以使用如下的格式进行异常的处理:
try {
可能出现异常的语句 ;
} [ catch (异常类型 异常对象) {
处理异常 ;
} catch (异常类型 异常对象) {
处理异常 ;
} ... ] [finally {
不管是否出现异常,都执行此代码 ;
}]
现在,使用以上的操作处理异常处理前面除法于是出现的异常:
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
try {
int result = 10 / 0; // 异常
System.out.println("2、除法计算结果:" + result); // 之前语句有异常,此语句不再执行
} catch (ArithmeticException e) {
System.out.println(e); // 异常处理:输出错误信息,java.lang.ArithmeticException:/ by zero
}
System.out.println("3、除法计算结束。");
}
}
运行结果:
1、除法计算开始。
java.lang.ArithmeticException: / by zero
3、除法计算结束。
可以发现,加入了异常处理之后,程序中即使有了异常,程序也可以正常的执行完毕,但是异常处理时的错误输出信息和之前相比,出错的信息不明确了,那么为了让错误的信息更加的完整,一般都会调用printStackTrace()方法进行异常信息的打印,这个方法打印的异常信息是最完整的:
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
try {
int result = 10 / 0; // 异常
System.out.println("2、除法计算结果:" + result); // 之前语句有异常,此语句不再执行
} catch (ArithmeticException e) {
e.printStackTrace(); // 异常处理:输出错误信息
}
System.out.println("3、除法计算结束。");
}
}
运行结果:
1、除法计算开始。
java.lang.ArithmeticException: / by zero
at Test.main(Test.java:5)
3、除法计算结束。
此时发现,打印的信息是很完整的。
除了try…catch格式处理异常外,还可以使用try…catch..finally:
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
try {
int result = 10 / 1;
System.out.println("2、除法计算结果:" + result);
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("3、除法计算结束。");
}
}
运行结果:
1、除法计算开始。
2、除法计算结果:10
不管是否出现异常都执行
3、除法计算结束。
但是,对于之前的程序又有了问题:现在执行数学计算的两个参数,都是由程序默认提供,那么如果说现在两个计算的参数通过初始化参数传递呢?
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
try {
int x = Integer.parseInt(args[0]); // 接收参数
int y = Integer.parseInt(args[1]); // 接收参数
int result = x / y;
System.out.println("2、除法计算结果:" + result);
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("3、除法计算结束。");
}
}
这个时候,数据由外部传送,那么就有可能出现以下几类问题:
(1)执行时不输入参数(java TestDemo),ArrayIndexOutOfBoundsException,未处理;
(2)输入的参数不是数字(java TestDemo a b),NumberFormatException,未处理;
(3)被除数为0(java TestDemo 10 0),ArithmeticException,已处理。
可以发现,以上的程序实际上是存在三种异常,而程序之中只能够处理一种,而对于不能处理的异常,发现程序依然会直接中断执行。
加入多个catch:
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
int result = x / y;
System.out.println("2、除法计算结果:" + result);
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("3、除法计算结束。");
}
}
现在,程序比之前更健壮了。
三、异常的处理流程
以上已经完成了异常的基本处理,但是所有的异常都像之前那样一条条的判断似乎是一件不可能完成的任务,因为日后肯定会接触到一些不常见的异常信息,那么下面就必须首先研究异常的流程和结构。
先查看两个异常类的继承结构:
(1)ArithmeticException:
java.lang.Object
|- java.lang.Throwable
|- java.lang.Exception
|- java.lang.RuntimeException
|- java.lang.ArithmeticException
(2)ArrayIndexOutOfBoundsException:
java.lang.Object
|- java.lang.Throwable
|- java.lang.Exception
|- java.lang.RuntimeException
|- java.lang.IndexOutOfBoundsException
|-java.lang.ArrayIndexOutOfBoundsException
可以发现,所有的异常类型最高的继承类是Throwable,Throwable下有两个子类:
(1)Error:指的是JVM错误,这个时候的程序并没有执行,无法处理;
(2)Exception:指的是程序之中出现的错误信息,可以进行异常处理。
通过继承关系可以发现,在进行日后异常处理的时候是以Exception为主,并且可以形成以下的异常处理流程:
(1)如果程序中产生了异常,那么JVM根据异常的类型,实例化一个指定异常类的对象;
(2)如果这时程序中没有任何的异常处理操作,则这个异常类的实例化对象将交给JVM进行处理,而JVM的默认处理方式就是进行异常信息的输出,而后中断程序执行;
(3)如果程序中存在了异常处理,则会由try语句捕获产生的异常类对象;
(4)与try之后的每一个catch进行匹配,如果匹配成功,则使用指定的catch进行处理,如果没有匹配成功,则向后面的catch继续匹配,如果没有任何的catch匹配成功,则这个时候将交给JVM执行默认处理;
(5)不管是否有异常都会执行finally程序,如果此时没有异常,执行完finally,则会继续执行程序之中的其他代码,如果此时有异常没有能够处理(没有一个catch可以满足),那么也会执行finally,但是执行完finally之后,将默认交给JVM进行异常的信息输出,并且程序中断。
通过以上的分析可以发现,实际上catch捕获异常类型的操作,就和方法接收参数是一样的,那么按照之前所学习过的对象多态性来讲,所有的异常类都是Exception的子类,那么这个时候,实际上所有的异常都可以使用Exception进行接收:
public class Test {
public static void main(String args[]) {
System.out.println("1、除法计算开始。");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
int result = x / y;
System.out.println("2、除法计算结果:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("3、除法计算结束。");
}
}
这时应该可以感受到异常处理所带来的好处了。但是这种操作也存在一种问题:如果在一些异常处理要求严格的项目之中,异常必须分别处理,如果现在异常的处理要求不是很严格,直接编写Exception就足够了。
上一篇: 回溯之八皇后问题