里式替换原则(Liskov Substitution Principle) 
引言 
里式替换原则是用来约束继承的使用的一种规范。里式替换原则鼓励开发者使用接口或抽象类开发,用接口或抽象类的引用来调用对象。
 
继承的优点 
代码共享,减少创建类的工作量。每个子类拥有父类的方法和属性。 
提高代码的重用性。 
子类完全继承父类,但也可以拥有自己的方法和属性,是对父类的扩展。 
提高代码的可扩展性,扩展接口可以继承父类。 
提高产品或项目的开放性。 
继承的缺点 
继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。 
降低代码的灵活性。子类必须拥有父类的属性和方法。 
增强了耦合性。当父类被修改,子类也必须要进行响应的更改,这会导致代码的大量重构。 
为了避免继承的缺点,引入里式替换原则  
 
 
定义 
第一种定义 
If for each object o1 of type S there is an object o2 of type T such that for all progeams P defined in terms of T ,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T . 
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有对象o1都代换为o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。 
理解:类型T的对象能够替换类型S的对象,也就是说类型T包含类型S(T是S的父类)
 
第二种定义 
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it 
所有引用基类的地方必须能透明地使用其子类的对象。 
理解:只要父类能出现的地方,子类就可以出现,而且替换子类也不会产生任何错误和异常,使用者根本不用知道是父类还是子类。但是有子类的地方,父类未必能适应。也就是说,父类对象和子类对象没有太大区别,使得使用者能够使用父类接口来使用子类对象。
 
含义 
子类必须完全实现父类的抽象方法,不能重写父类已实现的方法应用  
子类必须完全实现父类的抽象方法:  因为java中子类需要完整的实现父类的抽象方法,如果子类不完全实现父类抽象方法,那么这个子类也是一个抽象类,因为实体类必须完全实现抽象类的方法。在这里说的子类一般指代实体类,因此实体类必须完全实现抽象父类的抽象方法。 
子类不能重写父类已实现的方法:  父类已实现的方法是一种已定好的契约和规范。 
子类可以有自己的个性应用  
子类继承了父类,拥有父类的方法,子类也能有自己的方法和属性。 
如果子类转为父类类型很安全(向上转型,子类完全兼容父类),但父类类型转子类类型则不安全(向下转型,子类有父类没有的东西,父类不能完全兼容子类)。 
覆盖或实现父类的方法时输入(输入参数被称为前置条件 )可以被放大 应用  
子类重载父类方法时,当父类的参数类型范围比子类参数类型范围大时,子类会调用子类自己重载父类的方法,却不会调用子类继承父类的方法。这样子类并不能代替父类执行,不符合里式替换法则。 
子类重载父类方法时,当父类参数类型范围小于子类参数类型范围时,子类对代替父类执行父类的方法。 
因此,子类的参数范围需要大于或等于父类的参数类型范围,这样,会调用自动匹配子类继承父类的方法,不会调用子类重载的方法,从而使程序逻辑混乱。 
覆写或实现父类的方法时输出结果(返回结果被称为后置条件 )可以被缩小应用  
父类一个方法返回值是类型T,子类重写或重载的方法返回值是S,那么T必须是S的父类(T大于等于S) 
 
