当前位置:网站首页>多态 polymorphism
多态 polymorphism
2022-08-09 02:28:00 【牧..】
多态 polymorphism
前言 : 本 主要内容: 多态
多态: 字面 意义 多种 形态 / 状态 ,
如 : 一个 人 可以 有多种 状态 : 开心 , 生气, 郁闷。
但如 过你 面试 的时候 面到多态的 问题 ,如果 只说出 这语句 化 多半 要 凉凉。
想要了解 多态 我们需要 先 了解 这个 几个 方法面
1.重写
2.向上转型
3.动态绑定
1.重写
这里 先来 看我们的 代码
class Animal{
public String name;
public int age;
public void eat(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
}
class Cat extends Animal{
}
public class Test {
}
这里 Dog 和 Cat 都 继承 了 Animal , 但 我们 觉得 此时 eat 方法 有点不合适 ,狗 吃 狗粮 ,猫 吃 猫粮 , 这里 的 eat 方法中的 吃饭 就有点 草率了 。
那么 我们 就需要 重新 在 Dog 和 Cat 类 中 将 eat 方法 进行 重写 ,来 完成我们 狗 吃 狗粮, 猫 吃 猫粮 的 操作 。

此时 我们 的 Dog 中 的 eat 方法 和 Animal 中 的 eat 方法 就构成 了 我们 的重写。
重写 的 要求 :
1.方法名相同
2.返回值 相同
3.参数列表相同(数据类型,个数,顺序 都得相同)
小细节 :
1.重写 的 方法 访问 权限不做 要求 ,但是 父类 的 访问权限 要 低于或等于 子类 。
2.被 private 修饰 是 不能 被 重写的
下面 就来演示 一下:
1.父类 是 public 子类必须是 public

2.父类 是 包访问 权限 (什么都没加)

3.被prviate 修饰 的 方法是 不能 被 重写 的
此时 聪明 的 同学 们 ,就开始 说 ,我直接 父类 加上 个, private 修饰 直接 子类访问 修饰 符 想要啥 要啥了吗 ?
可惜 很 抱歉 , 我们 的 方法 如果 被 private 修饰 是 不能够 重写的

另外 : 我们 除了 被 private 修饰 的 方法 不能 重写其实 还有 两种 方式 会 导致 父类 方法 不能够 重写 。
1.被 final 修饰 的 父类方法 也不能够 重写
演示:

这里 final 修饰 的 方法 我们 叫做 密封 方法 ,不能被 重写。
2.被 static 修饰的 父类方法 也不能够 重写
演示:

这里我们 的 注解 报错 了 , 这里 可以 有 人 会 想 我们 删掉 注解 不就 ok了。

可惜 , 这里 我们还是报错 了 。
这里 就需要我们 注意 : @Override 注解 。 有了这个注解能帮我们进行一些``合法性校验.`
这 被 static 修饰 重写 本身 就 是 不和 法 的 这里 注解 就会 进行 合法性 校验 , 发现 你这个 错误就 在 注解处报错 了 ,没有 注解 那么 就直接 在 方法上 报错了。
补充 : 重写 其实 可以 返回值 不同
我们 上面 刚刚 说了 重写 返回 值 相同 ,这里 就 说 返回值 可以 不同 ,真 的 绕来绕去 , 为啥 这里 又能 返回值 不同呢 。
这里 就来看一下 :

这里我们 的 返回值 可以 不一样 但是 我们的 返回值 之间 必须 构成父子类 关系。
上面 差不多就算 重写 的 全部 内容, 下面 来 看看文字 描述 。
重写(override):
也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法
下面 我们 来 看一下 面试 问题 : 重载 和 重写 的 区别 是 什么?
| 重载 | 重写 | |
|---|---|---|
| 相同点 | 1.方法名称相同 | 1.方法名称相同 |
| 不同点 | 2.返回值不做 要求 | 2.返回值不同 构成父子类关系也可以 |
| 不同点 | 3.参数列表不同 (数据类型 顺序,个数 都不同) | 3.参数列表相同 (数据的 类型 ,顺序 ,个数,都相同) |
| 不同点 | 4.在同一个类中 或 不同类 中 都能构成重载 | 4.重写 一定是 发生在 继承层次上(两个 或 以上的 类) |
| 不同点 | 重载 是 没有权限 要求的 | 5.重写 是 有权限要求的 (子类的权限要大于等于 父类) 另外父类 被 private 修饰 是不能够重写的 |
知道了 重写 ,那么 我们 要如何 去设计 我们的 重写 呢 (换种 说法 啥时候 使用 重写) ?
重写的 设计 原则
这里我们 就来 看一下 我们的 【重写的设计原则】
对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的类,可能还在有用户使用,正确做法是:新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我们当今的需求了。

看完 设计 原则 , 下面 继续 。
这里先 抛出 两个 概念 ,先来 学习 一下 静态 绑定 , 动态绑定 在 讲完 向上 转换 讲解。
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
演示:
public class Test {
public static void func(){
}
public static void func(int a){
}
public static void func(int a, int b){
}
public static void main(String[] args) {
func();
func(1);
func(1,2);
}
}
这里我们编译的 时候 根据 你 传 入 的 参数,能够确定你 调用那个方法 , 这种 就叫做 静态绑定。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
这里我们 要了解 动态绑定 就需要 先了解我们的 向上转型 。
2.向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

下面 看代码 :
class Animal {
public String name;
public int age;
public void eat() {
System.out.println(name + "开行的 吃饭");
}
}
class Dog extends Animal{
public void func(){
System.out.println("Dog 特有的 方法");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// animal 这个 引用 指向了 dog 这个对象
Animal animal = dog;
}
}
这里 就是 我们的 向上转型。
注意: 此时 animal 这个引用 虽然 拿到 到了 dog 对象, 但是不能调用 Animal 类 中 没有 ,但 Dog 这个 类 中 特有 的 方法 或 属性。

可以 看到 我们 是不能 将 dog 这个 对象 的 func 方法 给调用出来的。
这里我们的 Animal 没有 func 这个 方法 所以我们就 访问不了 。
此时 我们 继续 。

下面就来 了解 我们的 动态绑定 :
概念 :
当 子类 重写 了 父类 的 方法 时 , 通过 父类 引用 引用了 子类 对象, 调用 这个被 重写 了 的 方法时 ,就会 发生我们的动态 绑定。
这里 我们 动态绑定 的 条件 :
分析 上面这句话 我们能得到几个 关键条件 ,
1.子类 重写父类 的 方法 .
2. 父类 引用了子类 对象 。
3.需要 调用 这个 重写的 方法
下面我们 通过 观察 反汇编 来 看看 动态绑定 。

下面 我们 继续 回到 我们的 向上转型 。
向上转型的 两种 方式 :
1.直接赋值 (刚刚我们写 的 就是 直接 赋值 )

2.方法 传参
class Animal {
public String name;
public int age;
public void eat() {
System.out.println(name + "开行的 吃饭");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("重写 的 eat 方法");
}
public void func() {
System.out.println("Dog 特有的 方法");
}
}
public class Test {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog();
func(dog);
}
}

这里 方法 传值 的 过程 中 , 就发生 了 向上 转型 , (传入 的 对象 是 子类 ,方法 接收 的 是 父类 引用)。
多态
介绍 到现在 , 接下来 一个 代码 就能 让 大家 立马 知道 多态是 是什么 。
请看:
class Animal {
public String name;
public int age;
public void eat() {
System.out.println(name + "开行的 吃饭");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("重写 的 eat 方法");
}
public void func() {
System.out.println("Dog 特有的 方法");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("重写 的 eat 方法");
}
public void func() {
System.out.println("Cat 特有 的方法");
}
}
public class Test {
public static void func(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog();
func(dog);
Cat cat = new Cat();
func(cat);
}
}

看完 上面的代码 再来 看 概念 是不是 就 非常 清楚 了
多态:
多态是同一个行为具有多个不同表现形式或形态的能力,
例如:黑白打印机和彩色打印机相同的打印行为却有着不同的打印效果,
- 对象类型和引用类型之间存在着继承(类)/ 实现(接口)的关系;
- 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
- 如果子类重写了父类的方法,最终执行的是子类覆盖的方法,如果没有则执行的是父类的方法。
补充 : 第三种 方法 产生 向上 转型 : 作为 返回值
class Animal {
public String name;
public int age;
public void eat() {
System.out.println(name + "开行的 吃饭");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog 中 重写 的 eat 方法");
}
public void func() {
System.out.println("Dog 特有的 方法");
}
}
public class Test {
// 第三种 向上 转型 , 作为 返回 值 。
public static Animal func(){
return new Dog();
}
}
这里我们 原本 返回的是 Animal 类 行 的 , 但 我们 返回 了 一个 Dog 子类 对象 ,这里 同样 也会 发生 向上 转型。
有 向上 转型,我们 还有 向下 转型 。
下面 就来 学习 一下 。
3.向下 转型
我们 前面 说过 ,向上 转型 的 引用 是 调用 不了 子类 特有的 属性 或 方法 的 , 那么 我们 要如何 去 调用 子类 特有 的 方法 呢? 这里 我们 就可 以 使用 向下 转型 。

使用 向下 转型 调用 Dog 中 特有 的 func 方法。

这里 就 是 我们 的 向下 转型 , 这里 我们 运行 下 。

这里 就 成功 的 调用 处 Dog 特有 的 特有的 方法 , 但 向下 转型 存在 不安全的 地方 , 这里我们 继续 来 举例子 ,

这里 我们 有 两个 类 , 一个 鸟 类 , 一个 狗 类 。
当你 完成 下面 操作的 时候 ,你 就会 发现 会报错 。

这里 我们 狗 会 想 鸟 一样飞吗 ? (除掉 那种 一生 只能 飞一次的 可能)。
原本 我们 向上转型 ,将 Dog 对象 给了 父类 Animal 的 引用, 然后 又 向下 转型 ,将 这个 引用 狗的 对象 , 赋值 给 Dog类型的 引用, 这样是不会 出错的 , 但 赋值 给 鸟 这个 类 的引用 ,此时 就会报错。 但是我们 每 编译 时 是 不能 发现 类型 不匹配 的 , 只有 编译 后 才能 发现 。
那么 要如何 解决 这个 问题 呢?
此时我们 就需要一个 关键字 来 帮助我们 来判断
关键字 : instanceof

最后 :我们 一般 不太 使用 向下 转系 了解 即可 。
关于构造方法的 坑
在 本文 的 最后 我们 来 看一下 一个 关于 构造 方法 的 坑 .
观察下面的代码 分析 最后输出 什么 :
class Animal {
public String name = "hellow";
public int age;
private int count;
public Animal(String name, int age) {
this.name = name;
this.age = age;
eat();
}
public void eat() {
System.out.println(name + "ani::eat()");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name + "狼吞虎咽的eat()");
}
}
public class TestDome {
public static void main(String[] args) {
Dog dog = new Dog("小黑", 18);
}
}
是 ani:: eat() 还是 狼吞虎咽 的 eat() 呢 ?
答案揭晓:

你的 答案 对 吗?
下面 就来 梳理 一下 。

本文结束 :下文 预告 抽象类 or 接口
边栏推荐
猜你喜欢

10.1-----19. Delete the Nth node from the bottom of the linked list

jmeter的websocket插件安装和使用方法

【电商运营】不知道怎么做网站优化?这里有你需要知道的一切!

Pytest+request+Allure实现接口自动化框架

全志平台双路LVDS配置

为什么应用程序依赖关系映射对于云迁移至关重要

Force buckled brush problem record 7.1 -- -- -- -- -- 707. The design list

企业从云服务的承诺支出中获得最大收益的四种方法

笔算开2次方根、3次方根详细教程

NPDP改版前最后一次考试!请注意
随机推荐
Open3D 随机采样
最新工业界推荐系统数据集-召回排序模型原理、结构及代码实战整理分享
2022年自然语言处理校招社招实习必备知识点盘点分享
Line segment tree of knowledge
Summary of pytorch related knowledge points
Simple example of .reduce()
gpio子系统和pinctrl子系统(下)
概率模型校准
2022 Eye Care Products Exhibition, Beijing Eye Health Exhibition, Ophthalmology Exhibition, Myopia Correction Equipment Exhibition
高性能 MySQL(十二):分区表
2020.10.13开发日志
eladmin container deployment super detailed process
composer的使用记录
JS 实现千分位分隔符
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules
Programmer's Daily Life | Daily Fun
Jenkins的环境部署,(打包、发布、部署、自动化测试)
js实现数组去重的方式(7种)
2020.12.4日志
MT4/MQ4L入门到精通EA教程第二课-MQL语言常用函数(二)-账户信息常用功能函数