LSP原则:任何基类可以出现的地方,子类一定可以用子类替换。
提出问题:当一个子类重写父类方法时,如果该父类方法throws了一种或多种异常,那么子类在重写这个方法的时候可以做哪些改变呢?
一、如何理解抛出异常相当于后置条件变弱
这里的异常一般都针对checked异常。对于unchecked异常,方法一般都不做处理,只需要它在发生异常的时候被抛出即可。
首先需要明白一个最基本的例子:如果父类方法没有抛出异常,那么子类方法重写时就不可以抛出异常。(指在方法签名后throws异常)
那么我们先来理解这个问题:父类没有throws异常意味着什么?意味着它需要处理这些异常,需要用try/catch对异常进行捕捉和处理。而如果子类在重写该方法的时候加上了throws关键字意味着什么?意味着它要把处理这些异常的责任推卸给调用者,而它的方法内部就不需要try/catch来进行捕捉和处理。
这显然时不符合LSP原则的,因为在之前调用父类方法的位置,如果将其替换为子类方法,那你这个新的子类方法抛出的异常就没被处理,事实上编译器是不会允许你这么干的。
所以强弱关系就很清楚了:之前你父类需要把这些异常都处理掉,而你子类要是加了throws之后就可以不处理这些异常了。显然,后置条件变弱了!
站在客户端的角度就是,之前我调用你这个方法不需要管你内部有什么异常,拿来就可以直接用。而现在你要是加了throws之后,我再调用你那我就必须考虑这些异常,针对这些异常作相应的处理,这显然增大了客户端的难度。
二、父类抛出的异常和子类抛出的异常是什么关系?
先说结论:子类方法抛出的异常可以是父类方法throws关键字后面的那些异常中的一部分。或者,子类抛出的异常是父类异常的子类。
如何理解呢?
eg1. 子类方法抛出的异常是父类方法抛出异常的一部分
public class Basic {
public void b() throws IOException,RuntimeException {}
}
public class Son extends Basic {
@Override
public void b() throws IOException {}
}
Son是Basic的子类,它重写父类b()方法的时候,将throws后面改为了IOException,这只是父类方法抛出异常的一部分。
这样做为什么可以呢?按照第一部分的思路就可以想清楚,父类之前有这么多异常都不用处理,而子类现在只选择了其中一部分不做处理,剩下的那些它都处理了。这就是子类方法的后置条件增强了,所以是符合LSP原则的。
eg2.子类方法抛出的异常是父类方法抛出异常的子类
public class Basic {
public void b() throws Throwable {}
}
public class Son extends Basic {
@Override
public void b() throws IOException {}
}
父类中抛出的是Throwable类型,而子类抛出的是IOException。根据异常的继承层次可以知道,IOException所示Throwable的子类。
那么这么做为什么可以呢?还是按照第一部分的思路,父类之前抛出的异常是一大类异常,而子类现在只抛出了其中的一个子类。显然子类需要处理更多的异常,所以后置条件增强满足LSP原则。
三、总结
综上可以看出,抛出异常其实就相当于一种推卸责任。所以抛出的异常越多、抛出的异常范围越大,那么你的后置条件就是越弱的。