应用 
应用场景1 
子类必须完全实现父类的抽象方法 
1 2 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 public  abstract  class  MilitaryAircraft   {         public  void  fly ()  {         System.out.println("我能飞" );     }          public  abstract  String type ()  ;          public  abstract  void  attack ()  ;          public  abstract  void  intro ()  ; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  class  J20  extends  MilitaryAircraft   {    @Override      public  String type ()   {         return  "第四代隐身重型战斗机J20" ;     }     @Override      public  void  attack ()   {         System.out.println("战斗机攻击" );     }     @Override      public  void  intro ()   {         System.out.println("我是解放军序列第四代隐身重型战斗机J20" );     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  class  Pilot   {    private  MilitaryAircraft militaryAircraft;     public  Pilot ()   {     }     public  Pilot (MilitaryAircraft militaryAircraft)   {         this .militaryAircraft = militaryAircraft;     }     public  MilitaryAircraft getMilitaryAircraft ()   {         return  militaryAircraft;     }     public  void  setMilitaryAircraft (MilitaryAircraft militaryAircraft)   {         this .militaryAircraft = militaryAircraft;     }     public  void  fighting ()  {         System.out.println("飞行员驾驶" +militaryAircraft.type()+"开始战斗" );         militaryAircraft.attack();     } } 
 
1 2 3 4 5 6 7 8 9 10 public  class  Test   {    public  static  void  main (String[] args)   {                  Pilot pilot = new  Pilot();                  pilot.setMilitaryAircraft(new  J20());         pilot.fighting();     } } 
 
1 2 飞行员驾驶第四代隐身重型战斗机J20开始战斗 战斗机攻击 
 
如果子类没有完全实现父类的抽象方法(举例可能有失偏颇) 
再创建子类(运输机不能攻击,如果这个子类直接继承父类,会出现问题) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  class  C130  extends  MilitaryAircraft   {    @Override      public  String type ()   {         return  "军用运输机C130" ;     }          @Override      public  void  attack ()   {     }     @Override      public  void  intro ()   {         System.out.println("我是美军序列军用运输机大力神C130" );     } } 
 
测试 
C130是运输机,是执行运输任务的飞机,并不是战斗单位,因此,不能继承父类的attack方法。 
 
1 2 3 4 5 6 7 8 9 10 public  class  Test   {    public  static  void  main (String[] args)   {                  Pilot pilot = new  Pilot();                  pilot.setMilitaryAircraft(new  C130());         pilot.fighting();     } } 
 
测试结果 
上面的虚构一个攻击方法使得运输机也上了战场,但是没有战斗力,那只有被击落的份。 
 
 
为了解决这个问题,我们必须将运输机这个子类与父类断开继承关系,再创建一个运输机父类,让C130继承这个。这使得C130运输机脱离战斗序列 
 
1 2 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 public  abstract  class  Transport   {    private  MilitaryAircraft militaryAircraft ;     public  Transport ()   {     }     public  Transport (MilitaryAircraft militaryAircraft)   {         this .militaryAircraft = militaryAircraft;     }          public  void  fly ()  {         militaryAircraft.fly();     }          public  abstract  String type ()  ;          public  abstract  void  intro ()  ;     public  abstract  void  transport ()  ; } 
 
1 2 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public  class  Pilot   {         private  MilitaryAircraft militaryAircraft;     private  Transport transport ;     public  Pilot ()   {     }     public  Pilot (MilitaryAircraft militaryAircraft)   {         this .militaryAircraft = militaryAircraft;     }     public  Pilot (MilitaryAircraft militaryAircraft, Transport transport)   {         this .militaryAircraft = militaryAircraft;         this .transport = transport;     }     public  MilitaryAircraft getMilitaryAircraft ()   {         return  militaryAircraft;     }     public  void  setMilitaryAircraft (MilitaryAircraft militaryAircraft)   {         this .militaryAircraft = militaryAircraft;     }     public  Transport getTransport ()   {         return  transport;     }     public  void  setTransport (Transport transport)   {         this .transport = transport;     }     public  void  transport ()  {         System.out.println("飞行员驾驶" +transport.type());         transport.transport();     }     public  void  fighting ()  {         System.out.println("飞行员驾驶" +militaryAircraft.type()+"开始战斗" );         militaryAircraft.attack();     } } 
 
1 2 3 4 5 6 7 8 9 public  class  Test   {    public  static  void  main (String[] args)   {                  Pilot pilot = new  Pilot();                  pilot.setTransport(new  C130());         pilot.transport();     } } 
 
 
子类不能重写父类已实现的方法 
1 2 3 4 5 6 7 8 public  class  Parent   {    public  int  add (int  a , int  b)  {         return  a+b ;     } } 
 
1 2 3 4 5 6 7 8 public  class  Child  extends  Parent  {    public  int  add (int  a , int  b)  {         return  a+(-b) ;     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  class  Test   {    public  static  void  main (String[] args)   {                  Parent parent = new  Parent();         System.out.println("执行父类" );         int  add = parent.add(1 , 4 );         System.out.println("父类执行结果:" +add);                  Parent child = new  Child();         System.out.println("执行子类" );         int  add1 = child.add(1 , 4 );         System.out.println("之类执行结果:" +add1);     } } 
 
测试结果 
父类执行的结果与子类代替父类执行的结果不同,这违反了里式替换原则 
 
1 2 3 4 执行父类 父类执行结果:5  执行子类 之类执行结果:-3  
 
应用场景2 
子类可以有自己的个性 
1 2 3 4 5 6 7 8 9 public  class  Parrent   {    public  int  max (int  a , int  b)  {         if (a == b){             System.out.println(a+"==" +b);             return  0  ;         }         return  a>b?a:b;     } } 
 
1 2 3 4 5 public  class  Child  extends  Parrent  {    public  void  intro ()  {         System.out.println("我是子类,我为我自己代言!" );     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  class  Test   {    public  static  void  main (String[] args)   {                  System.out.println("父类开始执行" );         Parrent parrent = new  Parrent();         int  max = parrent.max(3 , 4 );         System.out.println("最大值为:" +max);                  System.out.println("子类开始执行" );         Child child = new  Child();         child.intro();     } } 
 
测试结果 
子类继承父类,但子类也可以有自己的方法,也就是个性。如果用父类的引用指向子类对象,使用的是父类方法,不能使用子类方法。如果子类引用指向子类对象,能调用子类方法和父类方法。 
 
1 2 3 4 父类开始执行 最大值为:4  子类开始执行 我是子类,我为我自己代言! 
 
应用场景3 
覆盖或实现父类的方法时输入(输入参数被称为前置条件 )可以被放大 
1 2 3 4 5 6 7 import  java.util.HashMap;public  class  Parrent   {    public  void  function (HashMap map)  {         System.out.println("父类方法被执行" );     } } 
 
1 2 3 4 5 6 7 import  java.util.Map;public  class  Child  extends  Parrent  {    public  void  function (Map map)  {         System.out.println("子类被执行" );     } } 
 
1 2 3 4 5 6 7 8 9 10 11 import  java.util.HashMap;public  class  Test   {    public  static  void  main (String[] args)   {         HashMap<Object, Object> map = new  HashMap<>();         Parrent parrent = new  Parrent();         Child child = new  Child();         parrent.function(map);         child.function(map);     } } 
 
 
反例 
1 2 3 4 5 6 7 import  java.util.Map;public  class  Parrent   {    public  void  function (Map map)  {         System.out.println("父类方法被执行" );     } } 
 
1 2 3 4 5 6 7 import  java.util.HashMap;public  class  Child  extends  Parrent  {    public  void  function (HashMap map)  {         System.out.println("子类被执行" );     } } 
 
1 2 3 4 5 6 7 8 9 10 11 import  java.util.HashMap;public  class  Test   {    public  static  void  main (String[] args)   {         HashMap<Object, Object> map = new  HashMap<>();         Parrent parrent = new  Parrent();         Child child = new  Child();         parrent.function(map);         child.function(map);     } } 
 
 
应用场景4 
覆写或实现父类的方法时输出结果(返回结果被称为后置条件 )可以被缩小 
1 2 3 4 5 import  java.util.Map;public  abstract  class  Parrent   {    public  abstract  Map function ()  ; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import  java.util.HashMap;public  class  Child  extends  Parrent  {         @Override      public  HashMap<Integer,String> function ()   {         HashMap<Integer, String> map = new  HashMap<>();         map.put(1 ,"Linux" );         map.put(2 ,"Windows" );         map.put(3 ,"mac" );         return  map;     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  java.util.HashMap;import  java.util.Map;import  java.util.Set;public  class  Test   {    public  static  void  main (String[] args)   {         Child child = new  Child();         HashMap<Integer,String> map = child.function();         Set<Map.Entry<Integer, String>> entrySet = map.entrySet();         for  (Map.Entry<Integer,String> entry : entrySet){             Integer key = entry.getKey();             String value = entry.getValue();             System.out.println(key+"-----" +value);         }     } } 
 
1 2 3 1 -----Linux2 -----Windows3 -----mac