java 面向对象三大特性之多态 万字详解(超详细)

举报
Cyan_RA9 发表于 2023/04/04 10:10:40 2023/04/04
【摘要】 Java 面向对象三大特性——多态篇!

目录

前言 : 

一、为什么需要多态 : 

        1.白璧微瑕 : 

        2.举栗(请甘雨,刻晴,钟离吃饭):

        3.代码 : 

        4.问题 : 

二、什么是多态 : 

        1.定义 : 

        2.多态的实现步骤(重要) : 

三、多态的使用 : 

        1.多态中成员方法的使用(重要) : 

                ①使用规则 : 

                ②代码演示 : 

                ③利用多态实现“旅行者请璃月人吃饭” : 

        2.多态中成员变量的使用 : 

                ①使用规则 : 

                ②代码演示 : 

四、多态的优点和缺点 : 

        1.益处 : 

        2.弊端 : (重点)

五、类型转换 :

        Δ前言 : 

        1.向上转型(自动): 

                ①含义 : 

                ②语法格式 : 

                ③代码演示 : 

        2.向下转型(强制): 

                ①含义 : 

                ②语法格式 : 

                ③代码演示 : 

        3.注意事项 : 

        4.instanceof关键字(重要):

                ①概述 : 

                ②用法 : 

                ③代码演示 : 

六、Java的动态绑定机制(重要): 

七、多态的应用 : 

八、抽象类,抽象方法,abstract关键字详解 : 

九、final关键字详解 : 

十、static关键字(类变量和类方法)详解 : 

十一、接口详解 : 

十二、Debug详解 : 

十三、面向对象总结 : 


前言 : 

        Hi,guys.这篇博文是Java面向对象三大特性篇的第三篇———多态篇。之前我们就一直强调,面向对象三大特性中,封装是继承的前提,继承是多态的前提。多态也是实际开发中运用最多,最广泛的一个特性!因此,多态的重要性不言而喻。本篇博文,内容包括但不限于多态的介绍多态的使用详解多态中的类型转化机制动态绑定机制详解抽象类详解final关键字、static关键字详解接口详解等等,感谢阅读

        这篇博文算是三大特性篇的最后一篇😎,之后up会将三大特性对应的三篇博客合并起来,并在此基础之上写一篇总结Java面向对象的十万+字博文,力争以最通俗易懂的方式让初学者快速入门并理解面向对象的灵魂。

        还有是CSDN貌似不欢迎长文章😂,因此up计划将这篇博文中部分内容比较长的知识点,单独再出篇博文,然后以插入链接的方式呈现给大家。谢谢理解,感谢阅读!

一、为什么需要多态 : 

        1.白璧微瑕 : 

        继承的使用,给我们带来了极大的便利,不但提高了代码的可维护性,而且提高了代码的复用性,使我们免于敲写过多繁冗重复的代码。然而,甘瓜苦蒂,天下物无全美我们承认,继承已经是个很🐂🖊的特性了。但在某些情况下,继承也会显出它的颓势

        2.举栗(请甘雨,刻晴,钟离吃饭):

        这不,马上海灯节就要到了。 旅行者答应要请刻晴,钟离,和甘雨一起去新月轩吃晚饭。为了让大家开心,旅行者提前了解了三个人喜爱的食物 : 刻晴喜欢吃金丝虾球,钟离喜欢吃豆腐,甘雨则喜欢吃清心。 现在让你用Java来描述这件事情,你怎么做?

        思路

        首先,刻晴,钟离和甘雨是三个不同的对象,因此我们需要分别定义三个类来模拟和描述刻晴,钟离,甘雨。同理,金丝虾球,杏仁豆腐和清心也是三个不同的对象,因此也需要分别定义三个类来模拟这三种食物。而三类人,三类食物均有共同属性;且刻晴,钟离和甘雨均有各自特有的行为;因此考虑使用封装和继承特性来实现

        假设刻晴,钟离和甘雨都是璃月人,那么我们就可以定义一个父类来表示璃月人,然后让表示刻晴,钟离和甘雨的子类去继承表示璃月人的父类。

        同理 : 假设金丝虾球,豆腐,和清心都属于料理,那么我们就可以定义一个父类来表示料理,然后让表示金丝虾球,豆腐和清心的子类去继承表示料理的父类。

        根据假设,先定义一个Liyue_people类,然后再分别定义Keqing类,Zhongli类和Ganyu类,并让它们继承Liyue_people类。同时,在Keqing类,Zhongli类和Ganyu类中定义它们的特有方法。其中 : 

        Keqing类特有方法——天街巡游 : sky_street_cruise()
        Zhongli类特有方法——
天星 : sky_stars()
        Ganyu类特有方法——
降众天华 : descend_to_heaven()

        同样根据假设,先定义Cooking类,然后再分别定义Shrimp_balls类,Bean_curd类和Qingxin类,并让它们继承Cooking类。注意 : 每种料理都要有名字,营养,和味道三个属性。假设新月轩在海灯节期间会稿活动,部分菜肴有优惠,我们需要在Cooking类中定义一个cooking_info()方法,来打印出料理的基本信息(菜品名,营养,风味等)。

        定义Traveler类(旅行者类),并在Traveler类中分别定义方法名为my_treat的三个重载方法,第一个方法需要传入一个刻晴类对象和一个金丝虾球对象;第二个方法需要传入一个钟离类对象和一个豆腐类对象;第三个方法则需要传入一个甘雨类对象和一个清心类对象。利用方法重载可以实现请不同对象吃饭的需求。

        最后定义Treat类进行测试。在Treat类先创建好刻晴,钟离和甘雨的吃饭对象,以及金丝虾球,豆腐和清心的料理对象。之后再定义旅行者类对象,并调用旅行者类中的my_treat方法(),传入对象参数即可成功请客吃饭,过一个完美的海灯节。

        关系图(如下) : 

