本文来了解一下什么是里氏替换原则。
定义
- 如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2时类型T1的子类型。
- 一个软件实体如果适用于一个父类的话,那么一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。(所以避免子类覆盖父类的方法,而是子类新增方法满足实现)
- 引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的方法时(重写/重载或实现实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类返回值更严格或相等。
例子1
我们都知道,正方形是特殊的长方形,那么正方形是长方形的子类。但是对于某种场景,这种继承关系是不符合里氏替换原则的。我先定义一个长方形类:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | public class Rectangle {private int width;
 private int length;
 
 public int getWidth() {
 return width;
 }
 
 public void setWidth(int width) {
 this.width = width;
 }
 
 public int getLength() {
 return length;
 }
 
 public void setLength(int length) {
 this.length = length;
 }
 }
 
 | 
再定义一个正方形类:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | public class Square extends Rectangle{private int sideLength;
 
 public int getSideLength() {
 return sideLength;
 }
 
 public void setSideLength(int sideLength) {
 this.sideLength = sideLength;
 }
 
 @Override
 public int getWidth() {
 return getSideLength();
 }
 
 @Override
 public void setWidth(int width) {
 this.setLength(width);
 }
 
 @Override
 public int getLength() {
 return getSideLength();
 }
 
 @Override
 public void setLength(int length) {
 this.setLength(length);
 }
 }
 
 | 
测试,场景是判断宽度小于等于长度的话,宽度则加一。测试长方形是OK的:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | public class Test {
 public static void resize(Rectangle rectangle){
 while(rectangle.getWidth() <= rectangle.getLength()){
 rectangle.setWidth(rectangle.getWidth()+1);
 System.out.println("宽度为:"+rectangle.getWidth()+",长度为:"+rectangle.getLength());
 }
 }
 
 public static void main(String[] args) {
 Rectangle rectangle = new Rectangle();
 rectangle.setWidth(10);
 rectangle.setLength(20);
 resize(rectangle);
 }
 }
 
 | 
但是测试正方形的时候,就会死循环了,因为一直都满足这个条件。也就是说正方形这个子类替换掉父类的长方形类时,程序出错了,不满足里氏替换原则了。
例子2
子类的方法入参要比父类的宽松。
定义一个基类Base:
| 12
 3
 4
 5
 
 | public class Base {public void hello(HashMap map){
 System.out.println("base is running");
 }
 }
 
 | 
定义一个子类Child:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public class Child extends Base{@Override
 public void hello(HashMap map) {
 System.out.println("child hashmap is running...");
 }
 
 public void hello(Map map){
 System.out.println("child map is running...");
 }
 }
 
 | 
测试:
| 12
 3
 4
 5
 6
 7
 
 | public class Test {public static void main(String[] args) {
 Child child = new Child();
 HashMap map = new HashMap();
 child.hello(map);
 }
 }
 
 | 
结果肯定打印: child hashmap is running…
但是如果我将子类重写的方法删除呢?结果是: base is running
如果反过来,父类入参是Map,子类重载的方法入参是HashMap,那么就会执行这个重载的方法。在业务开发中,这样子可能会造成业务逻辑的混乱。