Java语言】从Java语言的角度如何理解里氏替换原则?

2021-05-25 14:25发布

7条回答
小橘子
1楼 · 2021-05-26 11:56.采纳回答

里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象

里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

      例如有两个类,一个类为BaseClass,另一个是SubClass类,并且SubClass类是BaseClass类的子类,那么一个方法如果可以接受一个BaseClass类型的基类对象base的话,如:method1(base),那么它必然可以接受一个BaseClass类型的子类对象sub,method1(sub)能够正常运行。反过来的代换不成立,如一个方法method2接受BaseClass类型的子类对象sub为参数:method2(sub),那么一般而言不可以有method2(base),除非是重载方法。

      里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

      在使用里氏代换原则时需要注意如下几个问题:

      (1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。

      (2)  我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

      (3) Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。

Danke - 四有青年
2楼 · 2021-05-26 11:21

接受的参数(类型)范围比父类大,返回的类型范围比父类小。可以理解为子类比父类强,父亲能干的事情儿子一定可以,反过来不一定成立。

pipi雪
3楼 · 2021-05-26 14:46

里氏代换原则( Liskov Substitution Principle)是指:一个软件实体如果使用的是基类的话, 那么也一定适用于其子类, 而且它根本觉察不错使用的是基类对象还是子类对象; 反过来的代换这是不成立的, 即: 如果一个软件实体使用一个类的子类对象,那么它不能够适用于基类对象。 - 里氏代换原则是实现开放封闭原则的具体规范。这是因为: 实现开放封闭原则的关键是进行抽象,而继承关系又是抽象的一种具体实现,这样LSP就可以确保基类和子类关系的正确性,进而为实现开放封闭原则服务。如图:



-举例说明: - 法海的任务是降妖除魔、造福众生。凡是妖魔现身,法海必定全力诛灭之


 



 


妖魔基类




public abstract class Spirit {

       /*

        * 妖魔的行为方法

        */

        public abstract void say();

    }



蛇妖基类



public abstract class Snake extends Spirit{

        /*

        * 蛇妖的基类,继承妖魔,可以扩展方法属性

        */

        @Override

        public void say() {

 

        }

    }



白蛇类:




 public class WhiteSnake extends Snake{

        /*

        * 白蛇对蛇妖的继承实现

        */

        @Override

        public void say() {

          System.out.print("我是白蛇");

        }

    }



测试:




public class Test{

        public static void main(String[] args){

            Spirit spirit=new WhiteSnake();

            spirit.say();

        }

    }



程序运行结果:




我是白蛇


 温馨提示:


里氏代换原则是很多其它设计模式的基础。它和开放封闭原则的联系尤其紧密。违背了里氏代换原则就一定不符合开放封闭原则。


寂静的枫林
4楼 · 2021-05-31 09:03

接受的参数(类型)范围比父类大,返回的类型范围比父类小。可以理解为子类比父类强,父亲能干的事情儿子一定可以,反过来不一定成立。

visonx
5楼 · 2021-06-03 11:41

接受的参数(类型)范围比父类大,返回的类型范围比父类小。可以理解为子类比父类强,父亲能干的事情儿子一定可以,反过来不一定成立

zgzbs
6楼 · 2021-08-19 09:29

首先,这是编译器的要求,如果不这么做,无法通过编译。
其次,面向对象的编程,其中继承有个大原则,任何子类的对象都可以当成父类的对象使用。

如有父类人类,可以使用一般的枪,有警察类,可以使用任何的枪。
class Person {
void shoot(SimpleGun simpleGun);
}
class Police extends Person {
void shoot(Gun gun);

}
其中SimpleGun extends Gun。

这样的话任何警察类的对象都可以被当做人类来使用。
也就是说警察类既然会使用任何的枪,当然可以使用一般的枪。
Person person = new Police();
person.shoot(simpleGun);

而如果反过来,普通人可以使用任何抢,警察只能使用一般枪。
class Person {
void shoot(Gun gun);
}
class Police extends Person {
void shoot(SimpleGun simpleGun);

}
这样的话就不合理了,既然警察是人类的一个子类,所以警察也是人类,既然是人类就应该能使用人类的方法,也就是使用任何的枪,可以根据上面的定义,反而警察类的能力还变小了。

所以有一个原则,子类的能力必须大于等于父类,即父类可以使用的方法,子类都可以使用。

返回值也是同样的道理。
假设一个父类方法返回一个List,子类返回一个ArrayList,这当然可以。
如果父类方法返回一个ArrayList,子类返回一个List,就说不通了。
这里子类返回值的能力是比父类小的。

还有抛出异常的情况。
任何子类方法可以声明抛出父类方法声明异常的子类。
而不能声明抛出父类没有声明的异常。

这一切都是为了,任何子类的对象都可以当做父类使用。

果果宝贝
7楼 · 2021-09-18 09:27
所谓里氏替换原则,就是让你的某一段程序耦合于基类或者接口,而不是具体继承了基类的子类或实现接口的具体类型。仅替换子类不会让你这个程序的属性有所改变。
所谓多态机制,则是给了你达成上述原则的其中一种能力。

举个栗子来说,同样是list基类/接口,子类可以是用array实现也可以用linkedlist实现,但都必须实现at方法(得到具体某个index的值)。你现在用list实现了一个找最小值的算法,我们假设你是一个个遍历过去找的,那么不管底下是array实现还是linkedlist实现(子类替换),你的算法都应该是返回最小值(属性不变),只是用linkedlist的时候很蠢。而你知道at方法对于array和linkedlist实现是不一样的,list调用at的时候根据底下具体的实现决定调用哪一个,这货就叫做多态。


相关问题推荐

  • 回答 8
    已采纳

    心里有个预期,然后看看是以什么目的进这家企业工作,要是赚钱的话,那就多要点,要是学习的话,可以根据情况要一个能养活自己的价格。

  • 回答 4
    已采纳

    Java中有八种数据类型,基础数据类型分别是:byte,short,int,long,float,double,char,boolean,引用数据类型分别是:数组,类和接口。方法传参的时候我们有两种,一种是形式参数(定义方法时写的参数),一种是实际参数(调用方法时给的具体值)。首先...

  • 回答 15
    已采纳

    现在的架构很多,各种各样的,如高并发架构、异地多活架构、容器化架构、微服务架构、高可用架构、弹性化架构等,还有和这些架构相关的管理型的技术方法,如 DevOps、应用监控、自动化运维、SOA 服务治理、去 IOE 等等,还有很多。分布式架构其实就是分布式系...

  • 回答 10

    1、监控GC的状态使用各种JVM工具,查看当前日志,分析JVM参数的设置,分析堆内存快照和GC日志,根据实际的各区域的内存划分和GC的执行时间,判断是否需要进行优化2、分析结果、判断是否需要优化如果各项参数设置合理,系统没有超时的日志出现,GC频率也不高,...

  • 回答 6

    MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以...

  • 回答 6

    学vue应该要先学习javascript 的基础知识和用法。

  • 回答 8

    1、lambda是jdk8的新特性2、使用lambda的前提,必须是一个接口,接口只能有一个抽象方法3、Lambda 表达式的简单例子:// 1. 不需要参数,返回值为 5  () -> 5    // 2. 接收一个参数(数字类型),返回其2倍的值  x -> 2 * x    // 3. 接受2个参数(数...

  • 回答 5

    你没有把jdk配置到eclipse里,步骤如下:打开eclipse,菜单栏找到window -> preference -> java -> install jres -> add -> standard vm -> 设置好相应的jre home就可以了。

  • 回答 8

    使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。...

  • 回答 11

    1. 区别:堆和栈区别堆:主要用于储存实例化的对象,数组。由JVM动态分配内存空间。一个JVM只有一个堆内存,线程是可以共享数据的。栈:主要用于储存局部变量和对象的引用变量,每个线程都会有一个独立的栈空间,所以线程之间是不共享数据的。2. 堆内存和栈内...

  • 回答 18

    至少h5、CSS、JS,包括数据库连接技术,ajax都要会哦

  • 回答 7

    对于一个秒杀系统来说,瞬时的大量请求会对后台服务造成冲击,需要保证服务的可用性以及业务的正确性。设计了一个高并发高可用的系统简要流程架构如下图:1.将商品(或券)的信息等静态数据放到cdn节点,实现动静分离2.业务请求和业务处理之间使用MQ对请求进行削...

  • 回答 16

    为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事...

  • 回答 10

    封装就是把抽象出来的JAVA类的变量和方法集成为一个集体,就像集成电路元件成为一个独立的芯片一样,它只留出对外的接口,使用者可以直接使用它,但看不到其内部是怎样实现的,JAVA类的封装就是对外而言能直接使用它来定义的对象去调用相关变量和方法。...

  • 回答 4

    1. java.awt:提供了绘图和图像类,主要用于编写GUI程序,包括按钮、标签等常用组件以及相应的事件类。2. java.lang:java的语言包,是核心包,默认导入到用户程序,包中有object类,数据类型包装类,数学类,字符串类,系统和运行时类,操作类,线程类,错...

没有解决我的问题,去提问