Java不同于其他语言的其中一个特性就是异常(Exception),也是我们项目中经常用到的一种机制。最近重读了一遍《Thinking in Java》中的异常章节,弥补了多年来我对异常很多细节部分的空缺,接下来我准备分四篇文章总结一下:
1.基本概念与使用
2.捕获所有异常
3.多态中的异常
4.开放话题,什么时候使用异常,什么时候处理异常
下面开始第一篇内容,虽然我个人不太喜欢,但是纯文字概念性的东西还是要先讲一下
异常的概念###
什么是异常:异常是指当前方法或作用域出现阻止继续往下执行的问题,而在此方法或者作用域中你没有足够的信息或者条件来处理这个问题,那就必须将此问题抛出,交给调用此方法的上一级环境来处理,如果上一级环境还是无法处理,就再向上抛出,以此类推。
异常的目的:将问题发生地点和问题处理地点区分开,大大降低了错误处理代码的复杂度
下面举个栗子,你将会看到几个Java的关键字:throws,throw,try-catch,另外还有一个finally
throws:用于异常说明,指出该方法内部会抛出哪些异常
throw:用于异常抛出,当方法内部发生问题,抛出相应的异常对象
try-catch:try-catch是两个关键字,就像咖啡和伴侣一样搭配使用的,try代码块中是正常代码的调用与处理,catch代码块中用于捕获try中代码抛出的异常
finally:在try-catch的结尾使用,无论try-catch代码块中是正常运行还是有异常抛出,finally代码块中的代码肯定会被执行,一般用于资源的关闭或释放等
发现问题,抛出异常,添加方法异常说明
// 方法参数后面通过throws关键字增加方法内部抛出异常的说明
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
// 下面两个if判断是否发生问题,并且抛出异常
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
捕获异常
try {
FileInputStream fis = new FileInputStream("");
} catch (FileNotFoundException e) {
e.printStackTrace();
// 这里是问题处理的地点
}
上述代码是以我们常用的FileInputSctream的源码为例,介绍了问题的发生,异常的抛出,异常的说明和异常的捕获。代码中我加了一些必要的注释,问题的发生和异常的抛出是在FileInputStream构造方法内部的,而异常的捕获是在new对象的地方,然后在catch代码块中捕获对应的异常并且添加对该异常的处理
自定义异常
在我们实际项目开发中,只用到JDK定义的异常是不太可能的,针对某些模块的业务逻辑,我们需要自定义一些异常来标识问题。自定义方式很简单,只需要定义一个Exception类继承自Exception即可,使用上和所有异常一样,只是异常类不同而已
public class MyException extends Exception {
private static final long serialVersionUID = 6138343062222408957L;
public MyException() {
super();
}
public MyException(String msg) {
super(msg);
}
}
当然,如果你就是不喜欢自定义异常类,也完全可以通过new一些现有的Exception类如IOException等,再传入不同的message来实现。仁者见仁智者见智,我个人推荐自定义异常,并且最好可以做到通过异常名称就能看出问题是什么。
finally
上文中已经讲过finally关键字的含义,单独拎出来讲就是为了一个我之前一直模棱两可的问题,到底是先执行return还是先执行finally,例如:
public int getIndex() {
int i = 1;
try {
i++;
doSomething();
return i; //内部return
} catch (MyException e) {
e.printStackTrace();
} finally {
i++;
System.out.println("this is finally: i = " + i);
}
return i; //外部return
}
getIndex()方法返回的值到底是2还是3?这个就要分情况来看了,若doSomething方法内部抛出异常,那么执行完finally,会继续执行外部return语句,毫无疑问返回的就是3;若doSomething方法内部不抛出异常,到底是先执行内部return语句还是先执行finally呢?我们来看看实际运行结果的日志:
实际运行结果为2,对于这种结果,我的猜测和理解是这样的:首先return语句先执行,finally后执行,return语句执行完之后,i由于是基本数据类型,值会被保存下来,然后执行finally语句,结束之后,方法会将之前保存的i的值返回给方法调用方。为什么我会这么猜测,因为我觉得这个和方法参数值传递还是引用传递的原理类似,一开始我也不太确定,所以有了下面这一个demo:
// Bean中只有一个int类型字段index
public Bean getBean() {
Bean bean = new Bean();
bean.index = 1;
try {
bean.index++;
doSomething();
System.out.println("before return: index = " + bean.index);
return bean;
} catch (MyException e) {
e.printStackTrace();
} finally {
bean.index++;
System.out.println("this is finally: index = " + bean.index);
}
return bean;
}
这个方法的运行结果就是3了,由此可以证明我的猜测应该是正确的,还是上文说的,return语句执行完,先保存bean对象的引用,注意,这里bean是对象不是基本数据类型了,所以保存的是引用,然后执行finally语句,修改了对象中的int字段值,最后返回的还是该对象对象的引用,所以最后的结果是3。
在catch语句块中我们经常要打印捕获到的异常信息,有时候我们不做任何处理,还会抛出一个新的异常,关于这部分的内容我会在下一篇文章中详细介绍,敬请期待!