Liskov替换原则(LSP)

逆变与协变

逆变与协变综述:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类):

  • f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
  • f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;
  • f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

协变(Co-variance)

  • 父类型->子类型:越来越具体(specific)。
  • 在LSP中,返回值和异常的类型:不变或变得更具体 。

在这里插入图片描述

逆变(Contra-variance)

  • 父类型->子类型:越来越抽象。
  • 参数类型:要相反的变化,不变或越来越抽象。
    在这里插入图片描述

LSP定义

Functions that use pointers or referrnces to base classes must be able to use objects of derived classes without knowing
it.(所有引用基类的地方必须能透明地使用其子类的对象。)

里氏替换原则的主要作用就是规范继承时子类的一些书写规则。其主要目的就是保持父类方法不被覆盖。

含义

  • 子类必须完全实现父类的方法
  • 子类可以有自己的个性
  • 覆盖或实现父类的方法时输入参数可以被放大
  • 覆盖或实现父类的方法时输出结果可以被缩小

LSP是子类型关系的一个特殊定义,称为(强)行为子类型化。在编程语言中,LSP依赖于以下限制:

  • 前置条件不能强化
  • 后置条件不能弱化
  • 不变量要保持或增强
  • 子类型方法参数:逆变
  • 子类型方法的返回值:协变
  • 异常类型:协变

各种应用中的LSP:

1.【数组是协变的】

数组是协变的:一个数组T[ ] ,可能包含了T类型的实例或者T的任何子类型的实例

Number[] numbers = new Number[2]; 
numbers[0] = new Integer(10); 
numbers[1] = new Double(3.14);

Integer[] myInts = {1,2,3,4}; 
Number[] myNumber = myInts;

myNumber[0] = 3.14; //run-time error!

报错的原因是myNumber指向的还是一个Integer[] 而不是Number[]

2.【泛型中的LSP】

Java中泛型是不变的,但可以通过通配符"?"实现协变和逆变:

  • <? extends>实现了泛型的协变:
  • List<? extends Number> list = new ArrayList();
  • <? super>实现了泛型的逆变:
  • List<? super Number> list = new ArrayList();

由于泛型的协变只能规定类的上界,逆变只能规定下界,使用时需要遵循PECS(producer–extends, consumer-super):

  • 要从泛型类取数据时,用extends;
  • 要往泛型类写数据时,用super;
  • 既要取又要写,就不用通配符(即extends与super都不用)。

泛型是类型不变的(泛型不是协变的)。举例来说

  • ArrayList 是List的子类型
  • List不是List的子类型

在代码的编译完成之后,泛型的类型信息就会被编译器擦除。因此,这些类型信息并不能在运行阶段时被获得。这一过程称之为类型擦除(type erasure)。
类型擦除的详细定义:如果类型参数没有限制,则用它们的边界或Object来替换泛型类型中的所有类型参数。因此,产生的字节码只包含普通的类、接口和方法。
在这里插入图片描述
类型擦除的结果: 被擦除 T变成了Object

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值