java多态的体现_java多态的具体表现实例和理解
Java的多态性
⾯向对象编程有三个特征,即封装、继承和多态。
封装隐藏了类的内部实现机制,从⽽可以在不影响使⽤者的前提下改变类的内部结构,同时保护了数据。
继承是为了重⽤⽗类代码,同时为实现多态性作准备。那么什么是多态呢?
⽅法的重写、重载与动态连接构成多态性。Java之所以引⼊多态的概念,原因之⼀是它在类的继承问题上和C++不同,后者允许多继承,
这确实给其带来的⾮常强⼤的功能,但是复杂的继承关系也给C++开发者带来了更⼤的⿇烦,为了规避风险,Java只允许单继承,派⽣类
与基类间有IS-A的关系(即“猫”isa“动物”)。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很⼤的限制,所以,Java引
⼊了多态性的概念以弥补这点的不⾜,此外,抽象类和接⼝也是解决单继承规定限制的重要⼿段。同时,多态也是⾯向对象编程的精髓所
在。
要理解多态性,⾸先要知道什么是“向上转型”。
我定义了⼀个⼦类Cat,它继承了Animal类,那么后者就是前者是⽗类。我可以通过
Catc=newCat();
实例化⼀个Cat的对象,这个不难理解。但当我这样定义时:
Animala=newCat();
这代表什么意思呢?
很简单,它表⽰我定义了⼀个Animal类型的引⽤,指向新建的Cat类型的对象。由于Cat是继承⾃它的⽗类Animal,所以Animal类型的引
⽤是可以指向Cat类型的对象的。那么这样做有什么意义呢?因为⼦类是对⽗类的⼀个改进和扩充,所以⼀般⼦类在功能上较⽗类更强⼤,
属性较⽗类更独特,
定义⼀个⽗类类型的引⽤指向⼀个⼦类的对象既可以使⽤⼦类强⼤的功能,⼜可以抽取⽗类的共性。
所以,⽗类类型的引⽤可以调⽤⽗类中定义的所有属性和⽅法,⽽对于⼦类中定义⽽⽗类中没有的⽅法,它是⽆可奈何的;
同时,⽗类中的⼀个⽅法只有在在⽗类中定义⽽在⼦类中没有重写的情况下,才可以被⽗类类型的引⽤调⽤;
对于⽗类中定义的⽅法,如果⼦类中重写了该⽅法,那么⽗类类型的引⽤将会调⽤⼦类中的这个⽅法,这就是动态连接。
看下⾯这段程序:
classFather{
publicvoidfunc1(){
func2();
}
//这是⽗类中的func2()⽅法,因为下⾯的⼦类中重写了该⽅法
//所以在⽗类类型的引⽤中调⽤时,这个⽅法将不再有效
//取⽽代之的是将调⽤⼦类中重写的func2()⽅法
publicvoidfunc2(){
n("AAA");
}
}
classChildextendsFather{
//func1(inti)是对func1()⽅法的⼀个重载
//由于在⽗类中没有定义这个⽅法,所以它不能被⽗类类型的引⽤调⽤
//所以在下⾯的main⽅法中1(68)是不对的
publicvoidfunc1(inti){
n("BBB");
}
//func2()重写了⽗类Father中的func2()⽅法
//如果⽗类类型的引⽤中调⽤了func2()⽅法,那么必然是⼦类中重写的这个⽅法
publicvoidfunc2(){
n("CCC");
}
}
publicclassPolymorphismTest{
publicstaticvoidmain(String[]args){
Fatherchild=newChild();
1();//打印结果将会是什么?
}
}
上⾯的程序是个很典型的多态的例⼦。⼦类Child继承了⽗类Father,并重载了⽗类的func1()⽅法,重写了⽗类的func2()⽅法。重载后的
func1(inti)和func1()不再是同⼀个⽅法,由于⽗类中没有func1(inti),那么,⽗类类型的引⽤child就不能调⽤func1(inti)⽅法。⽽⼦
类重写了func2()⽅法,那么⽗类类型的引⽤child在调⽤该⽅法时将会调⽤⼦类中重写的func2()。
那么该程序将会打印出什么样的结果呢?
很显然,应该是“CCC”。
对于多态,可以总结它为:
⼀、使⽤⽗类类型的引⽤指向⼦类的对象;
⼆、该引⽤只能调⽤⽗类中定义的⽅法和变量;
三、如果⼦类中重写了⽗类中的⼀个⽅法,那么在调⽤这个⽅法的时候,将会调⽤⼦类中的这个⽅法;(动态连接、动态调⽤)
四、变量不能被重写(覆盖),”重写“的概念只针对⽅法,如果在⼦类中”重写“了⽗类中的变量,那么在编译时会报错。
**********************************************再**来**⼀**篇*********************************************************************
多态详解多态是通过:
1接⼝和实现接⼝并覆盖接⼝中同⼀⽅法的⼏不同的类体现的
2⽗类和继承⽗类并覆盖⽗类中同⼀⽅法的⼏个不同⼦类实现的.
⼀、基本概念
多态性:发送消息给某个对象,让该对象⾃⾏决定响应何种⾏为。
通过将⼦类对象引⽤赋值给超类对象引⽤变量来实现动态⽅法调⽤。
java的这种机制遵循⼀个原则:当超类对象引⽤变量引⽤⼦类对象时,被引⽤对象的类型⽽不是引⽤变量的类型决定了调⽤谁的成员⽅法,
但是这个被调⽤的⽅法必须是在超类中定义过的,也就是说被⼦类覆盖的⽅法。
1.如果a是类A的⼀个引⽤,那么,a可以指向类A的⼀个实例,或者说指向类A的⼀个⼦类。
2.如果a是接⼝A的⼀个引⽤,那么,a必须指向实现了接⼝A的⼀个类的实例。
⼆、Java多态性实现机制
SUN⽬前的JVM实现机制,类实例的引⽤就是指向⼀个句柄(handle)的指针,这个句柄是⼀对指针:
⼀个指针指向⼀张表格,实际上这个表格也有两个指针(⼀个指针指向⼀个包含了对象的⽅法表,另外⼀个指向类对象,表明该对象所属的
类型);
另⼀个指针指向⼀块从java堆中为分配出来内存空间。
三、总结
1、通过将⼦类对象引⽤赋值给超类对象引⽤变量来实现动态⽅法调⽤。
DerivedCc2=newDerivedC();
BaClassa1=c2;//BaClass基类,DerivedC是继承⾃BaClass的⼦类
();//play()在BaClass,DerivedC中均有定义,即⼦类覆写了该⽅法
分析:
*为什么⼦类的类型的对象实例可以覆给超类引⽤?
⾃动实现向上转型。通过该语句,编译器⾃动将⼦类实例向上移动,成为通⽤类型BaClass;
*()将执⾏⼦类还是⽗类定义的⽅法?
⼦类的。在运⾏时期,将根据a这个对象引⽤实际的类型来获取对应的⽅法。所以才有多态性。⼀个基类的对象引⽤,被赋予不同的⼦类对
象引⽤,执⾏该⽅法时,将表现出不同的⾏为。
在a1=c2的时候,仍然是存在两个句柄,a1和c2,但是a1和c2拥有同⼀块数据内存块和不同的函数表。
2、不能把⽗类对象引⽤赋给⼦类对象引⽤变量
BaClassa2=newBaClass();
DerivedCc1=a2;//出错
在java⾥⾯,向上转型是⾃动进⾏的,但是向下转型却不是,需要我们⾃⼰定义强制进⾏。
c1=(DerivedC)a2;进⾏强制转化,也就是向下转型.
3、记住⼀个很简单⼜很复杂的规则,⼀个类型引⽤只能引⽤引⽤类型⾃⾝含有的⽅法和变量。
你可能说这个规则不对的,因为⽗类引⽤指向⼦类对象的时候,最后执⾏的是⼦类的⽅法的。
其实这并不⽭盾,那是因为采⽤了后期绑定,动态运⾏的时候⼜根据型别去调⽤了⼦类的⽅法。⽽假若⼦类的这个⽅法在⽗类中并没有定
义,则会出错。
例如,DerivedC类在继承BaClass中定义的函数外,还增加了⼏个函数(例如myFun())
分析:
当你使⽤⽗类引⽤指向⼦类的时候,其实jvm已经使⽤了编译器产⽣的类型信息调整转换了。
这⾥你可以这样理解,相当于把不是⽗类中含有的函数从虚拟函数表中设置为不可见的。注意有可能虚拟函数表中有些函数地址由于在⼦类
中已经被改写了,所以对象虚拟函数表中虚拟函数项⽬地址已经被设置为⼦类中完成的⽅法体的地址了。
4、Java与C++多态性的⽐较
jvm关于多态性⽀持解决⽅法是和c++中⼏乎⼀样的,
只是c++中编译器很多是把类型信息和虚拟函数信息都放在⼀个虚拟函数表中,但是利⽤某种技术来区别。
Java把类型信息和函数信息分开放。Java中在继承以后,⼦类会重新设置⾃⼰的虚拟函数表,这个虚拟函数表中的项⽬有由两部分组成。
从⽗类继承的虚拟函数和⼦类⾃⼰的虚拟函数。
虚拟函数调⽤是经过虚拟函数表间接调⽤的,所以才得以实现多态的。
Java的所有函数,除了被声明为final的,都是⽤后期绑定。
四.1个⾏为,不同的对象,他们具体体现出来的⽅式不⼀样,
⽐如:⽅法重载overloading以及⽅法重写(覆盖)override
classHuman{
voidrun(){输出⼈在跑}
}
classManextendsHuman{
voidrun(){输出男⼈在跑}
}
这个时候,同是跑,不同的对象,不⼀样(这个是⽅法覆盖的例⼦)
classTest{
voidout(Stringstr){输出str}
voidout(inti){输出i}
}
这个例⼦是⽅法重载,⽅法名相同,参数表不同
ok,明⽩了这些还不够,还⽤⼈在跑举例
Humanahuman=newMan();
这样我等于实例化了⼀个Man的对象,并声明了⼀个Human的引⽤,让它去指向Man这个对象
意思是说,把Man这个对象当Human看了.
⽐如去动物园,你看见了⼀个动物,不知道它是什么,"这是什么动物?""这是⼤熊猫!"
这2句话,就是最好的证明,因为不知道它是⼤熊猫,但知道它的⽗类是动物,所以,
这个⼤熊猫对象,你把它当成其⽗类动物看,这样⼦合情合理.
这种⽅式下要注意newMan();的确实例化了Man对象,所以()这个⽅法输出的是"男⼈在跑"
如果在⼦类Man下你写了⼀些它独有的⽅法⽐如eat(),⽽Human没有这个⽅法,
在调⽤eat⽅法时,⼀定要注意强制类型转换((Man)ahuman).eat(),这样才可以...
对接⼝来说,情况是类似的...
实例:
packagedomatic;
//定义超类superA
classsuperA{
inti=100;
voidfun(intj){
j=i;
n("ThisissuperA");
}
}
//定义superA的⼦类subB
classsubBextendssuperA{
intm=1;
voidfun(intaa){
n("ThisissubB");
}
}
//定义superA的⼦类subC
classsubCextendssuperA{
intn=1;
voidfun(intcc){
n("ThisissubC");
}
}
classTest{
publicstaticvoidmain(String[]args){
superAa=newsuperA();
subBb=newsubB();
subCc=newsubC();
a=b;
(100);
a=c;
(200);
}
}
/*
*上述代码中subB和subC是超类superA的⼦类,我们在类Test中声明了3个引⽤变量a,b,
*c,通过将⼦类对象引⽤赋值给超类对象引⽤变量来实现动态⽅法调⽤。也许有⼈会问:
*"为什么(1)和(2)不输出:ThisissuperA"。
*java的这种机制遵循⼀个原则:当超类对象引⽤变量引⽤⼦类对象时,
*被引⽤对象的类型⽽不是引⽤变量的类型决定了调⽤谁的成员⽅法,
*但是这个被调⽤的⽅法必须是在超类中定义过的,
*也就是说被⼦类覆盖的⽅法。
*所以,不要被上例中(1)和(2)所迷惑,虽然写成(),但是由于(1)中的a被b赋值,
*指向了⼦类subB的⼀个实例,因⽽(1)所调⽤的fun()实际上是⼦类subB的成员⽅法fun(),
*它覆盖了超类superA的成员⽅法fun();同样(2)调⽤的是⼦类subC的成员⽅法fun()。
*另外,如果⼦类继承的超类是⼀个抽象类,虽然抽象类不能通过new操作符实例化,
*但是可以创建抽象类的对象引⽤指向⼦类对象,以实现运⾏时多态性。具体的实现⽅法同上例。
*不过,抽象类的⼦类必须覆盖实现超类中的所有的抽象⽅法,
*否则⼦类必须被abstract修饰符修饰,当然也就不能被实例化了
*/
以上⼤多数是以⼦类覆盖⽗类的⽅法实现多态.下⾯是另⼀种实现多态的⽅法-----------重写⽗类⽅法
⾥没有多继承,⼀个类之能有⼀个⽗类。⽽继承的表现就是多态。⼀个⽗类可以有多个⼦类,⽽在⼦类⾥可以重写⽗类的⽅法(例如
⽅法print()),这样每个⼦类⾥重写的代码不⼀样,⾃然表现形式就不⼀样。这样⽤⽗类的变量去引⽤不同的⼦类,在调⽤这个相同的⽅法
print()的时候得到的结果和表现形式就不⼀样了,这就是多态,相同的消息(也就是调⽤相同的⽅法)会有不同的结果。举例说明:
//⽗类
publicclassFather{
//⽗类有⼀个打孩⼦⽅法
publicvoidhitChild(){
}
}
//⼦类1
publicclassSon1extendsFather{
//重写⽗类打孩⼦⽅法
publicvoidhitChild(){
n("为什么打我?我做错什么了!");
}
}
//⼦类2
publicclassSon2extendsFather{
//重写⽗类打孩⼦⽅法
publicvoidhitChild(){
n("我知道错了,别打了!");
}
}
//⼦类3
publicclassSon3extendsFather{
//重写⽗类打孩⼦⽅法
publicvoidhitChild(){
n("我跑,你打不着!");
}
}
//测试类
publicclassTest{
publicstaticvoidmain(Stringargs[]){
Fatherfather;
father=newSon1();
ld();
father=newSon2();
ld();
father=newSon3();
ld();
}
}
都调⽤了相同的⽅法,出现了不同的结果!这就是多态的表现!
本文发布于:2022-11-25 19:44:39,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/20462.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |