C\C++ 程序员从零开始学习Android - 个人学习笔记(九) - java基础 - 异常
2012-02-12 17:51 CreateLight 阅读(350) 评论(0) 编辑 收藏 举报1,概述
异常是错误处理的一种手段,相比传统的返回错误值的做法,java更推荐异常处理,因为这可以分离正常代码和错误处理代码,让代码看起来更清晰。错误一般可分为两类:
a, 逻辑错误,这种错误都是由于程序员错误的思考造成的。
b, 外界错误,比如用户进行错误的输入、内存用尽、硬盘空间不足、网络无链接、服务器无法访问等。
当遇到一个错误并决定抛出异常时,首先会构建一个异常对象(在堆上,如同构建其他任何对象),然后停止当前方法的执行路径,立刻退出当前方法,并且不会返回任何值,接着异常处理机制会开始搜索这个异常的处理器,继续执行程序。
2,分类:
2.1,所有异常类都继承于Throwable类或其子孙类,Throwable类是所有异常类的共同祖先。
2.2,Throwable 有两个子类:
2.2.1 Error
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误,应用程序不应该抛出这种类型的对象。当发生了这种异常时,除了告之用户,尽可能干净地退出外,没有太好的处理办法。这种异常很少见。
2.2.2 Exception
a RuntimeException
由于程序逻辑错误导致的异常,如果出现了RuntimeException,就一定是程序员的问题。RuntimeError的例子比如:访问空引用、数组越界访问等。程序员能够小心地编写代码,避免所有的RuntimerError。
b 非RuntimException
像IO错误这种依赖于环境的不可预测的错误,程序员无法通过编写代码避免所有的异常。比如URL格式错误,由于不同的浏览器可能支持不同的(特定)URL类型,这个异常的产生取决于环境。
2.3,unchecked异常和checked异常
所有的Error和RuntimeException称之为unchecked异常,即"未检查异常"或者更确切的“不需检查异常”,这类异常不需要也不应该有异常处理器,当出现这种异常时,应该修改代码以排除异常,而不是在异常处理器中进行处理。
其他的异常称之为checked异常,即“已检查异常”或更确切的“必须检查异常”,这类异常必须提供对应的异常处理器,编译器将对此做强制保证。
3,使用
异常应该用来处理非逻辑错误,是必须保留在release版本中一直起作用的代码。
逻辑错误应该使用断言(实现契约式编程)进行诊断,通过修改代码来修正,理想状况下release版本中不需要有断言。
自定义异常:应该总是派生自checked异常。当一个方法发现了一个错误时,它可以选择自己处理它,这是最佳选择,一个异常应该尽可能的在最近的局部进行处理;如果它不知道应该如何处理,可以向上一层抛出一个异常,由上层进行处理。这里其实存在争论,Bob大叔(R.Matin)的建议是不要使用checked异常,因为这会导致从产生异常的地方一直到最后处理异常的方法,这一路上的方法调用链都必须声明异常,这某种意义上暴露了实现细节、扩散了依赖,是一种“丑陋”的形式,他建议总是使用unchecked异常。
unchecked异常:总是会被抛出,由java编译器和虚拟机保证。
3.1 自己处理异常
使用try-catch-finally语句进行异常的抛出、捕获。
public void do()
{
try{
// code - 1
// may throw new MyException();
// code - 2
}catch(MyException e){
// code - 3
// may throw new MyException()
// code - 4
}finally{
// code - 5
}
// code - 6
}
各种情况下的执行顺序:
1, 不抛出任何异常:
code -1 、code - 2、code - 5、 code - 6、返回调用者。
将会执行try块中所有语句,然后跳过catch块,执行finally语句,然后顺序执行方法体直至返回。
2,try块中抛出异常,被catch块捕获:
code - 1、code - 3、code - 4、code - 5、code - 6、返回调用者
将会执行tyr块中的语句,直至遇到一个异常被抛出;然后跳过try块中的剩余语句,执行catch块中的语句;然后执行finally语句,然后退出方法返回至调用者。
3,try块中抛出异常,被catch块捕获,catch块中也抛出异常:
code - 1、code - 3、code - 5、返回调用者
将会执行tyr块中的语句,直至遇到一个异常被抛出;然后跳过try块中的剩余语句,执行catch块中的语句,直到遇到异常被抛出,然后跳过剩下的catch块中的语句,执行finally语句;然后退出方法返回至调用者。
4,try块中抛出异常,不被catch块捕获
code - 1、code - 5、返回调用者
将会执行tyr块中的语句,直至遇到一个异常被抛出;然后执行finally语句;然后退出方法返回调用者。
4,没有catch语句块,无论try块抛不抛出异常,都会执行finally语句块
5,try块之前的代码抛出异常
从异常处退出方法,不执行接下来的任何语句。
6, 含有return语句:
try
{
//code - 1;
return 1;
}
finally
{
//code - 2
return 2;
}
将会执行code - 1、set return value = 1、code - 2、return 2
执行try块中的语句,遇到return语句时设置返回值为1,并不立刻返回,而是开始执行finally语句,finally语句中的return 2将返回值复写为2,所以最终执行的是return 2.
7, finally语句中抛出异常
try
{
throw new MyException();
}
finally
{
throws new MyException2();
}
原始的异常(MyException)将会丢失,转而抛出finally中的异常(MyException2),这并非异常处理机制希望看到的结果,因此在finally语句中最好不要抛出异常。
3.2 抛出异常给上层处理
a, 异常规范
方法必须声明它将抛出某种类型的checked异常,以通知编译器及使用我们方法的程序员(称之为异常规范)。编译器会寻找对应的异常处理器,如果没有找到,则会终止相应(抛出异常的)线程。 和C++在运行时处理throw语句不同,java的throw语句是在编译时进行处理的,因此如果方法试图抛出未声明的checked异常,就会报编译错;而C++则会在运行时调用unexpected函数,这个函数的默认方式是中止程序的运行。即使代码体中没有某种异常的抛出语句,也可以声明此种异常,强迫掉用此方法的代码必须处理相应的异常 - 这等同于一个占位符,我们以后可能会回来处理(当然,对敏捷来说,这是一种不好的做法)。
对于unchecked异常(Error和RuntimeException),不应该声明(尽管可以声明,但应该花时间在修正代码逻辑错误上,而不是靠异常处理器)。
// 声明异常,告知编译器此方法可能抛出异常 MyCheckedException、MyCheckException2
Public void do() throws MyCheckedException, MyCheckException2
{
}
a.1 普通方法异常规范
如果super类某个方法抛出一些异常,子类覆盖此方法的方法所抛出的异常范围应该不超过super类。比如:如果super.f()抛出1、2、3三个异常;那么sub.f()只能抛出1、2、3的子集。这是为了保证类对象的可替换性。
a.2 构造器方法异常规范
构造器也可以抛出异常,子类的构造器必然会调用某个父类的构造器,如果调用了父类的带有异常声明的构造器,也必须声明相应父类构造器的异常;与普通方法不同的是,子类的构造器可以声明更多的异常。
a.3 接口方法异常规范
接口不能给实现接口的类方法添加新异常声明。
接口中的方法 f() 声明了某个异常 E1,一个类实现了这个方法,如果该类的父类不包括方法 f() (即没有发生方法复写),则子类中的 f() 可以声明E1;
如果该类的父类包含了f(),声明异常 E2,则子类只能在父类的异常种类范围内声明异常(E2或不声明),而不能声明接口方法中的E1.
b, 异常包装
当存在异常链的时候,可以使用异常包装的机制来保存原始异常的信息,以供上层处理器恢复:
try
{
throw new Exception1();
}
catch(Exception1 e1)
{
Exception2 e2 = new Exception("xxx", e1);
// or Exception2 e2 = new Exception("xxx"); e2. initCause(e1);
throw e2;
}
上层异常处理器可以用 e2.getCause()来获取原始异常。
3.3 、如何使用异常
基本原则:异常是为了异常情况而使用的。
1,异常的创建非常耗时,因此不能代替简单的判断语句,只应在很少发生的异常情况下使用。单纯的try-catch语句并不会造成多大的性能损失,只有在异常被触发时,需要创建一个异常对象,这时需要收集一个栈跟踪(stack trace),这通常是通过对运行时栈做一份快照来实现的,如果栈的层次很深,开销会非常大。
2,不要过分细化异常,try语句块中可放置一个功能的所有语句,而不需要每一步配一个try。
3,利用异常的层次结构,不要只是抛出RuntimeException,不要只是catch Thowable异常。在合适的时候转化异常类型,使之与当前的抽象类型一致,而不是暴露底层的实现细节。
4,一般应用中不使用unchecked异常,除非异常是无法被合适处理的(可能唯一的方法就是关闭程序)。