二继承与多态——第四节、多态

2022-05-15  本文已影响0人  杜艳_66c4

文章目录
1、 从吃烤山药重新认识多态
2、 多态前提条件【重点】
3、 多态的体现
4、 多态动态绑定与静态绑定
4、1.静态绑定(前期绑定)
4、2.动态绑定(后期绑定)
4、3.静态、动态绑定本质区别
4、4.静态、动态绑定在程序中运行区别
5、 多态特性的虚方法(virtual)
6、 重载属于多态吗?
7、 向上转型
8、 向下转型
8、1. instanceof的使用
9、 向上向下转型再次分析【加餐不加价】
10、 多态与构造器之间的微妙
11、 多态的优点
12、 分析开篇的九个问题
13、 最后我们一起来正式分析那九个题
————————————————
版权声明:本文为CSDN博主「宜春」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44543508/article/details/102409146

image.png

父类引用指向子类对象
一个对象拥有多种形态,这就是对象的多态性。
定义:多态指同一行为(方法),具有多个不同表现形式

package Polymorphic;


     class  Matcher{
        public void matcherSpeak(){
            System.out.println("想吃烤山药?");
        }
    }

     class HappyMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("开心的妈妈说:吃,吃大块的,一火车够吗");
        }
    }

     class SadMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("不开心的妈妈说:吃你个憨皮,看我回家扎不扎你就完事了");
        }
    }

     class VeryHappyMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("异常开心的妈妈说:买买买,烤山药咱全买了,顺便把大爷也买回家,天天给你表演激光雨(大爷懵逼中)");
        }
    }

    public class UnderstandPolymorphic{
        public static void main(String[] args) {
            Matcher m = new HappyMother();
            m.matcherSpeak();

            m = new SadMother();
            m.matcherSpeak();

            m = new VeryHappyMother();
            m.matcherSpeak();

        }
    }
运行结果:

开心的妈妈说:吃,吃大块的,一火车够吗
不开心的妈妈说:吃你个憨皮,看我回家扎不扎你就完事了
异常开心的妈妈说:买买买,烤山药咱全买了,顺便把大爷也买回家,天天给你表演激光雨(大爷懵逼中)

看这一段代码,妈妈听到小明想吃烤山药这同一行为,表现出不同的表现形式,这就是多态。

1、 多态前提条件【重点】

如果多态不能满足以下三个前提条件,那还玩犊子的多态【构不成多态,缺一不可】

1、继承或者实现【二选一】
2、方法的重写【意义体现:不重写,无意义】
子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
3、父类引用指向子类对象(也可以说向上转型)【体现在格式上】

2、 多态的体现:父类引用指向子类对象

格式:
父类名称 对象名 = new 子类名称();
或者
接口名称 对象名 = new 实现类名称();

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误 ,如果有,执行的是子类重写后的方法,也就是向上转型时, 子类单独定义的方法丢失问题。编译报错

你可能会说子类中都没有这些个方法啊,何来执行子类重写后的方法一说?它好像是去父类中找该方法了。事实上,子类中是有这些方法的,这个方法继承自父类,只不过没有覆盖该方法,所以没有在子类中明确写出来而已,看起来像是调用了父类中的方法,实际上调用的还是子类中的

3、多态中成员变量的使用特点

访问成员变量的两种方式
1、直接通过对象名称访问,看等号左边是谁,优先用谁,没有向上找, 成员变量不能覆盖重写,方法可以
2、间接通过成员方法访问,看该方法属于谁,优先用谁,没有向上找:
子类覆盖重写了父类方法,就是子类,没有覆盖重写就是父类。
Fu

package duotai.demo1;

/**
 * created by apple on 2020/6/7
 */
public class Fu {
    int num = 10;
    public void show(){
        System.out.println(num);
    }
}

Zi

package duotai.demo1;

/**
 * created by apple on 2020/6/7
 */
public class Zi extends Fu{
    int num = 20;

    @Override  
//没有覆盖重写,就是父的num,覆盖重写了,就是子的num
    public void show() {
        System.out.println(num);
    }
}

实现类:

package duotai.demo1;

/**
 * created by apple on 2020/6/7
 */
public class Demo1 {
    public static void main(String[] args) {
        Fu obj = new Zi();
        System.out.println(obj.num);  //父亲,10,, 左边是谁,优先用谁,若没有向上找
        System.out.println("=====");
        obj.show(); //子类没有覆盖重写,就是父,访问成员方法,看方法属于谁,用谁,么有则向上找
        //子类有覆盖重写,就是子,
    }
}

输出:

10
=====
20

4、多态中成员方法的使用特点

看new的是谁,则优先使用谁,没有则向上找。
一句话:编译看左边,运行看右边。
左边没有的方法,编译会出错。
运行看new的谁。
成员变量:编译看左边,运行看左边。 除非子类覆盖重写。
编译看左的意思:左边是fu,fu中没有methodzi方法,所以编译报错。 obj.methodZi(); 错误写法。

对比:成员变量,编译运行都看左,成员方法,编译看左,运行看右