image.png

        3.代码 : 

        为了简洁,up直接将Keqing类,Zhongli类和Ganyu类都写在Liyue_people类的源文件中,将Shrimp_balls类,Bean_curd类和Qingxin类都写在Cooking类的源文件中。

        Liyue_people类,Keqing类,Zhongli类和Ganyu类代码如下 :        

package knowledge.polymorphism.introduction;
//父类 : 璃月人类
public class Liyue_people {
    //成员变量
    private String name;
    private int age;
    private String gender;
    //构造器
    public Liyue_people() {
    }
    public Liyue_people(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    //setter,getter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
}
//子类 : 刻晴类
class Keqing extends Liyue_people {
    public Keqing(String name, int age, String gender) {
        super(name, age, gender);
    }
    //刻晴————天街巡游
    public void sky_street_cruise() {
        System.out.println("剑光如我,斩尽芜杂!");
    }
}
//子类 : 钟离类
class Zhongli extends Liyue_people {
    public Zhongli(String name, int age, String gender) {
        super(name, age, gender);
    }
    //钟离————天星
    public void sky_stars() {
        System.out.println("天动万象!");
    }
}
//子类 : 甘雨类
class Ganyu extends Liyue_people {
    public Ganyu(String name, int age, String gender) {
        super(name, age, gender);
    }
    //甘雨————降众天华
    public void descend_to_heaven() {
        System.out.println("为了岩王帝君!");
    }
}

image.gif

       Cooking类,Shrimp_balls类,Bean_curd类和Qingxin类代码如下 :                         

package knowledge.polymorphism.introduction;
//父类 : 料理类
public class Cooking {
    //成员变量
    private String food_name;
    private String nutrition;
    private String flavor;
    //构造器
    public Cooking() {
    }
    public Cooking(String food_name, String nutrition, String flavor) {
        this.food_name = food_name;
        this.nutrition = nutrition;
        this.flavor = flavor;
    }
    //setter,getter方法
    public String getFood_name() {
        return food_name;
    }
    public void setFood_name(String food_name) {
        this.food_name = food_name;
    }
    public String getNutrition() {
        return nutrition;
    }
    public void setNutrition(String nutrition) {
        this.nutrition = nutrition;
    }
    public String getFlavor() {
        return flavor;
    }
    public void setFlavor(String flavor) {
        this.flavor = flavor;
    }
    //打印出菜肴信息的方法
    public void cooking_info() {
        System.out.print("\t菜品: " + this.food_name);
        System.out.print("\t\t营养:" + this.nutrition);
        System.out.println("\t\t风味:" + this.flavor);
    }
}
//子类 : 金丝虾球类
class Shrimp_balls extends Cooking {
    public Shrimp_balls(String food_name, String nutrition, String flavor) {
        super(food_name, nutrition, flavor);
    }
}
//子类 : 豆腐类
class Bean_curd extends Cooking {
    public Bean_curd(String food_name, String nutrition, String flavor) {
        super(food_name, nutrition, flavor);
    }
}
//子类 : 清心类
class Qingxin extends Cooking {
    public Qingxin(String food_name, String nutrition, String flavor) {
        super(food_name, nutrition, flavor);
    }
}

image.gif

        Traveler类代码如下 :  

package knowledge.polymorphism.introduction;
//旅行者类
public class Traveler {
    //反正是个哑巴,要什么属性
    //打工人行为 : 
    //1.请刻晴吃金丝虾球
    public void my_treat(Keqing keqing, Shrimp_balls shrimpBalls) {
        System.out.println("海灯节,👴" + "请" + keqing.getName() + "吃" + shrimpBalls.getFood_name());
    }
    //2.请钟离吃杏仁豆腐
    public void my_treat(Zhongli zhongli, Bean_curd bean_curd) {
        System.out.println("海灯节,👴" + "请" + zhongli.getName() + "吃" + bean_curd.getFood_name());
    }
    //3.请甘雨吃清心
    public void my_treat(Ganyu ganyu, Qingxin qingxin) {
        System.out.println("海灯节,👴" + "请" + ganyu.getName() + "吃" + qingxin.getFood_name());
    }
}

image.gif

