你知道多态的实现机制吗?是时候好好回顾一下了!

你知道多态的实现机制吗?是时候好好回顾一下了!

Java面向对象的三大特征是封装,继承和多态,这里面我觉得比较难理解的应当属于多态了,而且多态也是一个经常被考查的问题,因此需要熟练掌握多态相关知识!

庆哥: 小白,你知道什么是多态吗?

小白: 这可是一个经典的问题啊,面试的时候也经常会被问到吧!不过这个问题我经常性的忘记,感觉自己掌握的还不是很熟练,要我说,多态就是父类对象引用指向子类对象!

庆哥: 确实,多态的问题是非常经典的,而且也有人说像封装和继承其实都是为多态服务的,这样说倒是还可以,后面说到多态的实现条件就知道怎么回事了,一般在面试或者笔试的时候也会经常遇到“什么是多态的问题”,那么遇到这个问题我们该怎么回答呢?我们可以记住这样的说法:

多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译的时候并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪一个类的实例对象,该引用变量发出的方法调用到底是哪一个类中的方法,必须在程序运行期间才能决定!

小白: 哈哈,这个跟我的回答好像有点区别啊,那么以后回答“什么是多态的问题”是不是就可以像上面那样说?

庆哥: 是的,很多人之所以不能对多态的定义随口说出就是因为见过描述多态的太多种方式,但是自己却没有记住一种,导致遇到这样的问题,自己虽然知道什么是多态,却用自己的而语言组织不好!所以我们可以记住上面的回答!

小白:对的,要记住一种回答,不过我觉得,靠死记硬背也不行,关键要理解多态的含义,这样再记就容易多了!

庆哥:这话倒是不假,对于多态,要把握的就是编译时不确定,只有等到运行时才能确定,这样就会导致多种状态的效果,也就是多态啦!

编译时多态和运行时多态?

小白:我记得我好想听过编译时多态和运行时多态的说法,这又是什么啊,两者有什么区别吗?

庆哥:这个当然有啦,首先记住一点,我们平常所说的多态指的就是运行时多态,也就是父类引用指向的具体对象以及调用的具体方法必须等到运行时才能确定,而编译时多态是什么呢?编译时多态就是在编译时就已经确定了指向的具体对象,而且要调用的方法也是已知的,也就是这些操作在编译阶段就已经确定,不像运行时多态必须等到运行时阶段才能确定!另外编译时多态也叫作静态多态,运行时多态也就做动态多态!

小白:这个编译时多态有什么有什么例子啊,感觉有个例子会更加容易理解一点!

庆哥:当然有啦,其实方法的重载就是编译时多态的体现!

小白: 方法重载?是不是就是在一个类中存在这同名但是参数不同的方法?

庆哥: 是的,在一个类中,可以存在方法名相同但是参数不同的方法,这就叫做方法的重载,其实你看,一个叫相同的方法名因为参数的不同就产生了多种状态,但是这个当你调用的时候需要传入具体的参数来确定调用哪一个方法,这样的话在编译阶段机已经确定下来的,所以对于方法重载其实就是编译时多态!

小白: 哦哦,这下明白什么是编译时多态,那么我们接下来要讨论的多态指的就是运行时多态吧?

庆哥: 是的,接下来我们重点讨论的就是运行时多态了,也就是我们常说的多态!

小白: 对了,除了方法重载,我还听过方法重写和方法覆盖,这都有什么区别啊?

方法重载,重写和覆盖?

庆哥: 这也是一个好问题,想必有的时候会有人搞混这些概念,因为确实有点相似,那么从今天说过以后可要记住了,首先明确一点重写其实就是覆盖,这里要弄清楚的就是重载和重写的区别!

方法重载是相对于一个类之间的,也就是我们之前说的,方法名称相同,但是参数不同,它有以下特点:

  1. 在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样);
  2. 不能通过访问权限、返回类型、抛出的异常进行重载;
  3. 方法的异常类型和数目不会对重载造成影响;

方法重写则是在继承关系中出现,也就是在子类和父类之间,一般都是子类重写父类的方法,它也有如下特点:
1. 重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载.
2. 重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。
3. 重写的方法的返回值必须和被重写的方法的返回一致;
4. 重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;
5. 被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
6. 静态方法不能被重写为非静态的方法(会编译出错)。

这下你记住了吧!

小白: 我觉得我再也不会忘记了!

庆哥: 那么咱们继续说多态吧,首先要告诉你的一个很重要的结论就是实现多态必须满足的三个条件:

  1. 要有继承关系,也就是有子类和父类
  2. 子类要重写父类的方法
  3. 父类引用指向子类对象

记住了,这可是多态三要素啊,缺一不可,接下来我们就以实际的例子来看看多态的实现,我之前在网上看过很多讲多态的例子,发现使用动物来举例还是比较合适的,所以下面咱们也以动物为例进行举例说明。

首先定义一个父类Animal代表动物

class Animal {
    String name = "我是动物";
    static int age = 20;
    public void eat() {
        System.out.println("动物可以吃饭");
    }
    public static void sleep() {
        System.out.println("动物可以睡觉");
    }

    public void run(){
        System.out.println("动物可以奔跑");
    }
}

这是一个父类,动物都有名字年龄,都会吃饭睡觉奔跑,接下来定义一个子类,比如说小狗

class Dog extends Animal {
    String name = "小狗";
    static int age = 60;
    public void eat() {
        System.out.println("小狗可以吃饭");
    }
    public static void sleep() {
        System.out.println("小狗可以睡觉");
    }
    public void watchdog() {
        System.out.println("小狗可以看门");
    }

}

