接上篇《JavaSE 基础学习之三 —— Java 的继承与接口》
四. Java 核心专题 —— 异常处理
1. 两种类型的异常
- 运行时异常 (RuntimeException):不处理也能通过编译,jvm 会帮助处理,也可以自行处理;
- 其他异常:对于其他异常,如果不处理程序就不能通过编译,必须自己处理;
注:
所有的异常产生之后,都是一个类的实例对象,而且这些异常全部继承于 Exception;java 中对所有的异常都进行了细致分类,每种异常都有一个具体的类。
2. 异常处理的关键字
(1) try... catch...
- try 后面可以有多个 catch;
- 所有的异常都是 Exception 的子类;
- 多个 catch 的时候,父类的异常一定要写在子类的后面;
- Exception 可以认为是万能异常;
- 常用 Exception 的方法:
- getMessage(): 返回此 throwable 的详细消息字符串;
- toString():返回此 throwable 的简短描述。
- printStackTrace():打印异常的堆栈,将此 throwable 及其追踪输出至标准错误流;
(2) finally
finally 关键字后无论有没有异常,最终都要执行的操作;
例:
public class Demo1 {
public static void main(String[] args) {
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println(a/b);
} catch (ArrayIndexOutOfBoundsException e) {
// TODO: handle exception
System.out.println("数组下标越界");
} catch (ArithmeticException e) {
System.out.println("除数不能为 0");
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println(e);
System.out.println("其他异常");
} finally {
System.out.println("无论有没有异常,最终都要执行的操作");
}
}
}
(3) throw
throw 关键字表示在某种情况下,我们自己手动抛出的异常实例;
例:定义一个 Person 类,对其年龄进行设置,如果年龄超过 120,则认为出现错误,手动抛出异常。
public class Person {
private int age = 10;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception{
try {
if(age < 0 || age > 120) {
throw new Exception("年龄有误");
} else {
this.age = age;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
如果运行了程序 setAge(130),则会抛出“年龄有误”的异常。
(4) throws
throws 关键字在一个方法的方法签名后面使用,用处在于通过编译,告诉使用者一旦调用了当前方法,可能出现处理哪些异常,这样可以令使用者尽量的回避异常;当你的方法里抛出了 checked 异常,如你不 catch,代表你当时不处理(不想处理或没条件处理),但你必须得通过"throws那个异常"告诉系统说,这儿有个问题,我现在不处理,将来一定别人要处理,否则执行到它,系统会"不优雅"的崩溃。
举个例子,工兵张三发现了地雷,假如他处理完就完事儿了。但是他发现了地雷,自己却没带齐工具,没法处理,他必须做个标记,说这儿有一个地雷,别的工兵将来一定要处理,否则将来有人踩上去会爆炸。
注意:throws只是标记,并没处理,程序执行到那里,系统还是会崩溃!
例:
public class Person1 {
private int age;
// throws 异常,通知此处代码有问题
public void setAge(int age) throws Exception{
if(age < 0 || age > 120)
throw new Exception("年龄有误!");
this.age = age;
}
public int getAge() {
return age;
}
}
3. 自定义异常
项目中,我们可以通过创建一个类继承 jdk 提供的异常类 (RunTimeException 或 Exception),自定义的实现一个异常类。
自定义异常的创建有利有弊:优点如下:
- 工作过程中,项目是分模块或者分功能开发的,基本不会你一个人开发一整个项目,使用自定义异常类就统一了对外异常展示的方式;
- 有时候我们遇到某些校验或者问题时,需要直接结束掉当前的请求,这时便可以通过抛出自定义异常来结束。例如,如果你项目中使用了SpringMVC比较新的版本的话有控制器增强,可以通过@ControllerAdvice注解写一个控制器增强类来拦截自定义的异常并响应给前端相应的信息;
- 系统中有些错误是符合Java语法的,但不符合我们项目的业务逻辑,使用自定义异常可以在我们项目中某些特殊的业务逻辑时抛出异常。例如对于性别,"中性".equals(sex),性别等于中性时我们要抛出异常,而Java是不会有这种异常的;
- 使用自定义异常继承相关的异常来抛出处理后的异常信息可以隐藏底层的异常,这样更安全,异常信息也更加直观。自定义异常可以抛出我们自己想要抛出的信息,可以通过抛出的信息区分异常发生的位置,根据异常名我们就可以知道哪里有异常,根据异常提示信息进行程序修改。例如,空指针异常NullPointException,我们可以抛出信息为“xxx为空”定位异常位置,而不用输出堆栈信息。
自定义异常的缺点主要在于,发现异常、抛出异常以及处理异常的工作必须靠编程人员在代码中利用异常处理机制自己完成。这样就相应的增加了一些开发成本和工作量,所以项目没必要的话,也不一定非得要用上自定义异常,要能够自己去权衡。
关于自定义异常,可以参考《Java异常之自定义异常》的博客。
4. 关于异常的编程建议
建议:
- 自己抛出的异常必须要填写详细的描述信息,便于问题定位;
- 例如:throw new IOException("Writing data error! Data: " + data.toString());
- 在程序中,选择使用异常处理还是错误返回码处理,应该根据是否有利于程序结构来确定,且不能将异常和错误码混合使用。通常情况下推荐异常处理;
- 记录异常时,不要只保存 exception.getMessage(),一般可以通过日志工具记录完整的异常堆栈信息;
- 一个方法不应该抛出太多类型的异常。如果确实有很多异常类型,首先应该考虑用异常来进行区别。通常来说 throws / exception 子句标明的异常最多不要超过三个;
- 异常捕获尽量不要直接捕获 catch(Exception ex),应该把异常细分处理,设计更加合理的异常处理分支;
- public 类型的底层方法需要对输入参数进行判断,如果参数不合法,应该主动抛出 RuntimeException;
强烈建议:
- 在调用的最高层,必须处理所有的异常;
- 如果捕获了异常,然后抛出新的异常,则必须将原异常的信息全部包含在新的异常中;
- 系统非正常运行产生的异常捕获之后,如果不对该异常进行处理,必须记录日志;
- 对于多个异常的额情况,必须对应多个 catch 分别处理,禁止使用一个 catch 处理多个异常。如下例所示:
public void sample() {
try {
// 某个抛出异常块
}
// 处理异常种类 1
catch (IllegalStateException illEx) {
log.wirte(illEx.printStackTrace());
throw illex;
}
// 处理异常种类 2
catch (SQLException SQLEx) {
log.write(SQLEx.printStackTrace());
throw SQLEx;
}
finally {
// 释放资源
}
}
接下篇《JavaSE 基础学习之五 —— IO 操作》