        Treat类代码如下

package knowledge.polymorphism.introduction;
public class Treat {
    public static void main(String[] args) {
    //1.创建要吃饭的角色对象
        //创建刻晴对象
        Keqing keqing = new Keqing("刻晴", 18, "女");
        //创建钟离对象
        Zhongli zhongli = new Zhongli("摩拉克斯", 6000, "男");
        //创建甘雨对象
        Ganyu ganyu = new Ganyu("王小美", 3000, "女");
    //2.创建被吃的食物对象
        //创建金丝虾球对象
        Shrimp_balls shrimp_balls = new Shrimp_balls("金丝虾球", "蛋白质是🐂🥩的六倍!", "鲜香味美");
        //创建杏仁豆腐对象
        Bean_curd bean_curd = new Bean_curd("杏仁豆腐", "富含微生物", "清甜爽口");
        //创建清心对象
        Qingxin qingxin = new Qingxin("清心", "妹说就是0卡", "苦的一批");
    //3.创建旅行者对象
        Traveler traveler = new Traveler();
    //4.打扫干净屋子再请客
        System.out.println("===============欢迎来到新月轩===============");
        System.out.println("---------------以下是本店的海灯节热门菜肴 : ");
        shrimp_balls.cooking_info();
        bean_curd.cooking_info();
        qingxin.cooking_info();
        System.out.println("\n旅行者:\"哎呀我去,整挺好,不用挑了!👴全都要!\"\n");
        traveler.my_treat(keqing, shrimp_balls);    // 传入刻晴对象和金丝虾球对象
        traveler.my_treat(zhongli, bean_curd);      // 传入钟离对象和杏仁豆腐对象
        traveler.my_treat(ganyu, qingxin);          // 传入甘雨对象和清心对象
    }
}

image.gif

        Treat类代码如下 : 

image.png

        4.问题 : 

        不知道大家发现没有,旅行者每请一个角色吃饭,都得重新定义一个新的重载方法,现在才请了三五个角色吃饭,可能还觉得没那么麻烦。但是,提瓦特大陆上的角色成百上千啊。到时候难道要定义100个方法,1000个方法?既然都是请客,为什么不能在一个方法中实现呢。就拿方才我们举得栗子来打个比方,既然刻晴,钟离,和甘雨都是璃月人,为什么我们不直接定义一个方法来请璃月人吃饭?这样不就省事儿多了!

        这时候我们开始认识到继承特性的一些弊端 : 只有继承的程度,无法实现诸如“旅行者直接请璃月人吃饭”的需求。那怎么办?害!铺垫一大堆废话,不就为了讲咱的多态么!这不就来了!

image.png

二、什么是多态 : 

        1.定义 : 

                所谓多态,其实字面意思就是多种状态,没那么复杂。在Java中,方法和对象,都可以体现出多态。

                ①对于方法,多态表现在方法重载和方法重写上。

                方法重载 : 同一行为,形参不同则表现形式不同

                方法重写 : 同一行为,调用者不同则表现形式不同

                ②对于对象,多态表现在同一对象在不同情况下表现出不同的状态或行为。对象不仅仅是体现多态,更重要的应用多态。因此,对象的多态就是Java多态的核心

        2.多态的实现步骤(重要) : 

                有继承(或实现)关系。(继承是多态的前提。"实现"指实现类或接口,后面我们会讲到)

                有方法重写

                父类引用指向子类对象

                对于第三条,要特别补充一些内容。我们先来举个栗子吧。

                eg : 假设有Cat类继承了Animal类,如下所示 : 

public class Animal {
    public void eat() {}
}
class Cat extends Animal {
    public void eat() {
        System.out.println("🐱喜欢吃🥩");
    }
}

image.gif

                那么父类引用指向子类对象即 

Animal animal = new Cat();

image.gif

                Δ解释 :                

                在上面这行代码中,等号左面的animal是一个Animal类型的引用变量,但是,这个Animal类型的引用所指向的对象,即堆空间中真正的对象,却是一个Cat类型。这就叫做父类引用指向子类对象。有什么用呢?别着急,下面多态的使用就会正式介绍到。这里我们要说一点别的内容 : 

                在多态中,我们将等号左边的这个引用变量的类型,称为编译类型。而将等号右边的——在堆空间中new出来的——真正的对象的类型,称为运行类型。其中,编译类型决定了引用变量可以调用哪些属性和行为;而运行类型则是在程序运行过程中jvm实际使用的类型

                比方说,现在我们通过animal对象来调用eat() 方法,因为编译类型是Animal类,因此在编译时,编译器要判断Animal类有没有eat() 方法。诶,有eat() 方法,那就可以调用。但在实际调用时,jvm会优先使用运行类型Cat类中的eat() 方法,因此打印结果为“🐱喜欢吃🥩”。

三、多态的使用 : 

        1.多态中成员方法的使用(重要) : 

                ①使用规则 : 

                编译看左(即左边的编译类型有没有这个成员,这决定了我们能不能调用目标成员)

                运行看右(即右边的运行类型中的该成员,才是运行中实际使用的成员)             

                ②代码演示 : 

                up就以Animal类为父类,Cat类为子类,TestMethod类为测试类(置于同一包下)。我们给Animal类定义一些动物共同的属性,例如species_name(物种名),average_life(平均寿命),living_habit(生活习性)等;定义一些动物共同的行为,例如eat(吃),sleep(睡)等。

                然后我们在Cat类中重写eat()方法和sleep()方法,并在TestMethod类中使用多态,根据多态中成员方法的使用规则 : 如果我们以Animal类的引用去调用eat()或者sleep()方法,实际运行中,优先使用的一定是子类Cat类中的eat()和sleep()方法

