一、Java异常处理
1.异常概念
(1)什么是异常?
所谓的异常是指在程序运行的过程中发生的一些不正常事件。(如:除0溢出,数组下标越界,所要读取的文件不存在。)
(2)异常导致的后果?
java程序在执行过程中如出现异常事件,可以生成一个异常类对象,该对象封装了异常事件的信息,并将其提交给java运行时系统,这个过程称为抛出异常,不处理的话会直接导致程序中断(即终止程序的进行)。
(3)如何防止程序中断?
设计良好的程序应该在程序异常发生时提供处理这些异常的方法,使得程序不会因为异常的发生而阻断或产生不可预见的结果。
2.异常分类
(1)Error这种异常一般都是由java虚拟机生成并抛出的,包括动态链接的失败,虚拟机错误等等。程序对其一般不做任何处理,因为不是程序本身的问题,是系统(比如说虚拟机)的问题。就算你捕获了也没办法对其处理,所以一般不推荐对Error异常做任何的捕获或处理。
(2)Exception这种异常,一般需要用户或程序员显示声明获取捕获。
异常类型 | 包括 | 来源 | 处理 |
受查异常(CheckedException) | Exception及其子类(不包括RuntimeException及其子类) | 由代码控制能力之外的因素导致的运行时错误 | 必须要处理,否则编译都通不过 |
非受查异常(UnCheckedException) | Error和RuntimeException及其子类 | RuntimeException一般代表编程错误 | 可以不用处理 |
3.java异常处理机制
(1)java的异常是通过两种机制来处理的:
①捕获异常:try---catch---finally
try:监控区域,执行可能产生异常的代码
catch:捕获,异常处理
finally:善后处理,无论是否发生异常,代码总能执行(一般用来释放一些资源)
②抛出异常:throw、throws
4.try...catch...finally
(1)try{}语句块中放的是要检测的java代码,可能会抛出异常,也可能会正常执行。
(2)catch(异常类型){}块是当java代码运行时系统接收到try块中所抛出异常对象时,会寻找能处理这一异常catch块来进行异常处理(可以有多个catch块)。
(3)finally{}不管系统有没有抛出异常都会执行,一般用来释放资源。除了在之前执行了System.exit(0)【直接结束应用程序】
如图所示:
实例:
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("请输入一个数字");
Scanner input = new Scanner(System.in);
int res = 0;
try {
int num = input.nextInt();
res = 10 / num;
} catch (InputMismatchException e) { // 输入不匹配异常
// e.getMessage()返回此Throwable的详细消息字符串
System.out.println(e.getMessage());
// 将此Throwable对象的堆栈跟踪输出至错误输出流,作为字段System.err的值
e.printStackTrace();
} catch (ArithmeticException e) { // 算术异常
System.out.println(e.getMessage());
e.printStackTrace();
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
} finally {
// 释放资源,比如关闭打开的文件,删除一些临时文件等。
System.out.println("结果为:" + res);
input.close();
}
}
}
①如果在try{}块中加入return(用来结束方法),finally块中的语句还会执行,即就算加了return,也会先执行完finally块再跳出方法。
②try块可以和catch块单独使用,也可以和finally块单独使用,也可以三者一起使用。和finally块一起使用时,它只关注自己所要做的事情,至于抛出异常,它自己不去捕获而是提交给调用相关方法的人去捕获。
③NullPointerException空指针异常:没有产生对象但却调用了对象里的相关方法或属性。
④在多catch块并存的情况下,安排catch语句的顺序时,首先应该捕获最特殊的异常,然后再逐渐一般化,即先子类后父类。
5.throw和throws
(1)throw用于手动抛出异常。作为程序员可以通过throw这个关键字在任意位置手动抛出异常。
(2)throws用于在方法上标识要暴露的异常。抛出的异常交由调用者处理(即throws修饰的方法,它本身内部不处理异常,而是交给调用此方法的那个人来处理这个方法所标识的异常类)。
(3)两者区别:
① throw用在方法内抛出异常,后面跟上要抛出的异常类对象,一般是Exception这个类的子类异常。
② throws主要用来修饰方法,告诉调用者此方法可能会抛出异常,后面跟上可能要抛出的异常类名。
public class ExceptionDemo2 {
public static void main(String[] args) {
Bar bar = new Bar();
bar.enter(15);
System.out.println("END");
}
}
class Bar {
public void enter(int age) {
if (age < 18) {
throw new IllegalArgumentException("年龄不合格!");
} else {
System.out.println("欢迎光临!");
}
}
}
注:这里程序没有打印输出end,因为在手动抛出异常的时候整个过程就中断了,那么我们不想让程序中断就应该捕获这个异常。这里没有捕获这个异常也没有报错,是因为这里抛出的是非受查异常,非受查异常可以不用捕获(这里说的不用捕获,指的是在抛出该非受查异常的地方可以不捕获这个异常,而不是调用此方法的地方),直接交给系统让系统自动检测,并将它们交给缺省的异常处理程序来处理。为了不让程序中断我们可以这样做:
public class ExceptionDemo2 {
public static void main(String[] args) {
Bar bar = new Bar();
try {
bar.enter(15);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
System.out.println("END");
}
}
① 如果我们在方法中不想通过手动抛出一个异常,还可以通过在方法上通过throws关键字标明这个方法可能会抛出的异常,不管这个异常是受查异常还是非受查异常。即:
public void enter(int age) throws IllegalArgumentException{...}
② 如果在方法中抛出Exception异常,即受查异常:throw new Exception("年龄不合格!");受查异常必须捕获(这里说的捕获指的是抛出该受查异常的地方),要么自己抛自己捕获,但这样做没有什么意义,一般往外抛,即在方法名后加throws Exception的方式告诉调用者,这个方法可能会抛出Exception这个异常,在调用的时候可以捕获这个异常或者继续往外抛。
public class ExceptionDemo2 {
public static void main(String[] args) {
Bar bar = new Bar();
try {
bar.enter(15);
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("END");
}
}
class Bar {
public void enter(int age) throws Exception {
if (age < 18) {
throw new Exception("年龄不合格!");
} else {
System.out.println("欢迎光临!");
}
}
}
总结: 当你手动抛出一个非受查异常的时候,可以不捕获,方法后面可以不加throws这个关键字。当你抛出的是一个受查异常,那么这个方法必须加throws关键字往外抛这个异常,而且调用这个方法的那条指令必须也要拿try{}来捕获或者不捕获也可以再往外抛,让调用主方法的人去捕获,否则就必须去捕获。往往throw和throws配合使用,以后在方法里面只要有throw,都要在方法后面加上throws,不管抛出的是受查异常还是非受查异常,这是一种写代码的规范。
6.自定义异常
(1)常见异常
①受查异常:IOException、SQLException、ClassNotFoundException(不能加载请求的类)、NoSuchMethodException(请求的方法不存在)、InterruptedException(线程中断)FileNotFoundException(不能找到文件)、IllegalAccessException(对类的访问被拒绝)、EOFException(文件结束),需要捕获的异常,否则编译都通不过。
②非受查异常:RuntimeException(运行时异常,包括:类转换异常,数组下标越界,算术异常,非法参数异常,空指针异常,输入不匹配异常)
(2)自定义异常类必须从已有的异常类继承(一般继承Exception),建立新的异常类型最简单的方法就是让编译器产生默认构造方法,对异常来说,最重要的部分就是它的类名,可以为异常类定义一个接受字符串参数的构造方法,这个字符串参数用来描述异常信息。
public class ExceptionDemo3 {
public static void main(String[] args) {
Bar bar = new Bar();
try {
bar.enter(15);
} catch (AgeLessThanEighteenException e) {
System.out.println(e.getMessage());
}
System.out.println("END");
}
}
class AgeLessThanEighteenException extends Exception {
private static final long serialVersionUID = 56153852936795919L;
// 描述异常信息
private String message = "";
public AgeLessThanEighteenException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
class Bar {
public void enter(int age) throws AgeLessThanEighteenException {
if (age < 18) {
throw new AgeLessThanEighteenException("年龄不合格!");
} else {
System.out.println("欢迎光临!");
}
}
}
案例讲解(医生给工人看病,工人在工作的过程中会生病,此时会抛出一个自定义异常。医生通过判断工人的状态,来给工人提供医治。)
public class ExceptionExample {
public static void main(String[] args) {
Worker worker = new Worker();
Doctor doctor = new Doctor();
try {
worker.work();
} catch (SickException e) {
System.out.println(e.getMessage());
doctor.cure(worker);
if ("医治有效".equals(worker.getStatus())) {
System.out.println("患者康复");
} else {
System.out.println(worker.getStatus());
System.out.println("我们尽力了");
}
}
System.out.println("欢迎下次来本院就诊");
}
}
class SickException extends Exception {
private static final long serialVersionUID = 9019785376733085852L;
private String message = "";
public SickException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
class Worker {
// 工人的健康状态
private String status = "";
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public void work() throws SickException{
Random random = new Random();
int rad = random.nextInt(3) + 1;
// 用随机数来模拟工人生病
if (rad == 1) {
throw new SickException("生病了");
} else {
System.out.println("我很健康");
}
}
}
class Doctor{
public void cure(Worker worker) {
Random random = new Random();
int rad = random.nextInt(2) + 1;
// 使用随机数模拟医生看病结果
if (rad == 1) {
worker.setStatus("医治有效");
} else {
worker.setStatus("医治无效");
}
}
}