你可能会说子类中都没有这些个方法啊,何来执行子类重写后的方法一说?它好像是去父类中找该方法了。事实上,子类中是有这些方法的,这个方法继承自父类,只不过没有覆盖该方法,所以没有在子类中明确写出来而已,看起来像是调用了父类中的方法,实际上调用的还是子类中的。同学继承方面的知识该补补了。

5、使用多态的好处:

image.png

无论右边new出的时候换成哪个子类对象,左边调用方法都不会发生变化
程序编写的更简单,并有良好的扩展

6、对象的向上转型和向下转型

1、向上转型:多态本身是子类类型向父类类型向上转换的过程,其中,这个过程是默认的。你可以把这个过程理解为基本类型的小类型转大类型自动转换,不需要强制转换。 当父类引用指向一个子类对象时,便是向上转型。
弊端:对象一旦向上转型为父类, 就无法调用子类原来特有的内容
解决方案:用对象的向下转型还原

2、向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。同样可以把这个过程理解为基本类型的自动转换,大类型转小类型需要强制转换。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,向下转使用格式:
Father father = new Son();
子类类型 变量名 = (子类类型) 父类变量名; 如:Son s =(Son) father;

不知道你们有没有发现,向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型),当然,向下转型还是有它的意义所在,下面就讲解向下转型的意义。
到这里,我们讲解一下为什么要向下转型?上面已经讲到过当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。

所以,想要调用子类特有的方法,必须做向下转型。

向上转型和向下转型
package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山猪?");
    }

}

class Boy extends Matcher {
    public void eatKaoYang(){
        System.out.println("妈妈,我想吃烤山猪");
    }
}

class Girl extends Matcher {
    public void eatKaoYang(){
        System.out.println("妈妈,我想吃烤山猪2333");
    }
}

public class Test {
    public static void main(String[] args) {

        Matcher g = new Girl();//向上转型编译通过

        Boy x = (Boy)g;//向下转型

        x.eatKaoYang();//编译通过,但运行报ClassCastException

    }
    
 运行结果:  运行报ClassCastException

}

这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Girl类型对象,运行时,当然不能转换成Boy对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验。如果转换成girl类型的向下转型,则不会报错,输出:妈妈,我想吃烤山猪2333

7、instanceof 关键字

如何才能知道一个父类的引用,本来是什么类?
格式:
变量名 instanceof 数据类型
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例

instanceof 的使用
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
所以,转换前,最好使用instanceof做一个判断,否则发生异常,类转换异常


instanceof

DEMO

package it.xinyecom.enhance.duotai;

/**
 * @author duyanyan
 * @date 2021/9/10 14:22
 */

class MaDemo{
    public void eat(){
        System.out.println("父类方法吃东西");
    }
}

class Boy extends MaDemo{
    public void eatMa(){
        System.out.println("boy吃东西");
    }
        }
class Girl extends MaDemo{
    public void eatMa(){
        System.out.println("girl吃东西");
    }
}

public class TestGirl {
    public static void main(String[] args) {
        MaDemo maDemo = new Girl(); //向上转型
       if (maDemo instanceof Boy){
            Boy boy = (Boy)maDemo; //向下转型
           boy.eatMa();
        }

       if (maDemo instanceof Girl){
           Girl girl = (Girl)maDemo;
           girl.eatMa();
       }
    }
}
输出:girl吃东西

8、 向上向下转型再次分析【加餐不加价】

看完之后是不是还是不够清晰向上向下转型?多态转型问题其实并不复杂,只要记住一句话:父类引用指向子类对象。那什么叫父类引用指向子类对象?看下面例子吧

有两个类,Father 是父类,Son 类继承自 Father。
第 1 个例子:

//  f1 引用指向一个Son对象
Father f1 = new Son();   // 这就叫 upcasting (向上转型),子类类型默认向父类类型向上转换的过程
// f1 还是指向 Son对象
Son s1 = (Son)f1;   // 这就叫 downcasting (向下转型)

第 2 个例子:

// f2现在指向father对象
Father f2 = new Father();
Son s2 = (Son)f2;       // 出错,子类引用不能指向父类对象

你或许会问,第1个例子中:Son s1 = (Son)f1; 为什么是正确的呢。很简单因为 f1 指向一个子类对象,Father f1 = new Son(); 子类 s1 引用当然可以指向子类对象了。

而 f2 被传给了一个 Father 对象,Father f2 = new Father(); 子类 s2 引用不能指向父类对象。

9、 多态与构造器之间的微妙

package it.xinyecom.enhance.duotai;

/**
 * @author duyanyan
 * @date 2021/3/12 10:33
 */
public class EatGzq {
   EatGzq(){
       System.out.println("吃烤山药之前————");
       eat();
       System.out.println("吃烤山药之后(熊孩子懵逼中)....");
   }

   public void eat() {
       System.out.println("7岁半就喜欢吃烤山药");
   }

}

class Kao extends EatGzq{
    private String Weight = "110";
    public  Kao(String Weight){
        this.Weight = Weight;
        System.out.println("熊孩子的体重:" + this.Weight);
    }

