• Java之多态


    一、多态前言

    1.为什么要使用多态

    Java中使用多态的主要目的是提高代码的可重用性和扩展性,使得代码更加灵活和易于维护。通过多态,我们可以将不同的对象看做是同一种类型,从而使得我们可以使用同一种接口来操作这些对象,而不必关心具体的实现细节。

    2.多态概念

    当父类的引用所指向的子类对象引用指向的对象不一样时。调用重写的方法,所表现出来的行为是不一样的,我们把这种思想叫做多态。上面所说的可能大家会觉得有点抽象,看到后面就懂了。
    多态的基础是动态绑定,所以要了解多态前提我们还要了解动态绑定。
    要想实现动态绑定,需要满足以上几个条件:
    1.要发生向上转型
    2.要发生重写
    3.使用父类对象的引用去调用重写方法
    完成了这三部分,就会发生动态绑定,而在这里,出现了重写以及向上转型这些概念。所以我们得先了解它们才能去了解动态绑定。进而了解多态。

    二、重写

    1.重写的概念

    重写 (override) :也称为覆盖。将父类的方法重新在子类中使用。 返回值和形参都不能改变 即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

    方法重写的规则:
    1.子类在重写父类的方法时,必须与父类方法原型一致:即返回值、方法名、参数列表要完全一致
    2.被重写的方法的访问修饰限定符在子类中要大于等于父类的。
    3.父类中被static或private或final修饰的方法以及构造方法都不能被重写。 
    4.在子类中重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验。

    2.重写的作用

    对于已经投入使用的类,尽量不要进行修改。最好的方式是:重新定义一个新的类,来重复利用其中共性的内容,并且添加或者改动新的内容。
    例如:若干年前的手机,只能打电话,发短信,来电显示只能显示号码,而今天的手机在来电显示的时候,不仅仅可以显示号码,还可以显示头像,地区等。在这个过程当中,我们不应该在原来老的类上进行修改,因为原来的 类,可能还在有用户使用 ,正确做法是: 新建一个新手机的类,对来电显示这个方法重写就好了,这样就达到了我 们当今的需求了

    三、向上转型

    向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。
    语法格式:父类类型 对象名
    = new 子类类型 ()
    Animal animal = new Cat ( );
    我们对以上代码进行实质化分析,以上的代码其实是省略化了,见以下代码

    Dog dog = new Dog();
     Animal animal = dog;//该代码发生了向上转换,将Dog对象转换为Animal类型

    通过向上转型后,就可以父类对象名来访问子类的方法了。使用animal.eat();这语句来访问
    这个语句发生了动态绑定(在编译过程中调用的其实是父类的eat,但是在运行时换为调用子类的eat了)故实现了 创建一个子类对象,将其当成父类对象来使用。见以下代码

    class Animal {

    void sound() {

    System.out.println("Animal makes a sound");

    }

    }

    class Dog extends Animal {

     void sound() {

    System.out.println("Dog barks");

    }

     void fetch() {

    System.out.println("Dog fetches a ball");

    }

    }

    public class Main {

    public static void main(String[] args) {

    Dog dog = new Dog(); // 创建Dog对象

     Animal animal = dog; // 向上转型,将Dog对象转换为Animal类型

     animal.sound();//调用子类的覆盖方法

     // animal.fetch(); // 编译错误,因为Animal类中没有fetch方法

    }

    }

    // 输出: Dog barks

    通过以上代码发现一个问题不能调用到子类特有的方法(因为编译时调用的是父类的方法),我们可以通过向下转型来调用到子类特有的方法(后面介绍)
    静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
    动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。

    向上转型 使用场景
    1. 直接赋值
    2. 方法传参
    3. 方法返回
    见以下代码

    public class TestAnimal {

    // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象

    public static void eatFood(Animal a){ //因为主方法的原因使用静态方法

    a.eat();  //方法传参向上转型

    }

    // 3. 作返回值:返回任意子类对象的实例

    public static Animal buyAnimal(String var){

    return new Dog();

    }

    public static void main(String[] args) {

    Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象

    Dog dog = new Dog("小七", 1);

    animal.eat();  //直接赋值向上转型

    eatFood(cat);  

    eatFood(dog);

    Animal animal = buyAnimal("狗");  

    animal = buyAnimal("猫");

    animal.eat();//方法返回向上转型

    }

    }

    向上转型的优点:让代码实现更简单灵活。
    向上转型的缺陷:不能调用到子类特有的方法。

    四、多态的实现

    多态具体点就是去完成某个行为时,当不同的对象去完成时会产生出不同的状态。代码如下:

    class Animal {

        public void eat(){

            System.out.println( "吃饭");

        }

    }

     class Cat extends Animal{

        @Override //注解

        public void eat(){

            System.out.println("吃鱼~~~");

        }

    }

     class Dog extends Animal {

        @Override

        public void eat(){

            System.out.println("吃骨头~~~");

        }

    }

    public class TestAnimal {

        public static void eat(Animal a){

            a.eat(); //两次调用该方法,但是结果却不一样

        }

        public static void main(String[] args) {

            Cat cat = new Cat();

            Dog dog = new Dog();

            eat(cat);

            eat(dog);

        }

    }

    //输出结果

    吃鱼~~~
    吃骨头~~~

    此时在上述代码中当父类的引用所指向的子类对象引用指向的对象不一样时。调用重写的方法(eat),所表现出来的行为是不一样的(输出结果不一样),我们把它叫做多态。

    五、向下转型

    将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
    语法格式:子类类型 对象名
    = (强制转换)父类对象名

     Dog myDog = (Dog) animal;

    那么以上代码为什么要强制类型转换呢?向上转型可以不用,因为是从小范围向大范围的转换。(可以类比整型里面的强制转换),我们现在提出一个问题:什么时候都可以向下转型吗?
    答案是不,在Java中,向下转型(将父类引用转换为子类引用)一般需要先进行向上转型
    见以下代码

    class Animal {

        void sound() {

            System.out.println("Animal的sound");

        }

        void sun() {

            System.out.println("Animal特有的sun");

        }

    }

    class Dog extends Animal {

        void sound() {

            System.out.println("Dog的sound");

        }

        void fetch() {

            System.out.println("Dog特有的fetches ");

        }

    }

    public class Mainn {

        public static void main(String[] args) {

            Animal animal = new Dog(); // 向上转型

            Dog myDog = (Dog) animal; // 向下转型

            myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法

            myDog.fetch(); // 输出: Dog fetches a ball,调用子类特有的方法

            // 调用父类的方法

            myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法

            myDog.sun();

        }

    }

    如果上面的代码没有 Animal animal = new Dog();,向下转型将报错,同时注意必须确保父类引用所指向的对象确实是子类的实例。如果父类引用所指向的对象不是子类的实例,那么即使进行了向上转型,向下转型也是不安全的:见以下代码

    class Parent {}
    class Child extends Parent {}
    class AnotherChild extends Parent {}

    public class Main {
    public static void main(String[] args) {
    Parent parent = new AnotherChild(); // 向上转型
     // 这里如果尝试向下转型为Child,编译器将会报错
     // Child child = (Child) parent;//不安全的向下转型
    }
    }

    //为了演示方便,这个代码是不完整的

    因此,向下转型之前,你需要确保父类引用所指向的对象确实是你要转型的子类的实例。这通常通过instanceof操作符来检查:用来判断parent是否为Child的实例,若是,返回true,否则返回false


    if (parent instanceof Child) {
        Child child = (Child) parent; //
    安全的向下转型
    } else {
       ......                         // 不能转换为Child
    }

    我们最后思考一个问题:向上转型的缺陷是不能调用到子类特有的方法,那么向下转型可以调用父类特有的方法吗?是可以的,同时向下转型后不会影响向上转型的操作。见以下代码

    class Animal {

        void sound() {

            System.out.println("Animal的sound");

        }

        void sun() {

            System.out.println("Animal特有的sun");

        }

    }

    class Dog extends Animal {

        void sound() {

            System.out.println("Dog的sound");

        }

        void fetch() {

            System.out.println("Dog特有的fetches ");

        }

    }

    public class Mainn {

        public static void main(String[] args) {

            Animal animal = new Dog(); // 向上转型

            Dog myDog = (Dog) animal; // 向下转型

            myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法

            myDog.fetch(); // 输出: Dog fetches a ball,调用子类特有的方法

        

            myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法

            myDog.sun();   //观察到向下转型过程中可以调用父类的特有的方法

            animal.sun();   //观察到向下转型后不会影响向上转型的操作

            animal.sound();

        }

    }

    //输出结果

    Dog的sound
    Dog特有的fetches 
    Dog的sound
    Animal特有的sun
    Animal特有的sun
    Dog的sound

    六、多态的优缺点

    待更新~~~

    七、避免在构造方法中调用重写的方法

  • 相关阅读:
    设计模式之观察者(Observer)
    git入门与基本
    电商平台搭建流程是怎样的_不懂编程怎么搭建_OctShop
    Android OpenGL ES踩坑记录
    2022 lineCTF WEB复现WriteUp
    中标麒麟国产服务器安装MinIO报错不能读取该二进制文件解决方案
    使用CLion和gdb server debug haproxy
    如何把JavaWeb项目部署到服务器
    mysql高阶语句
    【Linux】进程等待
  • 原文地址:https://blog.csdn.net/2301_79404878/article/details/138162569