认识异常对程序的影响
- Java语言提供的最为强大的支持就在于异常的处理操作上;
- 异常指的是导致程序中断执行的一种指令流;
//没有异常产生的程序
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
System.out.println("***** 数学计算:" + (10 / 2));
System.out.println("***** 程序执行完毕 *******");
}
}
- 在程序执行正常的过程里面会发现,所有的程序会按照既定的结构从头到尾开始执行;
//产生异常的程序
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
System.out.println("***** 数学计算:" + (10 / 0));
System.out.println("***** 程序执行完毕 *******");
}
}
***** 程序开始执行 *******
Exception in thread "main" java.lang.ArithmeticException: / by zero
at demo.JavaDemo.main(JavaDemo.java:5)
- 在出现错误之后,整个程序将不会按照既定的方式进行执行而是中断了执行,为了保证程序出现非致命错误之后程序依然可以正常完成,所以就需要有一个完善的异常处理机制,以保证程序的顺利执行;
处理异常
- 在java之中如果要进行异常的处理,可以使用try、catch、finally这几个关键字来完成,其基本的处理结构如下:
try {
//可能出现异常的语句
} [catch(异常类型 异常对象) {
//异常处理
} catch(异常类型 异常对象) {
//异常处理
} catch(异常类型 异常对象) {
//异常处理
} ...] [finally {
//不管异常是否处理都要执行
}]
- 在此格式之中可以使用的组合:try...catch、try...catch...finally、try...finally;
//处理异常
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
try {
System.out.println("***** 数学计算:" + (10 / 0));
} catch (ArithmeticException e) {
System.out.println(e);
}
System.out.println("***** 程序执行完毕 *******");
}
}
***** 程序开始执行 *******
java.lang.ArithmeticException: / by zero
***** 程序执行完毕 *******
- 此时就可以发现即便出现了异常,程序也可以正常执行完毕,所以此时的设计处于一个合理设计,但是一个问题出现了,此时在进行异常处理时直接输出的是一个异常类的对象,那么对于此对象如果直接打印(调用toString方法)所得到的异常信息并不完整,如果要想获得非常完整的异常信息,可以使用异常类中提供的printStackTrace()方法;
//获取完整异常信息
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
try {
System.out.println("***** 数学计算:" + (10 / 0));
} catch (ArithmeticException e) {
e.printStackTrace();
}
System.out.println("***** 程序执行完毕 *******");
}
}
- 对于异常的处理格式也可以在最后追加一个finally程序块表示异常处理后的出口,不管是否出现异常都执行;
//使用finally语句
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
try {
System.out.println("***** 数学计算:" + (10 / 0));
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("***** 程序执行完毕 *******");
}
}
- 此时程序中有异常执行finally,没有异常也执行finally;
处理多个异常
- 很多时候呢在程序执行过程之中可能会产生若干个异常,那么这种情况下也可以使用多个catch进行异常的捕获;
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInts(args[1]);
System.out.println("***** 数学计算:" + (x / y));
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("***** 程序执行完毕 *******");
}
}
- 那么对于此时的程序就有可能产生三类异常:
- 【未处理】程序执行的时候没有输入初始化参数:java.lang.ArrayIndexOutOfBoundsException;
程序执行结果 |
---|
***** 程序开始执行 ******* 不管是否出现异常都执行 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0at demo.JavaDemo.main(JavaDemo.java:6) |
- 【未处理】输入时的数据不是数字:java.lang.NumberFormatException;
程序执行结果 |
---|
***** 程序开始执行 ******* 不管是否出现异常都执行 Exception in thread "main" java.lang.NumberFormatException: For input string: "a" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang.Integer.parseInt(Integer.java:615) at demo.JavaDemo.main(JavaDemo.java:6) |
- 【已处理】输入的被除数为0:java.lang.ArithmeticException;
程序执行结果 |
---|
***** 程序开始执行 ******* java.lang.ArithmeticException: / by zero at demo.JavaDemo.main(JavaDemo.java:8) 不管是否出现异常都执行 ***** 程序执行完毕 ******* |
- 即便有了异常处理语句,但是如果没有进行正确的异常捕获,那么程序中断执行(finally的代码依然执行),所以这样一个情况下,必须进行多个异常的捕获;
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("***** 数学计算:" + (x / y));
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("***** 程序执行完毕 *******");
}
}
异常处理流程
此图来源于李兴华老师- 在程序运行过程之中才会产生异常,而一旦程序执行中产生了异常之后将自动进行指定类型的异常类对象实例化处理;
- 如果此时程序之中并没有提供异常处理的支持,则会采用JVM默认异常处理方式,首先进行异常信息打印,而后直接退出当前程序;
- 此时程序中如果存在异常处理,那么这个产生的异常类的实例化对象将会被try语句捕获;
- try捕获到异常之后与其匹配的catch中的异常类型进行以此比对,如果此时与catch中的捕获异常类型相同,则认为应该使用此catch进行异常处理,如果不匹配则继续匹配后续的catch类型,如果没有任何的catch匹配成功,那么就表示该异常无法处理;
- 不管异常是否处理最终都要执行finally语句,当执行完成finally的程序后会近一步判断当前的异常是否已经处理过了,如果处理过了则继续向后执行其他代卖,如果没有处理则交由JVM进行默认异常处理处理;
NumberFormatException | ArrayIndexOutOfBoundsException |
---|---|
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.IllegalArgumentException java.lang.NumberFormatException |
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.IndexOutOfBoundsException java.lang.ArrayIndexOutOfBoundsException |
- 可以发现在程序之中可以处理的最大的类型就是Throwable,而打开Throwable可以观察在此类中提供有两个子类:
- Error:此时程序还未执行出现的错误,开发者无法处理;
- Exception:程序中出现的异常,开发者可以处理,真正开发之中需要关注的就是Exception;
- 通过分析可以发现异常产生的时候会产生异常的实例化对象,那么按照对象的引用原则,可以自动向父类转型,那么如果按照这样的逻辑,实际上所有的异常都可以使用Exception来处理;
package demo;
public class JavaDemo {
public static void main(String [] args) {
System.out.println("***** 程序开始执行 *******");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("***** 数学计算:" + (x / y));
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("不管是否出现异常都执行");
}
System.out.println("***** 程序执行完毕 *******");
}
}
- 当你不确定可能产生哪些异常的时候,这种处理方式是最方便的,但是如果这样的处理也会产生一个问题,这种异常的处理形式虽然方便,但是描述的错误信息不明确,所有分开处理异常是一种更加明确的处理方式;
- 在以后进行多个异常同时处理的时候,要把捕获范围大的异常放在捕获范围小的异常之后;
throws关键字
- 通过之前的程序可以发现,在执行程序的过程之中,有可能会产生异常,但是如果说现在假设你定义来一个方法,实际上就应该明确的告诉使用者,这个方法可能会产生何种异常,那么此时就可以在方法的声明上使用throws关键字来进行异常类型的标注;
//观察throws的使用
package demo;
class MyMath {
//这个代码执行的时候可能会产生异常,如果产生异常了调用处处理
public static int div(int x, int y) throws Exception {
return x / y ;
}
}
public class JavaDemo {
public static void main(String [] args) {
try {
System.out.println(MyMath.div(10, 0));
} catch(Exception e) {
e.printStackTrace();
}
}
}
- 主方法本身也是一个方法,实际上主方法也可以继续向上抛出;
//在主方法上继续抛出异常
package demo;
class MyMath {
//这个代码执行的时候可能会产生异常,如果产生异常了调用处处理
public static int div(int x, int y) throws Exception {
return x / y ;
}
}
public class JavaDemo {
public static void main(String [] args) throws Exception {
System.out.println(MyMath.div(10, 0));
}
}
- 如果主方法继续向上抛出异常,那么就表示此异常将交由JVM负责处理;
throw关键字
- 与throws对应的还有throw关键字,此关键字的主要作用在于表示手工进行异常的抛出,即:此时将手工产生一个异常的实例化对象,并且进行异常的抛出处理;
//观察throw的使用
package demo;
public class JavaDemo {
public static void main(String [] args) {
try { //异常对象不再由系统生成,而是手工定义的
throw new Exception("自己抛着玩的对象");
} catch(Exception e) {
e.printStackTrace();
}
}
}
- throws和throw的区别:
- throw:是在代码块中使用的,主要是手工进行异常对象的抛出;
- throws:是在方法定义上使用的,表示将此方法中可能产生的异常明确告诉给调用出,由调用出进行处理;
异常处理的标准格式
现在要求定义一个除法计算的方法,在这个方法之中,开发要求如下:
- 在进行数学计算开始与结束的时候进行信息提示;
- 如果在进行计算的过程之中产生来异常,则交给调用出来处理;
package demo;
class MyMath {
public static int div(int x, int y) throws Exception {
int temp = 0;
System.out.println("除法计算开始");
try {
temp = x / y;
} catch(Exception e) {
throw e;
} finally {
System.out.println("除法计算结束");
}
return temp;
}
}
public class JavaDemo {
public static void main(String [] args) throws Exception {
try {
System.out.println(MyMath.div(10, 0));
} catch(Exception e) {
e.printStackTrace();
}
}
}
- 对于此类操作实际上可以简化,省略掉catch和throw;
package demo;
class MyMath {
public static int div(int x, int y) throws Exception {
int temp = 0;
System.out.println("除法计算开始");
try {
temp = x / y;
} finally {
System.out.println("除法计算结束");
}
return temp;
}
}
public class JavaDemo {
public static void main(String [] args) throws Exception {
try {
System.out.println(MyMath.div(10, 0));
} catch(Exception e) {
e.printStackTrace();
}
}
}
RuntimeException
- 通过之前的分析,只要方法后面带有throws往往都是告诉用户本方法可能产生异常是什么,所以这个时候来观察一段代码:
package demo;
public class JavaDemo {
public static void main(String [] args) {
int num = Integer.parseInt("1213");
System.out.println(num);
}
}
- 打开Integer类中的parseInt()方法来观察:public static int parseInt(String s) throws NumberFormatException,这个方法明确的抛出来一个异常,但是在处理的时候并没有强制性要求处理,观察一下NumberFormatException类的继承结构,同时也观察数学异常的继承结构:
NumberFormatException | ArithmeticException |
---|---|
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.IllegalArgumentException java.lang.NumberFormatException |
java.lang.Object java.lang.Throwable java.lang.Exception java.lang.RuntimeException java.lang.ArithmeticException |
- 如果现在所有的程序执行上只要使用了throws定义的方法都必须要求开发者进行手工处理,那么这个代码的编写就太麻烦类,所以在设计的过程之中,考虑到代码编写的方便,提供了一个灵活的可选的异常处理父类“ RuntimeException”,这个类的异常的子类可以不需要强制性处理;
- RuntimeException与Exception的区别:
- RuntimeException是Exception的子类;
- RuntimeException标注的异常可以不需要进行强制性处理,而Exception异常必须强制处理;
自定义异常类
- 在JDK之中提供有大量的异常类型,但是在实际的开发之中可能这些异常类型未必够用,不可能所有的设计里面都只抛出Exception,所以这个时候就需要考虑进行自定义异常类,但是对于自定义异常也有两种实现方案:继承Exception或者继承RuntimeException;
//实现自定义异常
package demo;
class BombException extends Exception {
public BombException(String msg) {
super(msg);
}
}
class Food {
public static void eat(int num) throws BombException {
if(num > 10) {
throw new BombException("吃太多了");
} else {
System.out.println("正常吃");
}
}
}
public class JavaDemo {
public static void main(String [] args) throws Exception {
Food.eat(11);
}
}
assert关键字
- 从JDK1.4开始追加有一个断言的功能,确定代码执行到某行之后一定是所期待的结果,对于断言而言,并不一定是准确的,也有可能出现偏差,这种偏差不应该影响程序的正常执行;
//断言的使用
package demo;
public class JavaDemo {
public static void main(String [] args) throws Exception {
int x = 10;
//中间经历了很多的x变量的操作步骤
assert x == 100 : "x的内容不是100";
}
}
- 如果现在要想执行断言,则必须在程序执行的时候加入参数:
java -ea JavaDemo | Exception in thread "main" java.lang.AssertionError: x的内容不是100 at demo.JavaDemo.main(JavaDemo.java:7) |
---|
- 所以Java里面并没有将断言设置为一个程序必须执行的步骤,需要特定环境下才可以开启;
网友评论