《张三求职日记》基础篇--(9) 工作这么久,多态搞懂了吗?

342 阅读10分钟

写在前面:这个标题一放出来,可能很多人就开始疑惑了,啥,多态?这不是我刚入门时学的东西吗?还有人会问这个吗?然而,这个确实是基础,也确实会问的人也不多,但是这个知识点里面名堂很多,坑也很多,并且我们工作中总是会有意无意中接触到它,所以还是让我们能够彻底的把它搞明白吧。有关于张三的背景介绍参考: 张三背景介绍

张三又开始了一次面试

面试官:先来个自我介绍吧。

张三:!@#¥%……&*。

面试官:OOP知道吗?

张三思考了一会,心想,啥?OOP?好熟悉啊,让我想一想,呃……,哦,面向对象程序设计,唉天天接触的都是AOP什么的,突然说个OOP好不适应,直接说面向对象编程不好吗?

张三:……面像对象编程啊。

面试官:那面向对象的三大特性了解吗?

张三心想,这面试官是来搞笑的吧,我都工作这么久了还问这么基础的问题(事实上有些人甚至回答不出这么基础的问题)。

张三(脱口而出的):继承、封装、和多态啊。(一般我们会把封装放在第一个位置)

面试官:举个例子呗。

张三:继承啊,就是子类可以继承父类的属性和方法,比如我可以定义一个Human类,在这个类里面定义一些属性和方法,比如人都有两个眼睛两个耳朵一个嘴巴,都能跑能吃饭能睡觉,那我它的子类Man或者Woman只要继承了Man类就可以完全继承这些属性和方法。封装在不同人眼中的定义不完全相同,但想表达的意思基本都是我把对象的属性和实现细节都隐藏起来,对外提供访问的方法,最广泛的使用就是定义bean使用private定义属性,提供get、set方法。多态比较抽象,那我就直接举例子吧,我可以定义一个动物类,它有牙齿,有四条腿,有一根尾巴,那么我们可以定义很多不同的子类继承它,可以是猫是狗是老虎。有些时候你不在乎这个动物究竟是啥,只要能跑会吃肉就行了,那么你可以定义一个接收的对象是动物类,而具体的实现是猫是老虎看具体的情况而定了,反正我要的能满足就行。

面试官:那多态的优点是什么呢?

张三脸一黑,还有这样问的?只能瞎吹了。

张三:还按原来那个例子,我需要个动物或者宠物,你给我来一条猫,我觉得可以。哪天我不想要了,你给我换了只狗,我也觉得没问题,甚至要是有只宠物猪给我,我也能接受。这就是多态的好处,子类可以替换,并且替换不影响原有的逻辑,毕竟我想要的是宠物嘛,这些子类都ok的啊。

有些时候虽然张三的这样回答我们比较好理解,但是如果能回答出一些专业的名词往往更能加分,我们可以记住几个词:可替换性、可扩充性、接口性、灵活性、简化性。

面试官:在多态里,父类有的方法子类能调吗?

张三心想,这和多态有什么关系......

张三:子类会继承父类的方法啊,如果不重写的话,那就和父类方法一样,重写的话就是子类自己的方法了。

面试官:其实我想问的是,就比如 Animal animal = new Cat(); ,Animal定义了eat方法,Cat重写了eat方法,那么animal调用eat方法调用的是谁的eat方法呢?

张三脑袋飞速思考,我重写了eat方法当然是想调用自己的eat方法,但是接收的是父类,我突然有点不太确定了。

张三:(支支吾吾的)Cat的方法吧。

面试官:那我如果是Animal animal = new Animal();,然后把animal强转成Cat,再调用eat方法,调用的是谁的呢?

张三凌乱了,没试过,还能这样玩?

张三:我...不知道

面试官:没事,xxx你了解吗(问到下一个知识点,后面的省略)

面试官:这次的面试就先到这里,后面会有我们的HR联系你。

张三:好的……

---

封装和继承我就不过多介绍了,默认大家是完全不会有问题的,只是如果问起来回答的漂不漂亮的问题。而多态我们也不多探讨概念上的问题,毕竟如果连多态都没有一定的概念,其实是不适和敲代码的。但是,多态这个知识点上面,还是有很多让人意想不到的门门道道的,虽然有可能在开发过程中大家并不一定会接触到这些特殊情况,但是如果能把这些情况都理解清楚,思考问题的方式还是能对大家有很大的帮助的。

首先我们来分析第一种最简单也最好理解的情况:

先定义一个animal类,我们只定义两个属性,颜色是白色,腿数是4,定义一个方法,吃。

然后我们定义一只白猫,这只白猫继承于Animal,仅仅只是多了一个play的方法。

