- 「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」。
- 当然 不论新老朋友 我相信您都可以 从中获益。如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力!
Part 1. 多态概述
多态,简而言之就是 同一个行为 具有 多个不同表现形式 或形态的能力。在面向对象的程序设计中,多态的能力是通过数据抽象和继承之后得来的。
比如,有一杯水,我不知道它是温的、冰的还是烫的,但是我一摸我就知道了,我摸水杯的这个动作 (方法),对于不同温度的水 (运行时不同的对象类型),就会得到不同的结果,这就是多态。
代码演示:
// 基类定义
public class Water {
public void showTem() {
}
}
// 冰水
public class IceWater extends Water {
@Override
public void showTem() {
System.out.println("我的温度是: 0度"); }
}
// 温水
public class WarmWater extends Water {
@Override
public void showTem() {
System.out.println("我的温度是: 40度"); }
}
// 开水
public class HotWater extends Water {
@Override
public void showTem() {
System.out.println("我的温度是: 100度"); }
}
// 测试类
public class TestWater {
public static void main(String[] args) {
Water w = new WarmWater();
w.showTem();
w = new IceWater();
w.showTem();
w = new HotWater();
w.showTem();
}
}
结果输出:
我的温度是: 40度
我的温度是: 0度
我的温度是: 100度
这里的方法 showTem()
就相当于你去摸水杯。我们定义的 Water
类型的引用变量 w
就相当于水杯,你在水杯里放了什么温度的水,那么我摸出来的感觉就是什么。就像代码中的那样,放置不同温度的水,得到的温度也就不同,但水杯是同一个。
里氏替换原则(LSP)
面向对象的设计原则有一条关于多态的原则,它的描述大概是这样子的:子类对象 (object of subtype/derived class) 能够 替换 程序 (program) 中 父类对象 (object of base/parent class) 出现的 任何地方,并且 保证原来程序的逻辑行为 (behavior) 不变及正确性不被破坏。
这么说可能有点抽象,简单说就是 子类和父类的行为应该保持一致。
哪些代码明显违背了 LSP?
实际上,里式替换原则还有另外一个更加能落地、更有指导意义的描述,那就是 “Design By Contract”,中文翻译就是 “按照协议来设计”。定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系。
为了更好地理解这句话,我举几个违反里式替换原则的例子来解释一下。
1 - 子类违背父类声明要实现的功能
父类中提供的 sortOrdersByAmount()
订单排序函数,是按照金额从小到大来给订单排序的,而子类重写这个 sortOrdersByAmount()
订单排序函数之后,是按照创建日期来给订单排序的。那子类的设计就违背里式替换原则。
2 - 子类违背父类对输入、输出、异常的约定
在父类中,某个函数约定:运行出错的时候返回 null
;获取数据为空的时候返回空集合(empty collection)。而子类重载函数之后,实现变了,运行出错返回异常(exception),获取不到数据返回 null
。那子类的设计就违背里式替换原则。
3 - 子类违背父类注释中所罗列的任何特殊说明
父类中定义的 withdraw()
提现函数的注释是这么写的:“用户的提现金额不得超过账户余额……”,而子类重写 withdraw()
函数之后,针对 VIP 账号实现了透支提现的功能,也就是提现金额可以大于账户余额,那这个子类的设计也是不符合里式替换原则的。
当然,当前的大环境下,注释的可信度还是得斟酌斟酌… (不可尽信…)
Part 2. 向上转型 && 向下转型
再谈向上转型
在 上一篇文章 里面我们已经谈到 —— 对象既可以作为它本身的类型使用,也可以作为它基类的类型使用。而这种把对某个对象的引用视为其基类型的引用的做法被称为 向上转型 (因为在继承树的画法中,基类位于子类上方)。
语句 Water w = new WarmWater();
就是向上转型的典型代码&