里氏替换原则:LSP
定义:
假设对于每个类型为S的对象o1。都有类型为T的对象o2。使得以T定义的全部程序P在全部的对象o1都换为o2时,程序的行为没有发生变化。那么S是T的子类型。
在继承的时候,父类出现的地方子类就能够出现,子类可替代父类,由于子类中有父类的方法,然而父类却不能够替代子类,由于子类中可能有父类没有的方法。这就是所谓的向下转型是不安全的。
使用继承有非常多长处,能够提高代码的重用性。提高可扩展性、开放性,可是不可否认,继承也是有缺点的:
1.继承是侵入性的,仅仅要继承,就必须拥有父类的全部属性和方法;
2.减少代码的灵活性
3.增强了耦合性。
解决方式就是里氏替换原则。
4个含义:
1.子类必须全然实现父类的方法
2.子类能够有自己的方法
3.覆盖或实现父类的方法时,输入參数能够被放大
4.覆写或实现父类的方法时。输出结果能够被缩小
前两个含义比較好理解。这里就不再赘述,主要说一下3和4。
先说第3个,覆盖或实现父类的方法时。输入參数能够被放大。
先看一个样例:
class Father { public Collection dosomething(HashMap map) { System.out.println("父类被运行--->"); return map.values(); } } class Son extends Father { public Collection dosomething(Map map) { System.out.println("子类被运行--->"); return map.values(); } } public class Client { public static void main(String[] args) { // 父类存在的地方就能够替换为子类 Father f = new Father(); HashMap map = new HashMap(); f.dosomething(map); } }
代码执行结果是:
父类被运行--->
依据里氏替换原则,将父类改为子类:
public class Client { public static void main(String[] args) { // 父类存在的地方就能够替换为子类 // Father f = new Father(); Son f = new Son(); HashMap map = new HashMap(); f.dosomething(map); } }
然而输出结果还是父类被运行。
。。
父类方法的參数是HashMap。而子类方法的參数是Map。也就是说子类的參数类型范围大,子类取代父类传递到调用者中,子类的方法永远不会被运行。
假设想要运行子类中的方法的话就须要覆写父类中的方法。覆写就是父类中的方法一模一样的出如今子类中。
class Father { public Collection dosomething(HashMap map) { System.out.println("父类被运行--->"); return map.values(); } } class Son extends Father { // public void dosomething(Map map) { // System.out.println("子类被运行--->"); // } @Override public Collection dosomething(HashMap map) { // TODO Auto-generated method stub System.out.println("子类被运行--->"); return map.values(); } } public class Client { public static void main(String[] args) { // 父类存在的地方就能够替换为子类 // Father f = new Father(); Son f = new Son(); HashMap map = new HashMap(); f.dosomething(map); } }
这是正常的。
假设父类參数的參数类型范围大于子类输入參数类型的话,会出现什么问题呢?会出现父类存在的地方,子类就未必能够存在,由于一旦把子类作为參数传入,调用者就非常可能进入子类的方法范畴。
改动一下上面的代码。扩大父类參数范围。缩小子类參数范围。
class Father { public Collection dosomething(Map map) { System.out.println("父类被运行--->"); return map.values(); } } class Son extends Father { public Collection dosomething(HashMap map) { System.out.println("子类被运行--->"); return map.values(); } } public class Client { public static void main(String[] args) { // 父类存在的地方就能够替换为子类 Father f = new Father(); Son f1 = new Son(); HashMap map = new HashMap(); f.dosomething(map); f1.dosomething(map); } }
f运行父类的方法,f1运行子类的方法。
这就不正常了
子类在没有覆写父类方法的情况下。子类方法被运行了。所以,子类中方法的參数范围(前置条件)必须与父类的參数范围(前置条件)同样或者更加宽松。
再来说一下第4个含义,覆写或实现父类的方法时,输出结果能够被缩小。
什么意思呢?父类方法的返回值是类型T,子类同样方法(重载或覆写)的返回值是S,那么里氏替换原则就要求S必须小于等于T。也就是说。要么S和T类型同样,要么S是T的子类。
对于覆写而言,父类和子类中的方法时一模一样的,所以返回类型也应当是一样的。
对于重载,也就是第3个含义所讲到的,子类的输入參数宽于或等于父类的參数,也就是说这种方法时不会被调用的。