01-面向对象(多态-概念)
接下来说一下面向对象的第三个特征:多态。
何为多态呢?
其实就是某些事物具有的多种表现形态。
比如说,人分为男人和女人,就是人的两种表现形态。
动物包括猫、狗.etc
猫 x=new 猫();
动物 x=new 猫();
这两种定可以方法都可以哦。
猫既具有猫的特征,也有动物的特征。
来了一只猫,我们可以说这只猫猫好可爱,也可以说这个小动物好可爱。
但是有个前提,猫得是动物的一种。
以往呢,我们是某一种类型对应这种类型的实例,我们可以理解:猫 x=new 猫();
而现在呢,类与类产生关系之后,我们发现,原来实体可以具备其他类型,这说明这个事物有多种存在形态。
那么多态呢,我们强调的是在Java上对象中的体现。其实不在对象中也可以体现,比如说函数。重载和覆盖,就是函数多态性的体现。
接下来围绕4点来完成多态的学习:
1,多态的体现
2,多态的前提
3,多态的好处
4,多态的应用
02-面向对象(多态-扩展性)
通过代码来展示。
定义动物类。
猫猫类继承动物类。
狗狗类也继承动物类。
让它们吃东西:
都顺利吃上啦:
但是我们想要这只猫去吃东西,得调用一次eat()方法,让那只猫去吃东西,又得调用一次eat()方法,这样是不是很麻烦呀!这句话在重复使用。
这个时候是不是阔以封装方法~
想让谁吃就调用这个方法就好啦。
提高了代码的复用性。
猫猫可以吃了,那狗狗呢,还是在重复吃。
再写一个狗狗吃东西的function()方法吗?
一年以后再养了一群猪猪呢?
再写一个猪猪吃东西的function()吗?
现在就有三个function()啦!
可是还有很多其他动物,我们都要挨个写吗?那累死了!
重新思考一下,不管是猫猫,狗狗,还是猪猪,它们都具备吃的行为。为什么它们都具备吃的行为呢?因为它们都是动物呀!
那能不能把代码写成这样呢?
这样c既是猫猫,又是动物,它吃的东西还是小鱼~
这就叫多态在程序中的表现。
1,多态的体现
父类的引用指向了自己的子类对象。
父类的引用也可以接收自己的子类对象。
Cat c=new Cat();
是本类类型指向本类对象。
Animal c=new Cat();
是父类类型指向本类对象。
接下来重新写一下function()函数:
这下好啦,猫猫、狗狗、猪猪都顺利吃到东西啦!
这样就提高了代码的扩展性,后期再有其他小动物想吃东西就可以直接用。
2,多态的前提
必须是类与类之间有关系,要么继承,要么实现。
通常还有一个前提:存在覆盖。(如果没有覆盖的话,也没有扩展它的意义了)
3,多态的好处
多态的出现大大的提高了程序的扩展性。
4,多态的弊端
提高了扩展性,但是只能使用父类的引用访问父类中的成员。
不能预先去使用子类,那个时候子类还不在呢。都没有子类,你怎么去访问它呢?所以它有一个局限性。(有点不太明白)
但是我们侧重扩展性。父类让你做什么去做什么就好啦。
03-面向对象(多态-转型)
Animal a=new Cat();
这句话其实是做了类型提升,把猫猫提升成了动物。就像之前讲的byte b=2;int a=b;b就被提升成了int。它也叫做向上转型。
如果想要调用猫的特有方法时,如何操作?
能向上转型,也能向下转型。
强制将父类的引用,转成子类类型。
但是这种情况下不可以强制转型哦:
因为动物可能是猫猫,可能是狗狗,可能是猪猪。如果你说,给我一只小动物,那给你一只猫猫、狗狗、猪猪都可以;如果你说,给我一只小猫猫,那给你一只小动物,也就是说,可能给你猫猫,也可能给你狗狗,也可能给你猪猪,这就不对了。
所以千万不要出现这样的操作:将父类对象转成子类类型。
我们能转换的是父类指向了自己的子类对象时,该引用可以被提升,也可以被强制转换。
多态自始至终都是子类对象在做着变化。(猫猫一会变成动物,一会变成猫猫)
再来个例子。
我是小楠,我有一只小猪猪叫小锴,我和小锴都足智多谋、鬼灵精怪、会讲课,所以小楠类和猪猪小锴类里面都有teaching()功能。小锴开了一个猪猪学习班,有一天,猪猪们来到我家找小猪猪小锴上课,可是小锴不在家,我就化装成小锴给猪猪们上课。这个时候我就发生了类型提升,提升成了小猪猪小锴。
小猪猪小锴 x=new 小楠();
x.teaching();
我开始讲课了,脱口而出的是喵喵星语言。我不懂猪猪语言,所以把猪猪语言复写掉了,变成了喵喵语言。(所以运行出来的结果是子类的结果)
课程顺利的讲完了,猪猪们都很喜欢我讲的课,(*^__^*) 嘻嘻~
猪猪们都走了,这时候我的小伙伴来找我逛街,可是我现在化装成小猪猪小锴了,虽然小楠类里面有shopping()功能,可是小猪猪小锴类没有这个功能呀,而且她们都以为我是小猪猪小锴,我只好说,不好意思,我不会逛街。
所以,x.shoping();失败啦!报错啦!
她们都走了,我很伤心,就赶紧跑回卧室换装,我要变回小楠!我脱下了猪猪服,换上了漂酿的小裙裙,又变成了可爱的小楠٩(๑òωó๑)۶
小楠 y=(小楠)x;
换好衣服之后,我赶紧给小伙伴打电话说,我回来了,走!逛街去!
y.shopping();
我们愉快的去逛街咯。
在这个过程中,自始至终是小楠在变化,一会变成小猪猪小锴,一会变成自己。
多态中自始至终是子类在发生变化。
那可以强制将小猪猪小锴变成小楠吗?不可以!为什么不可以?emmm...我好像举错例子了。
重来一遍!
故事开始了:
从前,小猪猪小锴是一只可爱的猪猪宝宝。
小猪猪小锴
{
void damajiang()
{
System.out.println("哈哈哈哈~呜呜~呜呜~哈哈哈~呜呜呜~");
}
void hejiu()
{
System.out.println("哈哈哈~哈哈哈~呼呼~呼呼呼~");
}
void shuilanjiao()
{
System.out.println("呼呼~呼呼~哼哼哼~呼呼呼~呼呼呼~");
shuomenghua();
System.out.println("嘤嘤嘤~呼呼~呼呼呼~");
}
void shuomenghua()
{
System.out.println("我不是小短腿!");
}
}
后来,小猪猪小锴长大了,后来又结婚了,生了一窝可爱的小猪猪小小锴。小猪猪小锴喜欢打麻将,他的大鹅纸小猪猪小胖,继承了他的优良基因,也打得一手好麻将。
小猪猪小胖
{
void damajiang()
{
System.out.println("哈哈哈哈~哈哈哈~哈哈哈~哈哈哈哈哈~");
}
void hejiu()
{
System.out.println("小口小口~吸溜吸溜~哈哈哈~");
}
void shuilanjiao()
{
System.out.println("呼呼呼~");
shuomenghua();
System.out.println("流口水~呼呼呼~");
}
void shuomenghua()
{
System.out.println("我麻麻好漂酿~");
}
void tingjiangzuo()
{
System.out.println("我要学习!我要进步!");
}
}
所以,小猪猪小锴类和小猪猪小胖类里面都有damajiang()功能。
有一天,小锴的朋友们打电话约他去打麻将,可是他不在家,电话被鬼灵精贵的小胖接到了。小胖心生一计,为什么不化装成爸比小锴替他去和朋友打麻将呢?于是就一口答应了下来。
小猪猪小锴 x=new 小猪猪小胖();
小胖精心打扮成了小锴的样子,去赴约啦。
在麻将局上,他调用了自己的damajiang()功能,和小锴的朋友们打了一把又一把。
x.damajiang();
因为遗传了小锴的优良基因,又从小耳濡目染小锴的高超技艺,所以小胖青出于蓝而胜于蓝,它的damajiang()功能里有很多自己新创的麻将套路,这些是小锴都没有掌握的呢!因此,小胖在这个麻将局里赚得盆满钵满,小锴的朋友都连连称(吐)赞(槽)他今天手气好。
打完了麻将,小胖心满意足的回家了。刚到家,还没来得及换掉化好的装,小胖的朋友们就来找他听讲座了。虽然小猪猪小胖类里面有tingjiangzuo()功能,可是小猪猪小锴类里面没有这个功能呀!小锴有打麻将功能,有喝酒功能,有睡懒觉功能,可是就是没有听讲座功能呀!
这时,虽然小胖心里知道自己是小胖,知道自己和爸比不一样,是一个热爱学术的人,可是他外表是小锴的样子,所以,只好说,不好意思,我不会听讲座。
所以,x.tingjiangzuo();失败啦!因为编译的时候检测到小猪猪小锴类里面没有tingjiangzuo()功能,所以编译报错啦!
小胖的朋友们前脚刚走,小胖后脚就跑回房间里把妆卸了,变回了可爱的小胖。
小猪猪小胖 y=(小猪猪小胖)x;
然后打电话给小伙伴们,“等等我,听讲座走!”
y.tingjiangzuo();
于是,小胖和小伙伴们一起认真的听了讲座,在自己成为一名科学家的道路上又前进的一大步!
在这个过程中,自始至终是小胖在变化,一会变成小锴,一会变成小胖自己。
多态中自始至终是子类在发生变化。
那可以强制将小锴变成小胖吗?不可以!为什么不可以?因为有小锴的时候有可能还没有小胖呢哈哈哈。就算这个时候小胖已经出生了,非要把小锴化装成小胖,那小锴有听讲座功能吗?木有!一去就露馅咯。
在刚刚这个故事中,
小猪猪小锴 x=new 小猪猪小胖();叫做向上转型。
小猪猪小胖 y=(小猪猪小胖)x;叫做向下转型。
好,回来啦,从故事里出来,继续笔记。
04-面向对象(多态-示例)
5,多态的应用
学生类:
基础班学员类:
高级班学员类:
在主函数里调用:
其实,我们可以把画红框的抽取出来,封装在一个函数中:
再进一步,我们这个功能能不能把它抽取出来,单独封装成一个对象呢?然后由这个对象去操作学生。
工具类:
该怎么用它呢?
我们把这件事情都交给DoStudent这个类去做就好啦,谁做传谁就OK。
在这些类中,最基本的是Student类和DoStudent类,这两个类把功能都写完啦,后面的基础班学员和高级班学员都是后来才有的,后面也许还会有冲刺班,其他班,这些都可以再加,只要有这两个类作为基础,这个功能就都阔以实现。
多态将对象调用这件事情变得简单了。以前是指挥某一类对象去做事情,现在是指挥某一批对象去做事情,因为找到了这些对象的共同所属类型。
05-面向对象(多态中成员的特点)
6,多态的出现代码中的特点(多态使用的注意事项)
一个例子,父类和子类中的函数是这样的:
这样调用会编译失败:
因为在父类中找不到method3()方法。
Fu f=new Zi();
在编译的时候,对象还没有产生,这个时候会检查f所属于的类型当中,到底有木有method3()方法。如果有,就编译通过,如果没有,就编译失败啦。
注释掉报错的语句,重新运行:
为什么是zi1、fu2呢?
因为最终在执行的是对象。就像上次讲的例子,小胖化装成小锴帮小锴打麻将,但他用的麻将技巧是自己的,而不是小锴的。
在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有,编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。
简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。
面试中会考的一个例子:
这样运行结果会是什么呢?
运行结果:
为什么第一个打印的是fu的num呢?
在多态中,成员变量的特点:无论编译和运行,都参考左边(引用型变量所属的类)。
Fu f=new Zi();
这句执行之后,堆内存中产生两个num,父类引用f肯定会先找自己的num。
再换一下,在Fu类和Zi类中都加一个静态的method4()方法(一般没有人去覆盖静态的):
然后酱紫调用,这种题很要命的,开发不会出现这种情况,没人去覆盖静态的,那么它的运行结果是什么呢?
发现打印结果是fu。
刚刚说的成员函数在多态调用时,编译看左边,运行看右边,说的是非静态的。
在多态中,静态成员函数的特点:无论编译和运行,都参考左边。
调用method1()的时候为什么执行zi?因为method1()在方法区的非静态区。当我们在调用method1()的时候,f确实在指向这个对象,method1()运行的时候,最终是被对象运行。对象在调用这个方法的时候,它最后打印的或者访问的是对象中的这个数据。
而静态方法,它本身不访问对象的特有数据,所以都可以直接用类名调用,这时只用看对象所属类中的成员。它访问的是静态区中的方法,不参考右边对象。
当method4()方法一进内存,它就已经被绑定了,因为它是静态的方法,绑定在所属的类中。
但是这种开发方式不多见。
为什么说一般覆盖不用在静态上,用在静态上:
这个时候运行的是zi的,如同被覆盖了一样。
可是在多态上就不一样了。静态和非静态的绑定方式不一样,静态用的是静态绑定,非静态绑定是动态绑定。有区别。
父类引用指向子类对象,对于静态方法调用的时候,父类走父类,子类走子类。
现在把多态的各种情况都分析啦,后面这种情况不常见,可能只有面试会用到,所以说记住上面加粗体的结论就好啦。
前面那种常见的要理解哦。
06-面向对象(多态的主板示例)
接下来举一个多态的小应用,把实际相结合一下~
需求很简单:
这样写一下,这个程序就能用了,调用主板:
但是这样写的话,这个程序就是一个死程序。
这个程序在现实中就是这样的:一个板子,插个CPU,搞个内存条,插上硬盘,往机箱里面一封装,这个主板里就这么点东西。封装完,一开机,噔噔噔就开始运行了,就完事儿了。
是不是缺什么呢?
比如说,我运行了一段时间以后,我想上网了,可是我的主板没有上网这个功能,我想上网上不了。我想听音乐,也听不了。
这时候怎么办呢?
我们需要给主板提供一些扩展性功能,它不能像刚刚写的那样,毫无扩展性。
改起来~
写一个网卡类:
在主板类里面加入使用网卡的功能:
试着运行一下,靠谱哒:
现在我想听音乐了,想加声卡。
但是现在主板和网卡的耦合性非常强,它是焊在一起的,我们得把网卡弄下来,再把声卡焊上去,非常麻烦。
而且后期还会有很多其他功能,也不知道后期都会有什么卡上来,为了降低这些卡与主板的耦合性,该怎么办呢?
主板这个时候找了国际组织开会,说,卡卡们可以有它们自己的型号,也可以有它们自己的标准。我在主板上留一些槽,你做的卡卡前半段来供电,后半段来走数据,你做的卡卡想在我的主板上插,就按这个标准来。
这时主板上面预留了很多插槽,给扩展留了余地。这时网卡和声卡和主板的依赖程度就没有那么大了。
现在结合这个图,把代码重新写一遍:
PCI接口:
主板类:
但是现在还没有对象,传null值会运行失败:
所以在主板类中改一下这里,这下就可以运行了:
网卡类:
现在可以建立网卡对象通过PCI使用网卡啦:
接口的出现降低了耦合性提高了功能的扩展性,多态的应用提高了程序的扩展性:
这里的 PCI p=new NetCard();//接口型引用指向自己的子类对象。
07-面向对象(多态的扩展示例)
接下来再讲一个应用。
需求:
通过JDBC操作用户信息的类:
使用它:
这个时候发现一个问题,如果后面我们不想用JDBC连接数据库,想改用Hibernate连接数据库,是不是还得改呢?
这个时候这个代码就得重新设计了。
设计好了重新写代码,加入用户信息数据访问对象的接口:
JDBC的类实现这个接口:
在主程序中调用:
Hibernate类也实现这个接口:
也可以轻松调用啦:
接口的出现,降低了耦合性,提高了扩展性。emmm...不是在讲多态么...
08-面向对象(Object类-equals())
接下来讲点特殊的东西。
Object:是所有对象的直接或者间接父类,传说中的上帝。
该类中定义的肯定是所有对象都具备的功能。
这是Object类所具有的方法,也是所有继承Object类的类(即所有类)所具有的方法:
接下来聊一聊里面的个别方法。
Java认为,所有对象都具有比较性,都能比较俩对象是否相同。
试一下使用这个比较的方法:
我们发现,第一个equals接收的Demo类型,第二个接收的Person类型,而任何对象都具有比较性。再看看这个方法,对象是Object类的引用,所以不管什么对象,这个方法都能比。
比较类中的数据:
我们有必要写这个compare方法吗?父类已经提供了,为什么还要自己写。
Object类中已经提供了对对象是否相同的比较方法。
如果自定义类中的也有比较相同的功能,没有必要重新定义。
只要沿袭父类中的功能,建立自己特有比较内容即可。这就是覆盖。
覆盖好啦:
但是这样调用就会报错:
再改一下代码:
OK啦。
当然也有其他办法,比如说抛出异常,“你疯啦!怎么拿它们两个比呐!”
09-面向对象(Object类toString())
接下来讲这个方法:
试一下:
打印出来的结果,前面是这个对象所属的类,后面是这个对象的哈希值。
所有对象都是依赖于类产生,所以用getClass:
讲一讲Class,Class也是一个类:
里面有个方法:
我们打印一下类名和哈希值,和toString()的运行结果对比一下,证实了打印出来的确实是类名和哈希值:
可以给中间再加两个@@,这样打印出来就一毛一样啦:
看看toString的描述,确实是这样:
但是这个哈希地址值感觉没啥意义,我们想让打印出来的东西有意义,在Demo类中复写一下:
这样打印出来就变成这样了:
通常我们经常都会进行复写操作,比如上面举的例子,建立对象特有的比较形式和字符串表现方式,父类默认的都没有什么意义。
写类描述的时候,Object类中的方法都可能被复写,那为什么不把它里面的方法都抽象呢?因为这样Object类也变抽象了,那么随便定义一个类,就都被强迫去做这件事情,这样就会很麻烦。