里氏替换原则的理解:
对于里氏替换原则的四个重要特性:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
1.里氏替换原则要实现的逻辑是:
声明对象时用基类/接口来声明,具体对象的实现 用new不同的实体来达到实现不同类中的功能的目的,就是向下转型。
2.对于为什么当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,是因为:
1>继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
2>设置子类的方法比父类的形参条件更宽松,在没有重写的情况下,父类对象调用方法是就会优先调用父类中的方法,而不是子类中重载的方法
方法中的输入参数称为前置条件,我们以一个例子说明一下覆盖或实现父类的方法时输入参数可以被放大:
我们先定义一个Father类:
import java.util.Collection;
import java.util.HashMap;
public class Father{
public Collection doSomething(HashMap map){
System.out.println("父类被执行");
return map.values();
}
}
再定义一个子类:
import java.util.Collection;
import java.util.Map;
public class Son extends Father{
public Collection doSomething(Map map){
System.out.println("子类被执行");
return map.values();
}
}
场景类:
public class Client{
public static void main(Strings[] args){
//父类存在的地方,子类应该可以存在
Father father = new Father();
HashMap map = new HashMap();
father.doSomething(map);
}
}
运行结果:
父类被执行
根据里氏替换原则,父类出现的地方,子类也是可以出现的。我们把Client代码修改如下:
public class Client{
public static void main(Strings[] args){
//父类存在的地方,子类应该可以存在
//Father father = new Father();
Son father = new Son();
HashMap map = new HashMap();
father.doSomething(map);
}
}
运行结果:
父类被执行
结果一样,父类的方法的输入参数是HashMap类型,子类的方法输入参数是Map类型,也就是说子类的输入参数类型范围扩大了,子类代替父类,子类的方法不被执行,这是正确的,如果你想让子类的方法运行,就必须覆写父类的方法。
如果,我们反过来,把父类的输入参数类型放大,子类的输入参数类型缩小,让子类的输入参数类型小于父类的输入参数类型,看看会出现什么情况?
父类前置条件较大:
import java.util.Collection;
import java.util.Map;
public class Father{
public Collection doSomething(Map map){
System.out.println("父类被执行");
return map.values();
}
}
子类的前置条件较小:
import java.util.Collection;
import java.util.HashMap;
public class Son extends Father{
public Collection doSomething(HashMap map){
System.out.println("子类被执行");
return map.values();
}
}
场景类:
public class Client{
public static void main(Strings[] args){
//父类存在的地方,子类应该可以存在
Father father = new Father();
HashMap map = new HashMap();
father.doSomething(map);
}
}
运行结果:
父类被执行
我们再把里氏替换原则引入,父类出现的地方子类也可以出现,我们修改一下Client类,看看有什么问题:
public class Client{
public static void main(Strings[] args){
//父类存在的地方,子类应该可以存在
//Father father = new Father();
Son father = new Son();
HashMap map = new HashMap();
father.doSomething(map);
}
}
输出结果:
子类被执行
调用了子类,子类在没有覆写父类的方法的前提下,子类方法被执行了,这会引起业务逻辑混乱,因为在实际应用中父类一般是抽象类,子类是实现类,你传递一个这样的实现类就会歪曲了父类的意图,引起业务逻辑混乱,所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松。