异常现象
程序错误分为三种:编译错误、运行时错误、逻辑错误
编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置。
运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。
逻辑错误是因为程序没有按照预期的逻辑顺序执行。
Java异常类
所有的异常都是由Throwable继承而来!!
Error和Exception是Throwable的两个子类。
Error类描述了Java运行时的内部错误和资源耗尽错误。应用程序不抛出此类异常,这种内部错误一旦出现,除了告知用户并使程序安全终止之外,再无能为力。这种情况很少出现。
Exception类又分为两个分支:RuntimeException和IOException。由于程序错误导致的异常属于RuntimeException(运行时异常);如果程序本身没有错误,像I/O错误这类问题导致的异常属于IOException(检查异常)。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查异常;所有的其他异常称为受查异常。
异常处理机制
异常就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制,保证程序出现异常后可以正常执行完毕。
在Java应用程序中,异常处理机制为:抛出异常、捕捉异常。
抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
捕获异常:在方法抛出异常后,运行时系统将转为寻找合适的异常处理器。潜在的异常处理器是异常发生时一次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,一次回查调用栈中的方法,直到找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
异常影响
异常时导致程序中断执行的一种指令流。程序之中如果出现异常并且没有合理处理的话就会导致程序终止执行。
观察正确程序流:
package test;
public class Test{
public static void main(String[] args){
System.out.println("1.数学计算开始前");
System.out.println("2.进行数学计算:"+10/2);
System.out.println("3.数学计算结束后");
}
}
此时没有产生任何异常,程序可以正常执行完毕。
产生异常:
package test;
public class Test{
public static void main(String[] args){
System.out.println("1.数学计算开始前");
System.out.println("2.进行数学计算:"+10/0);
System.out.println("3.数学计算结束后");
}
}
运行结果:1.数学计算开始前 Exception in thread"main"java.lang.ArithmeticException: / by zero at test.Test.main(Test.java:6)
现在程序产生了异常,但是在异常语句产生之前的语句可以正常执行完毕,而异常产生之后程序直接结束。为了保证程序出现异常后还可以继续向下执行,就需要异常处理。
异常处理格式
异常处理的语法格式如下:
try{
//有可能出现异常的语句;
}[catch (异常类 对象){
}…]
[finally{
//异常的出口
}]
对于以上三个关键字,可以出现的组合:try…catch、try…finally、try…catch…finally
举个栗子:对异常进行处理
package test;
public class Test{
public static void main(String[] args){
System.out.println("1.数学计算开始前");
try{
System.out.println("2.进行数学计算:"+10/0);
}catch(ArithmeticException e){
System.out.println("异常已被处理");
}
System.out.println("3.数学计算结束后");
}
}
上述代码出现了异常之后,由于存在异常处理机制,依然可以正常执行完毕。
虽然进行了异常处理,但是存在一个问题:你现在根本不知道程序产生了什么样的异常。所以为了取得异常信息,可以直接输出异常类对象,或者调用所有异常类中提供的printStackTrace()方法进行完整异常信息的输出。
取得异常的完整信息:
package test;
public class Test{
public static void main(String[] args){
System.out.println("1.数学计算开始前");
try{
System.out.println("2.进行数学计算:"+10/0);
}catch(ArithmeticException e){
e.printStackTrace();
}
System.out.println("3.数学计算结束后");
}
}
在进行异常处理的时候还可以使用try…catch…finally进行处理:
package test;
public class Test{
public static void main(String[] args){
System.out.println("1.数学计算开始前");
try{
System.out.println("2.进行数学计算:"+10/0);
}catch(ArithmeticException e){
e.printStackTrace();
}finally {
System.out.println("[finaly]不管是否产生异常,都执行此语句");
}
System.out.println("3.数学计算结束后");
}
}
不管此时是否产生异常,最终都要执行finally程序代码,所以finally会作为程序统一出口。
以上程序是直接固定好了两个数字进行除法运算,现在通过初始化参数来进行除法运算。
举个栗子:初始化参数进行数学运算
package test;
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]);
System.out.println("2.进行数学计算:"+x/y);
}catch(ArithmeticException e){
e.printStackTrace();
}finally {
System.out.println("[finaly]不管是否产生异常,都执行此语句");
}
System.out.println("3.数学计算结束后");
}
}
此时会出现以下问题:
1.用户没有输入初始化参数:ArrayIndexOutOfBoundsException
2.用户输入的不是数字:NumberFormatException
3.被除数为0:ArithmeticException
以上代码我们发现,用过catch捕获异常时如果没有捕获指定异常,程序依然无法进行处理,现在最直白的解决方法就是使用多个catch:
package test;
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]);
System.out.println("2.进行数学计算:"+x/y);
}catch(ArithmeticException e){
e.printStackTrace();
}
catch(NumberFormatException e){
e.printStackTrace();
} catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}finally{
System.out.println("[finaly]不管是否产生异常,都执行此语句");
}
System.out.println("3.数学计算结束后");
}
}
无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下情况finally不会被执行:
1.在finally语句块中发生了异常
2.在前面的代码中用了System.exit()退出程序
3.程序所在的线程死亡
4.关闭CPU
try、catch、finally语句块的执行顺序:
- 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
- 当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
- 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将调到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行的是finally语句块后的语句。
throws关键字
在进行方法定义的时候,如果要告诉调用者本方法可能产生哪些异常,就可以使用throws方法进行声明。该方法出现问题后不希望进行处理,就使用throws抛出。
举个栗子:使用throws定义方法:
package test;
public class Test{
public static void main(String[] args){
try{
System.out.println(calculate(10,0));
}catch(Exception e){
e.printStackTrace();
}
}
public static int calculate(int x,int y) throws Exception {
return x/y;
}
}
如果现在调用了throws声明的方法,那么在调用时必须明确的使用try…catch…进行捕获,因为该方法有可能产生异常,所以必须按照异常的方式来进行处理。
主方法本身也属于一个方法,所以主方法上也可以使用throws进行异常抛出,这个时候如果产生了异常就交给JVM处理
举个栗子:主方法抛出异常
package test;
public class Test{
public static void main(String[] args)throws Exception{
System.out.println(calculate(10,0));
}
public static int calculate(int x,int y){
return x/y;
}
}
throw关键字
throw是直接编写在语句之中,表示人为进行异常的抛出。如果现在异常类对象实例化不希望由JVM产生而由用户产生,就可以使用throw来完成。
举个栗子:使用throw产生异常类
public static int calculate(int x,int y){
try{
throw new Exception("抛个异常");
}catch(Exception e){
e.printStackTrace();
}
}
throw和throws的区别
1.throw作用在方法内部,表示人为进行异常的抛出。抛出具体异常,由方法体内的语句处理。
2.throws作用在方法声明上,表示如果抛出异常,则由该方法的调用者进行处理(可能抛出,也可能不抛出)