阿里云【名师课堂】Java面向对象开发89 ~ 96:【第04个代码模型】异常的捕获与处理
异常是导致程序中断执行的一种指令流,就是程序运行时可能出现一些错误,比如试图打开一个不存在的文件等。异常处理将会改变程序的控制流程,让程序有机会对错误进行处理。
89:观察异常带来的问题
范例:首先观察一个没有异常的简单程序
public class TestDemo {
public static void main(String args[]) {
System.out.println("【1】数学计算开始前:" + 4 + " " + 7) ;
System.out.println("【2】数学计算:" + 4 + "*" + 7) ;
System.out.println("【3】数学计算结束后:" + (4 * 7)) ;
System.out.println("【4】done") ;
}
}
范例:观察产生异常的程序
public class TestDemo {
public static void main(String args[]) {
System.out.println("【1】数学计算开始前:" + 17 + " " + 0) ;
System.out.println("【2】数学计算:" + 17 + "/" + 0) ;
System.out.println("【3】数学计算结束后:" + (17 / 0)) ;
System.out.println("【4】done") ;
}
}
根据输出结果可以看出,异常产生的语句(第5句)之前的代码都正常执行,异常产生之后程序将直接结束运行,不管后面什么代码。
为了保证程序出现异常之后还能继续向下运行,就需要进行异常处理。
90:异常处理格式
异常处理格式如下:
try {
有可能出现异常的语句 ;
} [catch (异常类 对象) {
异常的处理语句 ;
} catch (异常类 对象) {
异常的处理语句 ;
}] // []中为可选内容
finally {
异常的统一出口 ;
}
对于以上三个关键字:try
、catch
、finally
,可能出现的组合为:
try···catch
try···finally
try···catch···finally
try···catch
范例:对异常进行处理
public class TestDemo {
public static void main(String args[]) {
System.out.println("【1】数学计算开始前:" + 17 + " " + 0) ;
System.out.println("【2】数学计算:" + 17 + "/" + 0) ;
try {
System.out.println("【3】数学计算结束后:" + (17 / 0)) ;
} catch (ArithmeticException e) { // java.lang包自动导入,不用写上
System.out.println("【CATCH】异常已经被处理") ;
}
System.out.println("【4】done") ;
}
}
发现出现异常之后,由于程序存在有异常的处理机制,所以可以正常执行完毕。
但是此时存在一个问题:虽然异常被处理了,但是还是不知道程序究竟出现了什么样的异常,所以为了明确取得异常的信息
- 可以直接输出异常类对象(任何对象直接打印都会调用toString方法),缺点是不知道异常出现在何处(行数);
System.out.println(异常对象名) ;
- 比如:
System.out.println("【CATCH】异常已经被处理" + e) ;
- 推荐: 或者调用所有异常类中都提供的
printStackTrace()
方法进行完整异常信息的输出(异常类型和行数)。异常对象名.printStackTrace() ;
- 比如:
e.printStackTrace() ;
try···catch···finally
在执行try···catch
语句之后执行finally
语句。也就是说,无论在try
部分是否有异常发生,finally
子语句都会执行。
范例:观察try···catch···finally操作
public class TestDemo {
public static void main(String args[]) {
System.out.println("【1】数学计算开始前:" + 17 + " " + 0) ;
System.out.println("【2】数学计算:" + 17 + "/" + 0) ;
try {
System.out.println("【3】数学计算结束后:" + (17 / 0)) ;
} catch (ArithmeticException e) {
e.printStackTrace() ;
} finally {
System.out.println("【FINALLY】不管是否产生异常都执行此语句") ;
}
System.out.println("【4】done") ;
}
}
修改上面程序的数学运算为17/1之后:
所以,finally类就是本节开头说到的程序的出口。
下面改进程序,要求初始化两个参数传入程序来进行计算。
范例:接收参数进行除法计算
public class TestDemo {
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) ;
System.out.println("【3】数学计算结束后:" + (x / y)) ;
} catch (ArithmeticException e) {
e.printStackTrace() ;
} finally {
System.out.println("【FINALLY】不管是否产生异常都执行此语句") ;
}
System.out.println("【4】done") ;
}
}
正确的执行:
错误的执行:
- 输入参数数量不对(小于需要的参数)
- 输入的参数不是数字
可以发现,上面两种异常情况都没有得到处理(catch语句没有执行),因为catch语句在这里只能捕获、处理被除数为0的异常(ArithmeticException
)。
怎么解决?
简单粗暴(并不推荐):使用多个catch语句涵盖所有可能异常的情况。
catch (ArithmeticException e) {
e.printStackTrace() ;
} catch (NumberFormatException e) {
e.printStackTrace() ;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace() ;
}
这样虽然可以实现要求的功能,但是并没有实际意义。以后会讲解正确的方法。
91:throws关键字
在进行方法调用时,如果要告诉调用者本方法可能会产生哪些异常,可以使用throws关键字进行声明这些异常。
- 使用throws关键字定义方法时一定要有
try···catch
块语句中调用能发生异常的方法,因为catch可以捕获异常对象,而定义的throws方法可能会产生异常,所以必须按照有异常产生的情况来处理。
范例:使用throws定义方法
class MyMath {
// 此处明确告诉用户该方法上会有异常
public static int div(int x, int y) throws Exception {
return x / y ;
}
}
public class TestDemo {
public static void main(String args[]) {
try {
System.out.println("结果:" + MyMath.div(8, 2)) ;
} catch (Exception e) {
e.printStackTrace() ;
}
}
}
正常执行:
出现异常:
主方法本身也属于一个方法,所以主方法上也可以使用throws定义方法,这时如果产生了一场,就交给JVM进行处理
- 这时不需要
try···catch
语句。
class MyMath {
// 此处明确告诉用户该方法上会有异常
public static int div(int x, int y) {
return x / y ;
}
}
public class TestDemo {
public static void main(String args[]) throws Exception {
System.out.println("结果:" + MyMath.div(8, 0)) ;
}
}
92:throw关键字
throw是直接编写在语句之中的,表示人为进行异常的抛出。
- 比如,对于数学运算:
8 / 0
,产生的异常ArithmeticException
是由JVM负责进行异常类对象实例化catch (ArithmeticException e)
。而现在如果不希望异常类对象由系统产生,而是希望由用户控制异常类实例化对象的产生,就可以使用throw关键字。
范例:使用throw产生异常类对象
public class TestDemo {
public static void main(String args[]) {
try {
throw new Exception("抛,都给我抛") ; //实例化异常类对象
} catch (Exception e) {
e.printStackTrace() ;
}
}
}
一般情况下,throw的使用要与条件判断语句结合。
throws和throw的区别
- throws用于方法声明时,主要明确告诉用户本方法可能产生的异常,同时该方法可能不处理此异常。
- throw用于方法内部,主要表示进行人为的抛出
93:异常处理模型(重点)
本节通过一个案例讲解异常处理标准格式。上面已经讲解了try、catch、finally、throw、throws概念,本节将它们串联起来。
现在要求编写一个方法——除法操作,要求如下:
- 在进行除法操作之前要先打印一行语句;
- 如果在除法运算的过程中出现有错误,则应该将异常返回给调用处;
- 不管最终是否有错误产生,都要求打印一行计算结束的信息。
class MyMath {
// 此处明确告诉用户该方法上会有异常
public static int div(int x, int y) throws Exception {
int result = 0 ;
System.out.println("【开始】") ; // 2、打印【开始】
try { // 3、进行除法运算
result = x / y ; // 不使用try···catch的话,此处如果有异常,后面不执行
} catch (Exception e) { // 其实这里的catch语句可以省略
throw e ; // 4、如果有异常,抛出到本方法的throws Exception
} finally {
System.out.println("【结束】") ; // 5、不管有没有异常,都执行finally,打印结束
}
return result ;
}
}
public class TestDemo {
public static void main(String args[]) {
try { // 对应MyMath类中的throws
System.out.println(MyMath.div(8, 0)) ; // 1、调用div方法,在div方法中的输出完成后进行输出
} catch (Exception e) { // 6、捕获到div方法传过来的异常,输出异常信息
e.printStackTrace() ;
}
}
}
94:RuntimeException
讲解之前先看一段简单程序:
public class TestDemo {
public static void main(String args[]) {
String str = "100" ;
int num = Integer.parseInt(str) ;
System.out.println(num * 2) ;
}
}
观察Integer类中关于parseInt()方法的定义
public static int parseInt(String s) throws NumberFormatException
可以看到parseInt方法已经明确抛出了异常,但是在进行调用的时候可以发现即使没有进行异常处理,也可以正常执行,为什么?
RuntimeException
类
观察NumberFormatException
的继承结构:
Class NumberFormatException
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
java.lang.IllegalArgumentException
java.lang.NumberFormatException
异常是普遍发生的,如果所有出现异常的地方都要求进行强制性的异常处理,代码将会很复杂。所以在异常类设计的时候考虑到一些异常可能是一些简单的问题,所以将这些异常统一称为RuntimeException
。
- 也就是说使用
RuntimeException
定义的异常类可以不需要强制性进行异常处理。当然想使用try···catch处理也没人拦着。
Exception
与RuntimeException
的区别?
Exception
是RuntimeException
的父类,使用Exception
定义的异常要求必须使用异常处理;RuntimeException
可以用用户选择性的来进行异常处理。
列举几个常见的RuntimeException
ArithmeticException
算数运算异常
ClassCastException
类型间转换不兼容异常
NullPointerException
空指针异常
95:断言
断言(assert
)指的是当程序执行某些语句之后其数据的内容一定是约定的内容,否则停止执行。
范例:观察断言程序
public class TestDemo {
public static void main(String args[]) {
int num = 100 ;
// 中间可能会有很多步骤,预计num的内容应该变为300
assert num == 300 : "错误:num内容不是300" ;
System.out.println(num) ;
}
}
如果要让断言起作用,要在JVM解释时使用-ea选项:java -ea 文件名
断言主要作用为调试阶段debug,但是实际上基本用不到。
96:自定义异常类
在Java中,实际上对于可能出现的公共的程序问题都会提供有相应的异常信息,但是很多时候这些异常信息往往不足以满足需求。
- 例如:现在要求进行加法处理,如果发现两个内容相加为100,就抛出一个AddException异常。这种情况Java并没有提供相应的异常信息,因此需要我们自定义异常类/
想要自定义异常类,可以继承两种父类:Exception
和RuntimeException
。
范例:实现自定义异常类
class AddException extends Exception {
public AddException(String msg) {
super(msg) ;
}
}
public class TestDemo {
public static void main(String args[]) throws Exception {
if ((50 + 50) == 100) {
throw new AddException("错误的相加操作") ;
}
}
}