那么根据我们掌握的继承知识,白猫继承于Animal类,所以它就能拿到Animal类的所有属性和方法,那么我们去颜色会取到白色,取腿数会取到4,调用eat方法会调用父类的方法,那如果调用play方法呢?对不起由于父类并没有此方法,直接调用animal.play()会出现编译告警,需要我们强转为WhiteCat类才能调用play()方法。这里是第一个注意点哦,平常开发时,大家尽可能的不要做这样的向下强转,如果做了这样的操作,那么我们使用多态不仅没有给我们带来帮助,还给我们带来了代码上的风险与麻烦。最终的结果我们很容易想的到,父类的属性和方法都能够正常输出,子类的方法在强转后也能够正常输出。

那么最简单的情况我们了解了,那稍微普遍的用法我们再来看看吧,我们定义了一个黑猫类,我们重写了它的eat()方法,也重新定义了color属性:

多态最终的目的还是希望通过不同的子类能有不同的实现,所以说子类的重写方法还是比较关键的,如果子类重写了父类中的方法,那么多态调用时,调用的应该是子类的方法,这是我们都清楚逻辑,那属性呢?于是我们看看下面的运行结果:

通过结果我们可以看到,输出的方法调用的是子类的方法,但是输出的属性却是父类的属性,这怎么解释呢?

那我们在回过头来看看多态概念中的一个必要条件:父类引用指向子类对象。--概念还是很重要的哦,没事就蹦出几句专业术语,你真的明不明白先不说,起码让别人觉得很专业。

这个概念如果搞清楚了,那运行的结果我们就能够解释清楚了。首先,我们的引用是什么类型的呢?

是父类型Animal的,也就是说,不管子类定义了多少属性和方法,我们能访问的只有父类中定义的属性和方法(若想访问子类的属性和方法,参考最开始的例子强转)。那么我们知道了上面的概念,不是说访问的都是父类定义的属性和方法吗?为什么属性访问的是父类的,方法访问的是子类的呢?

那就是重写的概念了,如果子类中重写了父类中的方法,那么在多态调用时,将会调用子类中的方法,而属性不能被重写。所以我们看到的方法是子类的,属性是父类的。

分析了这么多,可能有人要说逻辑好乱啊,能总结一下吗?

那总结起来就是,多态调用属性时,只能访问父类中的属性,多态调用方法时,首先判断调用的方法是否是父类中有的方法,如果没有,则会报编译错误,如果有,则判断子类是否重写了该方法,如果重写了,则调用子类的方法。

假设上面的逻辑我们都能弄明白了,按我们再来看看一个奇怪的情况,我们定义一个橘猫类,它和黑猫类一样,重新定义了属性和方法,只是在方法中用到了定义的属性,那我们来看看在多态调用时,方法中的属性用的是谁的吧:

我们可以看到,虽然我们多态只能访问父类的属性,但是调用的子类的方法使用的是子类的属性。这可以解释吗?解释个啥啊,子类的方法当然访问的是自己的属性啊,与多不多态没关系啊。那么子类如果不想使用自己的属性,而去使用父类的属性,可以吗?当然可以,使用super关键字访问就行了,不过除了坑爹的面试题,应该不会有人这样写代码吧。

不知道你们上面的那些都理解了没有,没有的话就自己多看多试几次应该就能理解了。现在我们再来看看最容易让人猜不到结果的情况:首先我们在父类动物类里加上一个构造器,里面输入这样一段话:

然后我们定义一个斑点狗类,这个类里面也重新定义了颜色属性,还有自己的构造方法,注意在构造方法里把父类的属性赋值给了自己的属性,在最后又重写了eat()方法,那么这样一个类,我们重载访问它的属性和方法,会输出什么呢?注意这里我们定义了构造方法,要把构造方法也考虑进去哦。

思考时间,3,2,1

输出结果是这样的

没错,结果就是这样的,先输出父类的构造方法,再输出子类的构造方法,子类的构造方法访问的是子类定义的属性,子类构造方法可以重新赋值属性,构造方法中赋值后子类的属性就会永久改变了。

确实,这样的结果一般人还是很难猜的到的,只有大家多练多试才能够彻底搞清楚多态时访问属性和方法的关系。特别是涉及到构造方法时,没往这方面思考过的话,太容易搞错了。

小小一个多态,名堂还真是不少啊,有没有一种感觉,看这篇文章之前,你以为自己是懂多态的,看完之后,又感觉自己不懂了。想要自己真的能彻底弄清楚自己的这里面的各种情况,还是需要自己多想几个例子多去验证一下。如果有知识点是你知道自己不知道的,那其实问题还不大,如果你都不知道自己不知道,那会是一件很可怕的事情。

张三在这次的面试中收获颇丰,我们下周见。