                Animal类代码如下

package knowledge.polymorphism.about_method;
public class Animal {
    //成员变量
    private String species_name;    //物种名
    private int average_life;       //平均寿命
    private String living_habit;    //生活习性
    //构造器
    public Animal() {
    }
    public Animal(String species_name, int average_life, String living_habit) {
        this.species_name = species_name;
        this.average_life = average_life;
        this.living_habit = living_habit;
    }
    //setter, getter方法
    public String getSpecies_name() {
        return species_name;
    }
    public void setSpecies_name(String species_name) {
        this.species_name = species_name;
    }
    public int getAverage_life() {
        return average_life;
    }
    public void setAverage_life(int average_life) {
        this.average_life = average_life;
    }
    public String getLiving_habit() {
        return living_habit;
    }
    public void setLiving_habit(String living_habit) {
        this.living_habit = living_habit;
    }
    //成员方法
    public void eat() {
        System.out.println("这是Animal类的eat()方法,需要被重写捏😋");
    }
    public void sleep() {
        System.out.println("这是Animal类的sleep()方法,需要被重写捏🤗");
    }
}
/*
    按下insert键后,输入会变成改写模式,需要再按下一次才能取消。
 */

image.gif

                Cat类代码如下 : 

package knowledge.polymorphism.about_method;
public class Cat extends Animal {
    //构造器
    public Cat() {
    }
    public Cat(String species_name, int average_life, String living_habit) {
        super(species_name, average_life, living_habit);
    }
    //重写父类的eat()方法和sleep()方法
    @Override
    public void eat() {
        System.out.println("🐱🐱喜欢吃🥩捏");
    }
    @Override
    public void sleep() {
        System.out.println("🐱🐱一般蜷缩着睡觉,且不喜欢在光线强烈的地方睡");
    }
        
    //定义成员方法用于打印出猫的基本信息
    public void cat_info() {
        System.out.println("猫的物种学名:" + this.getSpecies_name() +
                "\t猫的平均寿命 : " + this.getAverage_life() +
                "\t猫的生活习性 : " + this.getLiving_habit());
    }
}

image.gif

                TestMethod类代码如下

package knowledge.polymorphism.about_method;
public class TestMethod {
    public static void main(String[] args) {
        //父类引用指向子类对象
        Animal animal = new Cat();
        animal.setSpecies_name("Felinae");      //Felinae是猫的拉丁学名
        animal.setAverage_life(15);
        animal.setLiving_habit("喜欢独自生活,喜欢干净,喜欢肉类");
        System.out.println("简单介绍一下猫这一物种 : ");
        ((Cat)animal).cat_info();               //强转类型,后面会讲到。
        System.out.println("------------------------------------------");
        //调用成员方法
        animal.eat();
        animal.sleep();
    }
}

image.gif

                运行结果 :  

image.png         

果然不出我们所料,实际运行的eat() 方法和sleep() 方法是Cat类中重写过后的方法

                为了更进一步验证我们的结论,现在我们把Cat类中的两个重写方法都给注释掉,如下图所示 :image.png

                此时,若通过animal引用再次调用eat() 方法和 sleep() 方法,因为子类重写方法被注释,因此会调用父类的eat() 和 sleep() 方法,运行结果如下

image.png            

通过演示,相信大家对于多态中成员方法的使用已有了一定的了解。但这时候可能有p小将(personable小将,指风度翩翩的人)出来说了 :  你就用了Cat一个子类,也好意思说自己是多态😅,我咋看不出来捏😅?          

                不愧是p小将,一针见血的😓!没错,前面我们说过——对象的多态才是Java多态的核心!同一对象在不同情况下表现出不同的状态或行为称为对象的多态。现在我们使用父类引用仅调用了Cat一个运行类型,没有体现多种状态

                好滴,于是我们在本包下新建一个Dog类,Dog类也继承Animal类。此时,继承关系如下图

image.png

                🆗,我们先讲Cat类中重写的eat() 方法和sleep方法恢复, 如下图所示 : 

image.png

                然后在Dog类中对eat() 和sleep() 方法进行重写,并且也定义一个dog_info() 方法,用于打印出狗的基本信息Animal类和Cat类代码保持不变

                Dog类代码如下

package knowledge.polymorphism.about_method;
public class Dog extends Animal {
    //构造器
    public Dog() {
    }
    public Dog(String species_name, int average_life, String living_habit) {
        super(species_name, average_life, living_habit);
    }
    //重写父类eat() 方法 和 sleep() 方法
    @Override
    public void eat() {
        System.out.println("🐕也喜欢吃🥩捏");
    }
    @Override
    public void sleep() {
        System.out.println("🐕想睡就睡,活在当下");
    }
    
    //定义成员方法用于打印出狗的基本信息
    public void dog_info() {
        System.out.println("狗的物种学名:" + this.getSpecies_name() +
                "\t狗的平均寿命 : " + this.getAverage_life() +
                "\t狗的生活习性 : " + this.getLiving_habit());
    }
}

image.gif

                在TestMethod类中,当指向Cat对象的animal引用变量调用完成员方法后,我们改变animal引用的指向,使它指向一个Dog类对象,并去调用Dog类中重写的方法

                TestMethod类代码如下

package knowledge.polymorphism.about_method;
public class TestMethod {
    public static void main(String[] args) {
        //1.父类引用指向子类对象
        Animal animal = new Cat();
        animal.setSpecies_name("Felinae");      //Felinae是猫的拉丁学名
        animal.setAverage_life(15);
        animal.setLiving_habit("喜欢独自生活,喜欢干净,喜欢肉类");
        System.out.println("简单介绍一下猫这一物种 : ");
        ((Cat)animal).cat_info();               //强转类型,后面会讲到。
        System.out.println("--------------------------------");
            //调用成员方法
        animal.eat();
        animal.sleep();
        System.out.println("=======================================================");
        //2.改变animal引用变量的指向,使其指向一个Dog类对象
        animal = new Dog();
        animal.setSpecies_name("Canis lupus familiaris");
        animal.setAverage_life(12);
        animal.setLiving_habit("喜欢啃骨头,喜欢嗅闻东西,喜欢摇尾巴");
        System.out.println("简单介绍一下狗这一物种 : ");
        ((Dog)animal).dog_info();               //强制向下转型,后面会讲到。
        System.out.println("--------------------------------");
            //调用成员方法
        animal.eat();
        animal.sleep();
    }
}

image.gif