注意看这个类,Dog 是继承了Animal,在Animal中有的属性和方法,在Cat中有的也有,但是仔细看eat和sleep这两个方法在子类中被重写了,然后在子类中还有一个特有的方法是狗独有的也就是看门,子类调用方法和属性的时候会优先调取自身的属性和方法,如果自身没有的话就回去父类中找,那下面在主程序中调用如下

public static void main(String[] args) {
        Animal am = new Dog();
        am.eat();
        am.sleep();
        am.run();
        System.out.println(am.name);
        System.out.println(am.age);
    }

这里似乎跟之前讲继承的时候不一样,这里是新建一个Cat对象,但是是使用Animal这个父类接收的,为什么能这样接收,因为子类是继承自父类,父类也就包括子类,自然可以使用父类来接收子类,那么上述的输出结果是什么呢?

这里其实就是要搞清楚一个问题就是这个am调用的方法和属性到底是父类Animal中的还是子类Cat中的。

如果是这样

Dog am = new Dog ();

那毫无疑问,首先调用的都是子类中的方法和属性,如果子类中没有则去父类中查找,但是现在这个情况是不一样的

你会不会以为是这个结果

小狗在吃饭
小狗在睡觉
动物可以奔跑
小狗
60

但是,实际的结果是

enter image description here

和你想的不一样吧?

小白: 就是,怎么会是这样的结果呢?

庆哥: 这就是多态啊,我们看是不是满足了多态,首先Dog继承Animal,这是满足了继承关系 然后子类eat和sleep重写父类中的方法满足了子类重写父类的方法 接着是创建一个Dog对象然后使用Animal接收则满足了父类引用指向子类对象

这不就是多态嘛,至于为什么会得出上述结果,其实对于多态有它自己的一套规则:

  1. 如果是访问成员变量,编译的话就是看父类,运行同样是看父类
  2. 如果访问的方法,编译就看父类,运行则看子类
  3. 如果是静态方法,编译和运行都是看父类

总的来说就记住这么一句,多态中只有非静态的成员方法才是编译看父类,而运行时看子类,也即是实际调用的是子类方法

小白: 这个似乎有点小不理解啊,我需要好好思考一下!

庆哥: 确实,刚开始的时候我也是有点小懵,不过知道我在知乎上看见一个大神举了一个花木兰从军的例子,看完就清晰很多了

花木兰替父从军

大家都知道花木兰替父从军的例子,花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有自己的成员属性年龄,姓名,性别。花木兰也有这些属性,但是很明显二者的属性完全不一样。花弧有自己的非静态成员方法‘骑马杀敌’,同样花木兰也遗传了父亲一样的方法‘骑马杀敌’。花弧还有一个静态方法‘自我介绍’,每个人都可以问花弧姓甚名谁。同时花木兰还有一个自己特有的非静态成员方法‘涂脂抹粉’。但是,现在花木兰替父从军,女扮男装。这时候相当于父类的引用(花弧这个名字)指向了子类对象(花木兰这个人),那么在其他类(其他的人)中访问子类对象(花木兰这个人)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(60岁)、性别(男)。当访问子类对象(花木兰这个人)的非静态成员方法(骑马打仗)时,其实都是看到花木兰自己运用十八般武艺在骑马打仗。当访问花木兰的静态方法时(自我介绍),花木兰自己都是用她父亲的名字信息在向别人作自我介绍。并且这时候花木兰不能使用自己特有的成员方法‘涂脂抹粉’。—–多态中的向上转型

那么终于一将功成万骨枯,打仗旗开得胜了,花木兰告别了战争生活。有一天,遇到了自己心爱的男人,这时候爱情的力量将父类对象的引用(花弧这个名字)强制转换为子类对象本来的引用(花木兰这个名字),那么花木兰又从新成为了她自己,这时候她完全是她自己了。名字是花木兰,年龄是28,性别是女,打仗依然那样生猛女汉子,自我介绍则堂堂正正地告诉别人我叫花木兰。OMG!终于,终于可以使用自己特有的成员方法‘涂脂抹粉’了。从此,花木兰完全回到了替父从军前的那个花木兰了。并且和自己心爱的男人幸福的过完了一生。—–多态中的向下转型

原文链接:JAVA的多态用几句话能直观的解释一下吗? - 程序狗的回答 - 知乎
https://www.zhihu.com/question/30082151/answer/120520568

小白: 仰慕大神啊,这例子太好了,通过这个例子对多态的认识就更加清晰了,很感谢这位大神!

庆哥: 的确,这个例子挺好的,要感谢这位大神。我们回过头来再说多态,其实多态也有不好的一个地方就是满足多态的话是无法访问到子类中也有的方法和属性的,比如以上在小狗这个类中还有一个独有的方法就是看门的方法,但是通过am则是无法访问的,就像花木兰不能在军中涂脂抹粉一样,那么这样该怎么办,难道要重新创建一个Dog对象吗?实际上可以这样做

Dog dog= (Dog)am;
        dog.watchdog();

这就是向下转型,将父类强制转换成子类了,这里要注意的是,无论是向上转型还是向下转型,都是在多态的前提下!你看这样有什么好处呢?

小白: 这样的话好像就不用再新创建一个Dog对象了,节省空间。

庆哥: 是的,想象一下,我们这里只是定义了一个子类,当我们需要很多个子类的话,我们都可以通过这样的方式去创建子类对象,如果不使用多态的话,那就要一个个的创建子类的对象,既耗内存又麻烦!

说到这里我们要知道多态的好处是什么呢?总结一下就是

把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。

小白: 还是多态大法好啊!

完!

轻松一刻

看完文章那么累?来点轻松的吧!

enter image description here

这是一个神奇的脚!就问你行不行!

enter image description here

这肉有点小硬啊!

  • 17
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

庆哥Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值