栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

多态 polymorphism

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多态 polymorphism

多态 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 接口

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1038931.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号