                运行结果 :

image.png

                通过运行结果我们可以看出 : animal引用变量指向Cat类对象时,运行的eat() 和 sleep() 方法就是Cat类中的方法,而当我们改变animal引用的指向,使其指向Dog类对象时,运行的方法就变成了Dog类中的方法。

                什么概念?

                同一引用变量,调用相同的方法,却因为指向的对象不同而产生了不同的结果。而我们平时习惯于将指向对象的引用变量当作对象的简称。那么,此处animal对象体现的不就是多态么。

                ③利用多态实现“旅行者请璃月人吃饭” : 

                有了多态后,前面的“旅行者请吃饭”问题便可以迎刃而解了 :

                我们只需要在Traveler类(旅行者类)中定义一个my_treat() 方法,但与之前不一样的是,形参类型定义为Liyue_people类和Cooking类,即角色对象和料理对象各自的父类。如下所示  :

public void my_treat(Liyue_people liyue_people, Cooking cooking) {
        System.out.println("海灯节,👴" + "请" + liyue_people.getName() + "吃" + 
                cooking.getFood_name());
    }

image.gif

                注意,这时候, 因为Liyue_people类是Keqing类,Zhongli类和Ganyu类的父类,因此,不管你传入这三个对象中的哪一个,都会形成相当于" Liyue_people liyue_people = new 子类对象;"的形式,即父类引用指向子类对象——多态的形式。此时,传入的是哪个对象,就调用哪个对象的方法。Cooking类和它的子类也是同理。

                璃月人类,刻晴类,钟离类,甘雨类以及料理类,金丝虾球类,豆腐类,清心类代码均不变Traveler类代码如下

package knowledge.polymorphism.introduction;
public class Traveler {
    //利用多态,一个方法解决请客吃饭问题
    public void my_treat(Liyue_people liyue_people, Cooking cooking) {
        System.out.println("海灯节,👴" + "请" + liyue_people.getName() + "吃" +
                cooking.getFood_name());
    }
}

image.gif

                Treat类代码也不变,不过注意,此时Treat类中使用的my_treat() 方法已变化
                运行结果 : 

image.png

                利用多态,将父类类型作为形参,一个方法照样实现了我们的需求😎!

        2.多态中成员变量的使用 : 

                ①使用规则 : 

                编译看左(即左边的编译类型有没有这个成员,这决定了我们能不能调用目标成员)

                运行看左多态关系中,成员变量是不涉及重写的

                ②代码演示 : 

                1)up以Fruit类(水果类)为父类,子类Apple类(苹果类)和Grape类(葡萄类)分别继承Fruit类,以TestField类为测试类

                2)我们在父类Fruit类中定义一些水果公有的属性(不用private修饰),例如species_name(物种学名), sweetness(平均甜度), shape(形态特征)。并分别在Apple类和Grape类中定义它们自己的这三个同名属性。

                3)最后在测试类中,分别建立Fruit类和Apple类,Grape类间的多态关系,并通过Fruit类型的引用去调用这三个属性并输出。根据多态关系中成员变量的使用规则,输出的三个属性应该每次都以Fruit类中的为基准。

                4)为使代码简洁,up将两个子类都写在Fruit类的源文件中。Fruit类,Apple类,Grape类代码如下 :         

package knowledge.polymorphism.about_field;
//父类 : Fruit类
public class Fruit {
    //成员变量
    String species_name = "物种学名噢";
    double sweetness = 9.5;             //水果的平均甜度大致为8~10左右。
    String shape = "水果的外观形状捏";
    //构造器
    public Fruit() {
    }
    public Fruit(String species_name, double sweetness, String shape) {
        this.species_name = species_name;
        this.sweetness = sweetness;
        this.shape = shape;
    }
}
//子类 : 葡萄类
class Grape extends Fruit {
    //成员变量
    String species_name = "Vitis_vinifera_L";       //葡萄的拉丁学名
    double sweetness = 16.5;                        //葡萄的平均甜度
    String shape = "长得和葡萄差不多";               //葡萄的形态特征
    //构造器
    public Grape() {super();}
    public Grape(String species_name, double sweetness, String shape) {
        super(species_name, sweetness, shape);
    }
}
//子类 : 苹果类
class Apple extends Fruit {
    //成员变量
    String species_name = "Malus_pumila_Mill";       //苹果的拉丁学名
    double sweetness = 8.5;                          //苹果的平均甜度
    String shape = "长得和苹果一样";                  //苹果的形态特征
    //构造器
    public Apple() {super();}
    public Apple(String species_name, double sweetness, String shape) {
        super(species_name, sweetness, shape);
    }
}

image.gif

                TestField类代码如下 :  

