第18章-异常处理
提问:我们已经见过的异常有哪些?产生异常后,程序还能继续执行吗?
答:
- 已经见过的异常有:ArrayIndexOutOfBoundsException、StackOverflowError、NullPointerException、ClassCastException等
- 产生异常后,程序将崩溃,不再执行后续代码,也不能继续提供服务;所以,如果不对异常进行处理,对程序来说,是致命的
概述
程序中,因为各种原因,通常会产生各种异常,有基础类抛出的,也有自己代码中产生的
异常会导致程序奔溃,后果极其严重,不可接受
在程序的开发阶段,必须尽量的对可预见的异常进行捕获、处理,保证程序的运行
好的异常处理机制,可以让异常处理代码和正常业务逻辑分离,保证程序健壮性、容错性、优雅性
通常处理方式有:
- 可能产生异常的代码中开发者自己处理
- 抛出异常,给代码所在方法的调用者处理
应用实例
应用实例1,产生几种常见类型异常
package com.bjpowernode.demo;
/**
* 异常示例
*/
public class ExceptionDemo {
public static void main(String[] args) {
//产生异常
//空指针异常
String str=null;
System.out.println(str.length());
//数组越界异常
int[] datas = new int[0];
datas[0]=2;
//被0除异常
int result = 5/0;
//异常后的逻辑,由于前面异常将导致程序崩溃,此处代码将不会被执行
System.out.println("异常后的代码块。");
}
}
【练习】
- 练习演示的内容,熟悉异常类对程序的影响
异常处理机制
关键字
异常处理,主要由如下几个关键字或关键字组成的代码块完成
- 自己处理,主要由下面3个关键字配合进行
- try:可能产生异常的代码块;必选
- catch:异常处理代码块,可以有1~n个catch,会根据try代码块中抛出的异常类型,自动匹配不同catch的异常处理代码块;必选
- finally:后置处理代码块,用于资源回收,不管产生异常与否,都被执行;一般用来做收尾工作,像资源回收,如数据库的连接释放、文件io资源释放..;可选
- 调用者处理,主要由下面2个关键字配合进行
- throw:主动抛出异常类的对象,表示自己不处理,交给方法的调用者处理
- throws:标识方法抛出的编译期异常,让调用方知道,调用此方法需要处理哪些异常
自己处理
对于一段代码,如果预计会产生异常,一般需要使用try代码块进行异常捕获,并进行处理
并接着定义catch捕获到的不同类型异常的处理代码
如果有资源需要进行回收,接着在finally中定义后置处理代码块
语法规则:
方式1、不带finally
try{
...//可能产生异常的代码块
}catch(异常类型1 异常的变量名1){
...//异常类型1处理代码块
}catch(异常类型2 异常的变量名2){
...//异常类型2处理代码块
}方式2、带finally
try{
...//可能产生异常的代码块
}catch(异常类型1 异常的变量名1){
...//异常类型1处理代码块
}catch(异常类型2 异常的变量名2){
...//异常类型2处理代码块
}finally{
...//后置处理代码块
}
注意,多个catch捕获异常时:
- 只要有一个与产生的异常类型匹配,就不会去匹配下一个catch,类似if...else if...逻辑
- 由于异常类有一定的继承层次,需要把子类类型的异常放在最前面(如果不是这样,一般会有编译错误)
应用实例
应用实例1,进行异常处理后的程序,程序不会崩溃
package com.bjpowernode.demo;
/**
* 异常示例
*/
public class ExceptionDemo {
public static void main(String[] args) {
//调用方法
ExceptionDemo.exception();
}
/**
* 异常处理,方式1:自己处理
*/
public static void exception(){
try{
//产生异常
//空指针异常
String str=null;
System.out.println(str.length());
//数组越界异常
int[] datas = new int[0];
datas[0]=2;
//被0除异常
int result = 5/0;
//可能产生异常代码块中,异常产生后的代码块,将不会被执行
System.out.println("产生异常后,try中的其他代码块。");
}catch (NullPointerException ex){
//主动输出异常栈,里面的内容有指向性意义,比如行号
ex.printStackTrace();
//空指针异常
System.out.println("空指针异常,异常信息:"+ex.getMessage()); //自定义输出
}catch (ArrayIndexOutOfBoundsException ex){
//数组越界异常
System.out.println("数组越界异常,异常信息:"+ex.getMessage()); //自定义输出
}catch (Exception ex){
//其他异常,因为Exception是所有异常类的父类,将进行兜底
System.out.println("其他异常,异常信息:"+ex.getMessage()); //自定义输出
}finally {
System.out.println("finally,每次都会输出。");
}
//异常后的逻辑,由于前面异常将导致程序崩溃,此处代码块会被执行
System.out.println("异常处理后的代码块。");
}
}
【练习】
- 练习演示的内容,完成代码编写
调用者处理
同样,在一个方法中,也可以不处理可能会产生的异常,交由方法的调用者进行处理
并且,在需要的时候,可以主动使用throw抛出一个异常类对象,交由方法的调用者进行处理
那么,这个方法的声明后(方法体前),需要使用throws声明此方法需要调用者处理的异常
语法规则:
throw抛出异常
throw 异常类对象;
throws声明方法抛出的异常,如果是编译期异常在方法中使用throw抛出异常,且并未使用try...catch进行异常捕获,就必须在方法声明后添加该异常的声明
访问修饰符 [其他修饰符] 返回值类型 方法名([参数类型 参数名,...]) throws 异常类型{
//方法体
return 返回值;
}
应用实例
应用实例1,抛出异常,并在方法上声明编译期异常
package com.bjpowernode.demo;
import java.io.IOException;
/**
* 异常示例
*/
public class ExceptionDemo {
public static void main(String[] args) {
//调用方法,需要处理调用方法中抛出的异常
try {
ExceptionDemo.exception();
} catch (IOException ex) {
//捕获并处理异常
System.out.println("IO异常,异常信息:" + ex.getMessage());
}
}
/**
* 异常处理,方式2:调用者处理,方法签名中需要加上throws声明
*/
public static void exception() throws IOException {
//在方法中主动抛出一个IO异常
throw new IOException("我是IO异常");
}
}
【练习】
- 练习演示的内容,完成代码编写
异常分类
在Java的异常处理体系中,将Exception分为了两类
运行时异常(非检查性异常),继承于RuntimeException
- 编译时发现不了的问题,但是在代码运行的过程中可能会出现的问题。当出现运行时异常的时候,程序直接就中断了,但是也可以通过异常处理机制,捕捉到异常,恢复程序的继续执行。
- 不需要使用try...catch强制捕获处理
- 常见的有:
- NullPointerException:空指针异常
- IndexOutOfBoundsException:索引越界异常
- ClassCastException:类转换异常
- ArithmeticException:算术异常
- ...
编译时异常(检查性异常),继承于Exception
编译器在编译你的代码的时候发现你的代码可能会出现的问题,就会提示你要去手动处理这个异常,否则代码编译不通过,所以叫编译时异常。
需要使用try...catch强制捕获处理,或者用throws声明由方法调用者捕获处理
常见的有:
InterruptedException:一个线程被另一个线程中断,抛出该异常。
IOException:IO异常
SQLException:SQL异常
ClassNotFoundException:找不到类异常
...
异常结构体系
在Java中,异常分为Error和Exception两个系列,类型名称分别为xxxError和xxxException
Error系列
- 最严重错误
- 程序无法处理,无法通过try...catch进行捕获处理,但可能由代码不合理导致
- 由JVM负责处理,如内存不足、堆栈溢出、程序奔溃等
Exception系列
- 分运行时异常和编译时异常
- 运行时异常,继承于RuntimeException,可使用try...catch进行捕获处理
- 编译时异常,直接继承于Exception,必须使用try...catch捕获,或者在方法签名后使用throws关键字声明需要调用方进行处理
- 需要在代码中进行捕获,并处理,来保证程序的健壮性
- 分运行时异常和编译时异常
其结构如下图
自定义异常类【扩展】
在一些工具开发场景,或一些项目的开发中,会针对工具或项目中的意外情况给使用方提供合理的提示信息,多用自定义异常实现
自定义异常一般继承Exception类,并可添加一些自己的属性和方法
应用实例
应用实例1,定义一个自定义异常,并进行抛出、捕获、处理
package com.bjpowernode.demo;
/**
* 自定义异常
*/
public class MyException extends Exception {
public MyException() {
super("自定义异常");
}
public MyException(String message) {
super(message);
}
public static void main(String[] args) {
//捕获并处理自定义异常
try {
MyException.exception();
} catch (MyException ex) {
//捕获并处理异常
System.out.println("自定义异常,异常信息:" + ex.getMessage());
}finally {
System.out.println("finally,每次都会输出。");
}
System.out.println("异常处理后的代码块。");
}
/**
* 抛出自定义异常
*/
public static void exception() throws MyException {
//在方法中主动抛出一个自定义异常
throw new MyException();
}
}
【练习】
- 练习演示的内容,完成代码编写
实战和作业
- 重构程序,针对”第11章-多态“中的people项目中各个国家的人的姓名和年龄进行处理,具体要求如下:
- 添加年龄属性,并生成getter/setter
- 自定义异常PeopleException,作为整个项目的业务异常类
- 在各个国家人姓名设置时,进行验证,是否包含数字(可将字符串转成char[]数组,然后使用包装类Character的isDigit静态方法验证每个字符是否为数字)
- 如果包含,抛出PeopleException异常,异常信息为"姓名不能包含数字"
- 如果不包含,正常设置值
- 在各个国家人年龄设置时,进行验证,年龄不能小于0
- 如果小于0,抛出PeopleException异常,异常信息为"年龄不能小于0"
- 如果大于等于0,正常设置值
- 定义测试类,测试正常设置和抛出异常的情况