专家级程序猿与缺乏经验的程序猿【菜猿】一个最主要的区别在于,专家追求并且通常也能够实现高度的代码重用。代码重用是值得提倡的,这是一条规则,异常也不例外。Java平台的类库提供了一组基本的未受检异常,它们满足了绝大多数API抛出的所需要的异常。
重用现有的异常有多方面的好处。其中最主要的好处是,它使你的API更加易于学习和使用,因为它与程序猿已经熟悉的习惯用法是一致的。第二个好处是,对于用到这些API的程序而言,它们的可读性会更好,因为它们不会出现很多程序猿不熟悉的异常。最后(也是最不重要的)一点是,异常类越少,意味着内存印迹(footprint)【异常堆栈】就越小,装在这些类的时间开销也越少。
最经常被重用的异常是IllegalArgumentException。当调用者传递的参数值不合适的时候,往往就会抛出这个异常。例如,假设一个参数代表了“某个动作重复的次数”,如果程序猿给这个参数传递了一个负数,就会抛出这个异常。
另一个经常被重用的异常是IllegalStateException。因为接收对象的状态而使调用非法,通常就会抛出这个异常。例如,如果在某个对象被正确地初始化之前,调用者就企图使用这个对象,就会抛出这个异常。
可以这么说所有错误的方法调用都可以被归结为非法参数或者非法状态,但是,其他还有一些标准异常也被用于某些特定情况下的非法参数和非法状态。如果调用者在某个不允null值的参数中传递了null,习惯的做法就是抛出NullPointerException,而不是IllegalArgumentException。同样地,如果调用者在表示序列下标的参数中传递了越界的值,应该抛出的就是IndexOutOfBoundsException,而不是IllegalArgumentException。
另一个可重用的异常是ConcurrentModificationException。如果设计为由单个线程(或外部同步)使用的对象检测到它正在被并发修改,则应该抛出它。此异常充其量只是一个提示,因为无法可靠地检测并发修改。
最后一个值得注意的通用异常是UnsupportedOperationException。如果对象不支持尝试的操作,就会抛出这个异常。它很少使用到,因为绝大多数对象都会支持它们实现的所有方法。如果接口的具体实现没有实现该接口所定义的一个或者多个可选操作,它就可以使用这个异常。例如,对于只支持追加操作的List实现,如果有人试图从列表中删除元素,它就会抛出这个异常。
不要直接重用Exception,RuntimeException,Throwable或Error 。将这些类看作是抽象的。 您无法可靠地测试这些异常,因为它们是方法可能抛出的其他异常的超类。
下面的这个表概括了最常见的可重用的异常:
Exception | Occasion for Use |
---|---|
IllegalArgumentException | Non-null parameter value is inappropriate |
IllegalStateException | Object state is inappropriate for method invocation |
NullPointerException | Parameter value is null where prohibited |
IndexOutOfBoundsException | Index parameter value is out of range |
ConcurrentModificationException | Concurrent modification of an object has been detected where it is prohibited |
UnsupportedOperationException | Object does not support method |
虽然它们是Java平台类库中迄今为止最常被重用的异常,但是,在条件允许的情况下,其他的异常也可以被重用。例如,如果要实现诸如复数或者有理数之类的算术对象,也可以重用ArithmeticException和NumberFormatException。如果某个异常能够满足你的需要,就不要犹豫,使用它【盘它!】,不过,一定要确保抛出异常的条件与该异常的文档中描述的条件一致:这种重用必须建立在语义的基础上,而不是建立在名称的基础之上【就是要根据异常的语义来使用,而不要根据异常的名称来决定使用哪个异常】。此外,如要要为异常添加更多的细节【信息】(第75项),请随意为标准的异常创建子类【使劲盘它!】,但请记住,异常是可序列化的(第12章)。因此,如果没有充分的理由就不要自己编写异常【不要为所欲为地盘它】。
选择要重用的异常可能很棘手,因为上表中的“使用场合”似乎并不相互排斥。例如,考虑表示一副纸牌的对象。假设有个处理发牌操作的方法,它的参数是一手牌的纸牌张数。假设调用者在这个参数中传递的值大于整副纸牌的剩余张数。这种情形既可以被解释为IllegalArgumentException(handSize参数的值太大),也可以被解释为IllegalStateException(纸牌对象包含的纸牌太少)。在这种情况下,有个规则就是:如果没有可用的参数值,则抛出IllegalStateException,否则抛出IllegalArgumentException