package knowledge.polymorphism.about_field;
public class TestField {
    public static void main(String[] args) {
        //多态
        Fruit fruit = new Apple();
        System.out.println("苹果的物种学名:" + fruit.species_name);
        System.out.println("苹果的平均甜度:" + fruit.sweetness);
        System.out.println("苹果的形态特性:" + fruit.shape);
        System.out.println("---------------------------------");
        //改变fruit引用变量的指向,使其指向Grape类对象。
        fruit = new Grape();
        System.out.println("葡萄的物种学名:" + fruit.species_name);
        System.out.println("葡萄的平均甜度:" + fruit.sweetness);
        System.out.println("葡萄的形态特性:" + fruit.shape);
    }
}

image.gif

                运行结果

image.png

        果然不出我们所料,不管你fruit引用指向的是哪个子类水果对象,直接调用成员变量,永远优先使用Fruit类本身的属性。这时候p小将(personable小将,指风度翩翩的人)又要出来bb问了 : 🤬tnnd(太难弄哒),好不容易知道多态有个能解决继承缺陷的用处,怎么现在又用不了子类的属性了😅?那你写这么一堆干嘛,博主你搁这儿搞笑呢🤗?

        p小将你先别急。以上演示只是要说明 : 多态关系中,直接使用成员变量的规则是编译看左,运行看左即使子类定义了与父类同名的属性,但本质上那也是子类特有的属性了。我们之前一直在说,封装是继承的前提,继承是多态的前提。了解了封装和继承就知道,实际开发中通过对象直接调用属性的情况是不常见的,不符合封装的要求。我们编写的类应该尽量靠近JavaBean标准。那我们就不能在多态的前提下使用子类的属性了吗?当然不是😎,这不,多态的优缺点总结就来了。

