在理想情况下,我们写的程序不会有任何的异常,用户输入的数据也永远正确,选择打开的文件永远存在,我们的程序也没有任何Bug,但是现在情况下,这种情况不现实,我们的项目和代码经常会出现各种各样的异常,我们除了在写代码的时候注意规范之外,还需要理解Java的异常机制;
用户在遇到异常的时候总是会感觉不爽,久而久之就不想使用我们的软件了,所以遇到异常我们要做到以下几点;
1.向用户返回,他可以接受的错误提示;
2.保存数据的完整性;
3.允许用户以妥善方式退出程序;
处理错误:
即遇到错误之后我们处理的方法,比如遇到下标错误,空指针异常,物理错误(磁盘满),代码错误等,当出现这些错误的时候,我们应该怎么去解决啦?
1.规范代码,规范代码,使我们的代码减少错误,比如判断非空,判断数组下标是否越界等;
2.返回到一种安全状态,并能够让用户继续操作;
3.允许用户保存完整的操作结果,并安全退出;
要做到以上几点,很难,因为Java的异常机制,只是将异常处理的任务从产生错误的地方转到我们的能够处理这种错误的地方。从而知道我们发生了哪些错误和问题,好让我们再程序中进行改进,减少错误发生的概率;
异常分类:
来分析一下上图,我们可以看出所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception;
Error类层次结构描述了java运行时系统的内部错误和资源耗尽的错误,我们的程序不应该抛出这样的错误。因为出现了这种错误,我们除了告知用户,并安全退出之外再也无能为力,当然这种错误也很少出现;
在设计Java程序时,我们需要关注的是Exception层次结构,而这个层次又有两个分支;一个分支派生于RuntimeException,另一个分支派生于IOException;由程序错误导致的异常属于RuntimeException,而由于I/O错误这类问题导致的异常则属于IOException;
RuntimeException包含的常见异常:
错误的类型转换;
数组访问越界;
访问控指针;
不是派生于RuntimeException包括的常见异常:
试图在文件尾部后面读取数据;
试图打开一个不存在的文件;
试图根据给定的字符串查找Class对象,而这个字符串表示的对象不存在;
请记住一条准则那就是如果发生了RuntimeException异常,那么一定是程序员自己的问题了;
Java将所有派生于IOException的异常称为已检查(checked)异常,而RuntimeExcepton和Error异常称为非检查时异常;
声明已检查异常:
如果遇到了无法处理的情况,那么java方法允许抛出一个异常,我们不仅要告诉编译器返回什么值,执行什么动作,也需要告诉编译器可能会发生的错误;
方法在其首部声明可能会抛出的异常通过throws来进行抛出,当我们抛出的异常所发生的时候,讲不会执行异常后面的代码,而把我们的异常交给异常对象取执行,运行时系统就会开始搜索异常处理器,以便知道如何处理异常对象;那么什么时候该使用throws抛出啦,我们记住以下几点;
1)调用一个抛出已检查异常的方法。例如I/O异常;
2)程序出现错误,例如数组下标越界异常;
3)java虚拟机和运行时库出现的内部错误;
抛出:
除了声明异常之外,我们还可以捕获异常。这样会使异常不被抛出到方法之外。也不需要throw规范。另外如果子类覆盖了父类的某个方法,那么父类的异常的通用性要大于子类,或者子类不需要抛出异常;把异常全部交给父类处理;
对于一个已经存在的异常类,将其抛出非常容易,在这种情况下:
1)找到一个合适的异常类。
2)创建这个类的一个对象。
3)将对象抛出。
自定义异常类:
在程序中我们可能会遇到标准异常类无法描述的问题。在这种情况下我们就需要创建自己的异常类了。我们需要做的只是定义一个派生于Exception的类,或者派生于
Exception子类的类。我们定义的类,应当有两个构造器,一个是默认的,另外一个是带有详细描述信息的构造器。它会打印详细信息,这在调式中非常有用;
public class IUnusual extends ArithmeticException{
public IUnusual() {
super();
}
public IUnusual(String s) {
super(s);
}
public static void division(int x,int y) throws IUnusual{
int fruit;
if(y==0){
throw new IUnusual("除数不能为0");
}
fruit=x/y;
}
public static void main(String[] args) {
try {
division(1,0);
} catch (IUnusual i) {
System.out.println(i.getMessage());
}
}
}
上面一段简单的自定义异常类,自我感觉用处不大,好像没什么意义。如果用java做web程序的话,像这些输入验证都是用js验证的;
捕获:
有些代码除了抛出异常之外,我们还需要捕获以下。捕获异常其实很简单try/catch/finally
try{
code
more code
more code
}catch(ExceptionType e){
异常处理
}finally{
总是执行这段代码;
}
如果try语句中出现异常了,那么程序将跳过try语句后面的代码。程序将去匹配catch里面的异常处理器,如果匹配到了就执行catch里面的代码,如果没有匹配到就去执行finally里面的代码,不管有没有出现异常finally里面的代码总是执行;一般我们用它来关闭数据库连接或者I/O的连接;
try {
division(1,1);
} catch (IUnusual | NullPointerException i) {
System.out.println(i.getMessage());
}finally{
}
JDK1.7以后可以合并catch字句;捕获多个异常可以让我们的代码看起来更简单还会更高效;带资源的try 语句,如果我们的的try语句发生了异常,而执行finally关闭连接是时候也发生了异常,那么新异常会覆盖旧异常,而旧异常有可能是非常重要的,那么这个时候我们就可以用带try资源的块,他会自动的去关闭连接;
try(FileInputStream file=new FileInputStream("d:"))
使用异常的技巧:
1.异常处理不能代替简单的测试;
2.不要过分的细化异常;
3.利用异常层次结构;
4.不要压制异常;
5.在检测异常错误时,捕获要比抛出更好;
6.不要羞于传递异常;
5和6可以归纳为早抛出,晚捕获;