    public void eat(){ // 子类覆盖父类方法
        System.out.println("熊孩子吃烤山药之前的体重是:" + this.Weight);
    }

    //Main方法
    public static void main(String[] args) {
        EatGzq k = new Kao("250斤");

    }
}
输出结果:
吃烤山药之前————
熊孩子吃烤山药之前的体重是:null
吃烤山药之后(熊孩子懵逼中)....
熊孩子的体重:250斤

原因其实很简单,因为在创建子类对象时 ,会先去调用父类的构造器,而父类构造器中有调用了被子类覆盖的多态方法,由于父类并不清楚子类对象中的属性值是什么,(先初始化父类的时候还没开始初始化子类),于是把string类型的属性暂时初始化为默认值null ,
然后调用子类的构造器,这时子类构造器已经初始weight属性,所以子类构造器知道熊孩子体重是250

10、多态的优点

在实际开发中父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展 性与便利。
下面程序不使用多态,代码如下:

package it.xinyecom.enhance.duotai;

/**
 * @author duyanyan
 * @date 2021/9/10 15:09
 * 不使用多态
 */

class  Animal{
    public void eat(){
        System.out.println("动物eat");
    }
}

class Cat {
    //方法重写
    public void eat(){
        System.out.println("猫吃东西");
    }
    public void call(){
        System.out.println("猫叫");
    }
}

class DOg {
    //方法重写
    public void eat(){
        System.out.println("狗吃东西");
    }
    public void call(){
        System.out.println("狗叫");
    }
}
//z针对动物操作的工具类
class AnimalTool{
    private AnimalTool(){}  //把工具类的构造方法私有,防止别人创建该类的对象
    //调用猫的功能
    public static void catLife(Cat cat){//工具类,方法就写成static .然后直接在测试类,工具类名.方法名使用
        cat.eat();
        cat.call();
    }

    //调用狗的功能
    public static void dogLife(DOg dog){//工具类,方法就写成static .然后直接在测试类,工具类名.方法名使用
        dog.eat();
        dog.call();
    }

}

public class TestNoDuotai {
    public static void main(String[] args) {
        Cat cat  = new Cat();
        AnimalTool.catLife(cat);

        DOg dOg = new DOg();
        AnimalTool.dogLife(dOg);
    }
}
输出结果
猫吃东西
猫叫
狗吃东西
狗叫

这里只写了两只动物,如果再来一种动物猪,则需要定义个猪类,提供猪的两个方法,再到工具类中添加对应的XXLife方法,这三步都是必须要做的,而且每多一种动物就需要在工具类中添加一种一个对应的XXLife方法,这样维护起来就很麻烦了,毕竟动物种类成千上万!崩溃吧,没事多态来拯救你,如下使用多态代码:

package it.xinyecom.enhance.duotai;

/**
 * @author duyanyan
 * @date 2021/9/10 15:21
 * 使用多态
 */
//父类
class AnimalD{
    public void eat(){
        System.out.println("eat");
    }
    public void call(){
        System.out.println("call");
    }
}

//猫,
class CatD extends AnimalD{
    //方法重写
    public void eat(){
        System.out.println("猫猫eat");
    }
    public void call(){
        System.out.println("猫猫call");
    }
}

//狗,
class DogD extends AnimalD{
    //方法重写
    public void eat(){
        System.out.println("狗eat");
    }
    public void call(){
        System.out.println("狗call");
    }
}
class AnimalToold{
 private AnimalToold(){}
    //针对动物的工具类
    public static void AnimalDTool(AnimalD animalD){
        animalD.call();
        animalD.eat();
    }
}


public class TestDuotai {
    public static void main(String[] args) {
        CatD catD = new CatD();
        AnimalToold.AnimalDTool(catD);

        DogD dogD = new DogD();
        AnimalToold.AnimalDTool(dogD);
    }
}
输出:
猫猫call
猫猫eat
狗call
狗eat

注意: 上面动物类都继承了AnimalD父类
这个时候再分析,如果再来一种动物猪,则需要定义个猪类,提供猪的两个方法,再继承Animal父类,这个时候就不需要在工具类中添加对应的XxLife方法,只写一个AnimalDTool方法即可,而且每多一种动物都不需要在工具类中添加对应的XxLife方法,这样维护起来就很乐观了。

由于多态特性的支持,AnimalDTool方法的Animal类型,是Cat和Dog的父类类型,父类类型接收子类对象,当 然可以把Cat对象和Dog对象传递给方法。 当eat和call方法执行时,多态规定,执行的是子类重写的方法,那么效果自然与Animal的子类中的eat、call方法一致, 所以animalLife完全可以替代以上两方法。 不仅仅是替代,在扩展性方面,无论之后再多的子类出现,我们都不需要编写XxLife方法了,直接使用 animalLife就可以完成。 所以,多态的好处,体现在可以使程序编写的更简单,并有良好的扩展。

上一篇下一篇

猜你喜欢

热点阅读