四、多态的优点和缺点 : 

        1.益处 : 

                可维护性 : 基于继承关系,只需要维护父类代码,提高了代码的复用性,降低了维护工作的工作量。

                可拓展性 : 把不同的子类对象都当作父类看待,屏蔽了不同子类对象间的差异,做出了通用的代码,派生类的功能可以被基类的方法或引用变量所调用,以适应不同的需求,实现了向后兼容

        2.弊端 : (重点

                父类引用不能直接使用子类的特有成员。

                这也很好解释 : 前面在讲多态的实现步骤时我们说过——编译类型决定了引用变量可以调用哪些属性和行为;而运行类型则是在程序运行过程中jvm实际使用的类型。父类引用,说明编译类型是父类类型,以父类类型编译当然只能使用父类中存在的成员。当然,这里所说的成员包括成员变量和成员方法,这二者在多态关系中的使用略有不同:使用的成员变量必须是在父类中存在的,且成员变量不涉及重写;使用的成员方法也必须是在父类中存在的,但是如果子类重写了父类方法,优先调用子类重写的方法。

                从jvm内存的角度解释就是 : .java文件经"javac.exe"命令编译后会生成.class的字节码文件,当代码中需要使用到某个类,该类的字节码文件就会加载进入方法区,而jvm识别并执行的就是字节码文件。因此,编译类型为父类类型,那jvm识别的当然是这个类的字节码文件,子类的特有成员,根本就不在这个字节码文件里面,jvm当然不认识。而对于子类重写的方法,父类字节码文件中包含有被重写方法的信息,jvm能够识别。而因为父类引用真正指向的是堆空间中的子类类型对象,所以此时会优先从堆空间中的子类对象里面找,使用子类重写后的方法,若子类没有重写,根据继承机制,则使用父类的该方法。

五、类型转换 :

        Δ前言 : 

                当需要使用到子类特有功能,比如要使用子类重写后的方法,或者要使用子类的特有成员,这时候就需要进行类型转换。类型转换分为向上类型转换向下类型转换两种。其中,向下转型是一个重点知识。        

        1.向上转型(自动): 

                ①含义 : 

                即子类类型转换成父类类型(父类引用指向子类对象)。向上转型是自动进行的,我们的多态就是一种向上转型。eg : Animal animal = new Cat();

                ②语法格式 : 

        父类类型  父类引用变量  =  new  子类类型();

                ③代码演示 : 

                这个说实话😅没啥好演示的。因为我们前面举过的所有多态的例子,都属于向上转型。  请继续看向下转型,那才是重点。

        2.向下转型(强制): 

                ①含义 : 

                即父类类型转换成子类类型。为什么叫强制类型转化呢? 因为向下转型不会自动发生,需要人为强转。并且,向下转型改变了编译类型,而编译类型决定了我们可以使用哪些成员,当编译类型由父类类型转换为子类类型后,我们当然可以使用子类的特有成员了。因此,我们说要使用子类的特有功能,靠的就是向下转型

                ②语法格式 : 

        子类类型  子类引用变量  =  (子类类型) 父类引用变量。

        或者 直接使用 " ((子类类型)父类引用变量) " 的方式来调用子类特有成员,而不去做接收。

                 什么意思呢?给大家举个栗子 : 

                eg : Animal animal = new Cat();      

                      Cat cat = new (Cat) animal;  后一条语句将Animal类型的引用变量animal转换成了子类Cat类类型的引用变量cat,相当于animal和cat两个引用指向了同一Cat对象,但堆空间中实际存在的Cat对象本身并没有变化

                ③代码演示 : 

                演示Ⅰ:

                我们就先来解决刚刚在演示多态中成员变量的使用时,Fruit类引用无法直接调用Apple类和Grape类成员变量的问题。

                Fruit类,Apple类,Grape类代码均不变,大家可以往上翻翻看,就在上面“多态中成员变量的使用”这一部分。当然,懒得去翻也没关系,重在演示向下类型转换。我们只需要在TestField类输出语句中的成员变量前使用强制类型转换,即可成功输出子类特有成员。

                TestField类代码如下 : 

package knowledge.polymorphism.about_field;
public class TestField {
    public static void main(String[] args) {
        //多态
        Fruit fruit = new Apple();
        System.out.println("苹果的物种学名:" + ((Apple)fruit).species_name);
        System.out.println("苹果的平均甜度:" + ((Apple)fruit).sweetness);
        System.out.println("苹果的形态特性:" + ((Apple)fruit).shape);
        System.out.println("---------------------------------");
        //改变fruit引用变量的指向,使其指向Grape类对象。
        fruit = new Grape();
        System.out.println("葡萄的物种学名:" + ((Grape)fruit).species_name);
        System.out.println("葡萄的平均甜度:" + ((Grape)fruit).sweetness);
        System.out.println("葡萄的形态特性:" + ((Grape)fruit).shape);
    }
}

image.gif

                运行结果 : 

image.png

                演示Ⅱ : 

                不知道大家还记不记得在“多态中的成员方法的使用”的演示中,我们在Cat类和Dog类中分别定义了cat_info()方法和dog_info()方法,用于打印出Cat对象和Dog对象的基本信息,其实up在当时已用了强制向下转型,大家不用再往上翻了😂,我给大家放个截图吧,如下 : 

image.png

                当时我们将Animal类型的引用变量animal分别向下转型为了Cat类引用和Dog类引用。以调用它们特有的方法,运行结果如下 : 

image.png

                演示Ⅲ : 

                不知道大家是否还记得😂,在开篇多态的引用中,我们举了旅行者请刻晴,钟离,甘雨吃饭的栗子, 当时up还分别在刻晴类,钟离类和甘雨类中定义了它们特有的成员方法,但是没有再测试类中调用!其实就是为了等现在演示呢😆。

                Keqing类,Zhongli类,Ganyu类各自的特有成员方法如下,我还是直接放截图吧 : 

image.png

                Treat类(请客吃饭的测试类)代码如下 : 

package knowledge.polymorphism.introduction;
public class Treat {
    public static void main(String[] args) {
        //在多态关系下,调用Keqing类,Zhongli类,Ganyu类的特有成员方法
            //刻晴
        Liyue_people lp1 = new Keqing("刻晴", 18, "女");
        System.out.println("刻晴特有的行为是天街巡游:");
        Keqing kq = (Keqing) lp1;
        kq.sky_street_cruise();
        System.out.println("------------------------------------------------");
            //钟离
        lp1 = new Zhongli("摩拉克斯", 6000, "男");
        System.out.println("钟离特有的行为是天星:");
        Zhongli zl = (Zhongli) lp1;
        zl.sky_stars();
        System.out.println("------------------------------------------------");
            //甘雨
        lp1 = new Ganyu("王小美", 3000, "女");
        System.out.println("甘雨特有的行为是降众天华:");
        Ganyu gy = (Ganyu) lp1;
        gy.descend_to_heaven();
    }
}

image.gif

                运行结果 : 

image.png     

        3.注意事项 : 

                只有在继承关系的继承上才可以进行类型转换,否则会报出ClassCastException(类型转换异常)。如下图所示 : 

image.png

                在对引用变量进行向下转型之前,必须保证该引用变量指向的——堆空间中真正的对象的类型就是目标类型。(重要

                比如,Animal animal = new Cat(); 现在animal引用指向了一个Cat类型的对象,如果要对animal引用进行强制向下转型,就只能转换成Cat类型的引用;如果想转换成其他类型的引用,就需要先改变animal引用的指向,使其指向目标类型的对象。否则,同样会报出类型转换异常

                ③那么,我们在进行向下转型之前,怎么就能知道——当前引用指向的对象是不是我们想要的目标类型的对象呢?

                答案是 : 在进行强制类型转化之前,使用instanceof关键字来进行判断。

        4.instanceof关键字(重要):

                ①概述 : 

                instanceof关键字,可以判断指定对象是否为指定的类型,并返回一个boolean类型的值,常与if条件语句一起使用。instanceof关键字在多态的应用——多态参数和多态数组中也会频繁使用,当然,多态的应用我们在后面会讲到的。

                ②用法 : 

        对象名  instanceof  数据类型             

                说明 : 

                1° 前面的“对象名”即引用变量。

                2° 实际参与判断的是引用变量指向的——堆空间中真正的对象。

                ③代码演示 : 

                up以Person类为父类,让Teacher类和Student类分别继承Person类,最后在Test_instanceof类中进行测试。为了代码简洁,up将Teacher类和Student类写在了Person类的源文件中。

                我们要在测试类干什么?

                在测试类中,我们先建立Person——Teacher类之间的多态,然后利用instanceof关键字来判断Person引用指向的对象是否为Teacher类型,以及是否为Student类型,如果是Teacher类型,就利用强制向下转型去调用Teacher类中的特有方法;接着,改变Person类型引用的指向,使其指向Student类型的对象,利用instanceof关键字对当前引用指向的对象重新进行判断,以确定对象类型,若确定当前对象是Student类型,同理,利用强转去调用Student类特有的成员方法。

                Person类,Teacher类,Student类代码如下 : 

package knowledge.polymorphism.ceof_demo;
public class Person {                //父类
    /*
        因为仅演示instanceof关键字,因此父类暂时不以JavaBean标准来敲,
        当然,等讲到多态的应用时,我们还会用到instanceof关键字,到时候
        代码肯定会向JavaBean标准靠拢。这里大伙儿就先将就看看⑧😋。
     */
}
class Teacher extends Person {        //子类
    //定义Teacher类的特有成员方法
    public void teach() {
        System.out.println("教书育人,重在德行,有德无德,大相径庭。");
    }
}
class Student extends Person {        //子类
    //定义Student类的特有成员方法
    public void what_time() {
        System.out.println("嘿,哥们儿,几点了?还有几分钟下课?");
    }
}

image.gif

                Test_instanceof类代码如下 :

package knowledge.polymorphism.ceof_demo;
public class Test_instanceof {
    public static void main(String[] args) {
        //1.多态先整上。
        Person person = new Teacher();
            //判断当前对象的类型
        boolean boolean_1 = (person instanceof Teacher);
        System.out.println("当前对象是Teacher类型吗: " + boolean_1);
        boolean boolean_2 = (person instanceof Student);
        System.out.println("当前对象是Student类型吗: " + boolean_2);
        System.out.println("----------------------------------------------");
            //确定对象类型后,进行强制向下转换,调用该类特有的成员方法
        if (person instanceof Teacher) {
            System.out.println("当前对象指向的对象是Teacher类型,可以将引用强转为Teacher类型");
            ((Teacher) person).teach();
        } else if (person instanceof Student) {
            System.out.println("当前对象指向的对象是Student类型,可以将引用强转为Student类型");
            ((Student) person).what_time();
        }
        System.out.println("\n===========================================================\n");
        //2.改变person引用的指向,并重复上一轮的步骤。
            //判断当前对象的类型
        person = new Student();
        boolean boolean_3 = (person instanceof Teacher);
        System.out.println("当前对象是Teacher类型吗: " + boolean_3);
        boolean boolean_4 = (person instanceof Student);
        System.out.println("当前对象是Student类型吗: " + boolean_4);
        System.out.println("----------------------------------------------");
            //确定对象类型后,进行强制向下转换,调用该类特有的成员方法
        if (person instanceof Teacher) {
            System.out.println("当前对象指向的对象是Teacher类型,可以将引用强转为Teacher类型");
            ((Teacher) person).teach();
        } else if (person instanceof Student) {
            System.out.println("当前对象指向的对象是Student类型,可以将引用强转为Student类型");
            ((Student) person).what_time();
        }
    }
}

image.gif

                注意测试类中的代码,我们是先以一个boolean类型做接收,以证明使用instanceof关键字返回的是boolean类型,后又搭配 if 条件语句来调用子类特有的方法。其实学了static关键字之后,我们就可以把判断部分的代码单独写在main方法外的一个静态方法中,不用这么繁冗写两遍了。

                运行结果

image.png

六、Java的动态绑定机制(重要): 

                限于博客字数已过20000,从动态绑定机制开始,up将把知识点的讲解另外放到一篇博文中,然后把博客的链接给大家放在这里,以增强大家的阅读体验感谢理解,博文链接如下

https://blog.csdn.net/TYRA9/article/details/128880552?spm=1001.2014.3001.5501

七、多态的应用 : 

                多态的应用常见有多态数组和多态参数博文链接如下

https://blog.csdn.net/TYRA9/article/details/128880552?spm=1001.2014.3001.5501

八、抽象类,抽象方法,abstract关键字详解 : 

                抽象类详解博文链接如下 :

https://blog.csdn.net/TYRA9/article/details/129137305?spm=1001.2014.3001.5501

九、final关键字详解 : 

                final关键字详解博文链接如下 :

https://blog.csdn.net/TYRA9/article/details/129097055?spm=1001.2014.3001.5501

十、static关键字(类变量和类方法)详解 : 

                static关键字详解博文链接如下 :

https://blog.csdn.net/TYRA9/article/details/129039405?spm=1001.2014.3001.5501

十一、接口详解 : 

https://blog.csdn.net/TYRA9/article/details/129174384?spm=1001.2014.3001.5501

十二、Debug详解 : 

https://blog.csdn.net/TYRA9/article/details/128884528?spm=1001.2014.3001.5501

十三、多态篇总结 : 

        🆗,以上就是面向对象篇章——多态篇的全部内容了。讲真的,多态篇的内容真是巨多😂,要是up不放链接的话,可能这一篇的字数就过10万字了。回顾一下,我们从一个有趣的例子引入了多态的必要性;接着介绍了Java中非常非常重要的动态绑定机制。接着又详细介绍了多态的使用;然后是抽象类,final关键字,static关键字,和接口这四大块对多态的补充和应用。最后,up又给大家补充了一篇介绍Java Dubug的博文。其实Debug中我也是主要演示了一些最基本的逻辑语句和动绑机制,按道理讲Debug更应该放到继承篇。但是我觉得讲面向对象讲得好好的,突然插入这么个玩意儿,多少有些抽象,于是最后还是把Debug放到了多态篇。好滴,之后up会出一篇总结性质的博文,把Java面向对象三大特性——封装、继承,多态三大节合并为一章,并且做一些对面向对象基础的引入和内容的完善。我们不见不散😎!感谢阅读!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。