java⽗类获取⼦类对象_Java多态性详解(⽗类引⽤⼦类对
象)
⾯向对象编程有三个特征,即封装、继承和多态。
封装隐藏了类的内部实现机制,从⽽可以在不影响使⽤者的前提下改变类的内部结构,同时保护了数据。
继承是为了重⽤⽗类代码,同时为实现多态性作准备。那么什么是多态呢?
⽅法的重写、重载与动态连接构成多态性。Java之所以引⼊多态的概念,原因之⼀是它在类的继承问题上和C++不同,后者允许多继承,
这确实给其带来的⾮常强⼤的功能,但是复杂的继承关系也给C++开发者带来了更⼤的⿇烦,为了规避风险,Java只允许单继承,派⽣类
与基类间有IS-A的关系(即“猫”is a “动物”)。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很⼤的限制,所以,Java引
⼊了多态性的概念以弥补这点的不⾜,此外,抽象类和接⼝也是解决单继承规定限制的重要⼿段。同时,多态也是⾯向对象编程的精髓所
在。
===========================================================================================
要理解多态性,⾸先要知道什么是“向上转型”。(⾃动向上转型, 向下转型要强制类型转换(向下转型还没发现有什么意义))
我定义了⼀个⼦类Cat,它继承了Animal类,那么后者就是前者是⽗类。我可以通过Cat c = new Cat(); 实例化⼀个Cat的对象,这个不
难理解。
但当我这样定义时: Animal a = new Cat();
这代表什么意思呢?
很简单,它表⽰我定义了⼀个Animal类型的引⽤,指向新建的Cat类型的对象。由于Cat是继承⾃它的⽗类Animal,所以Animal类型的引
⽤是可以指向Cat类型的对象的。那么这样做有什么意义呢?因为⼦类是对⽗类的⼀个改进和扩充,所以⼀般⼦类在功能上较⽗类更强⼤,
属性较⽗类更独特,
定义⼀个⽗类类型的引⽤指向⼀个⼦类的对象既可以使⽤⼦类强⼤的功能,⼜可以抽取⽗类的共性。
所以,⽗类类型的引⽤可以调⽤⽗类中定义的所有属性和⽅法,⽽对于⼦类中定义⽽⽗类中没有的⽅法,⽗类的引⽤是不能调⽤的;
同时,⽗类中的⼀个⽅法只有在在⽗类中定义⽽在⼦类中没有重写的情况下,才可以被⽗类类型的引⽤调⽤⽗类的⽅法;
对于⽗类中定义的⽅法,如果⼦类中重写了该⽅法,那么⽗类类型的引⽤将会调⽤⼦类中的这个⽅法,这就是动态连接。
注意:此处的⽗类可以是普通类,interface接⼝,abstract抽象类
看下⾯这段程序:
class Father{
public void func1() //⽗类所独有的
{
func2();
}
public void func2() // 在⼦类中重写
{
System.out.println("AAA");
}
}
class Child extends Father{
public void func1(int i) / /⼦类⾃⼰定义的,跟⽗类的func()不是⼀个
{
System.out.println("BBB");
}
public void func2()//重写了⽗类Father中的func2()⽅法//如果⽗类类型的引⽤中调⽤了func2()⽅法,那么必然是⼦类中重写的这个⽅法
{
System.out.println("CCC");
}
}
public class PolymorphismTest {
public static void main(String[] args) {
Father child = new Child();
child.func1();//打印结果将会是什么?
}
}
上⾯的程序是个很典型的多态的例⼦。⼦类Child继承了⽗类Father,重写了⽗类的func2()⽅法。⽗类类型的引⽤child就不能调⽤⼦类的func1(int i)⽅法。⽽⼦类重写了func2()⽅法,那么⽗类类型的引⽤child在调⽤该⽅法时将会调⽤⼦类中重写的func2()。
那么该程序将会打印出什么样的结果呢?
很显然,应该是“CCC”。
===============================================================
对于多态,可以总结它为:
⼀、使⽤⽗类类型的引⽤指向⼦类的对象;
⼆、该引⽤只能调⽤⽗类中定义的⽅法和变量;
三、如果⼦类中重写了⽗类中的⼀个⽅法,那么在调⽤这个⽅法的时候,将会调⽤⼦类中的这个⽅法;(动态连接、动态调⽤)
四、变量不能被重写(覆盖),”重写“的概念只针对⽅法,如果在⼦类中”重写“了⽗类中的变量,那么在编译时会报错。
1 接⼝ 和 实现接⼝并覆盖接⼝中同⼀⽅法的⼏不同的类体现的
2 ⽗类 和 继承⽗类并覆盖⽗类中同⼀⽅法的⼏个不同⼦类实现的.
⼀、基本概念
多态性:发送消息给某个对象,让该对象⾃⾏决定响应何种⾏为。通过将⼦类对象引⽤赋值给超类对象引⽤变量来实现动态⽅法调⽤。
java 的这种机制遵循⼀个原则:当超类对象引⽤变量引⽤⼦类对象时,被引⽤对象的类型⽽不是引⽤
变量的类型决定了调⽤谁的成员⽅法,但是这个被调⽤的⽅法必须是在超类中定义过的,也就是说被⼦类覆盖的⽅法。
1. 如果a是类A的⼀个引⽤,那么,a可以指向类A的⼀个实例,或者说指向类A的⼀个⼦类。
2. 如果a是接⼝A的⼀个引⽤,那么,a必须指向实现了接⼝A的⼀个类的实例。
⼆、Java多态性实现机制
SUN⽬前的JVM实现机制,类实例的引⽤就是指向⼀个句柄(handle)的指针,这个句柄是⼀对指针:
⼀个指针指向⼀张表格,实际上这个表格也有两个指针(⼀个指针指向⼀个包含了对象的⽅法表,另外⼀个指向类对象,表明该对象所属的类型);
另⼀个指针指向⼀块从java堆中为分配出来内存空间。
三、总结
1、通过将⼦类对象引⽤赋值给超类对象引⽤变量来实现动态⽅法调⽤。
DerivedC c2=new DerivedC();
BaseClass a1= c2; //BaseClass 基类,DerivedC是继承⾃BaseClass的⼦类
a1.play(); //play()在BaseClass,DerivedC中均有定义,即⼦类覆写了该⽅法
分析:
* 为什么⼦类的类型的对象实例可以覆给超类引⽤?
⾃动实现向上转型。通过该语句,编译器⾃动将⼦类实例向上移动,成为通⽤类型BaseClass;
* a1.play()将执⾏⼦类还是⽗类定义的⽅法? 答:⼦类的。
在运⾏时期,将根据a这个对象引⽤实际的类型来获取对应的⽅法。所以才有多态性。⼀个基类的对象引⽤,被赋予不同的⼦类对象引⽤,执⾏该⽅法时,将表现出不同的⾏为。
在a1=c2的时候,仍然是存在两个句柄,a1和c2,但是a1和c2拥有同⼀块数据内存块和不同的函数表。
2、不能把⽗类对象引⽤赋给⼦类对象引⽤变量
BaseClass a2=new BaseClass();
DerivedC c1=a2; //出错
在java⾥⾯,向上转型是⾃动进⾏的,但是向下转型却不是,需要我们⾃⼰定义强制进⾏。
c1=(DerivedC)a2; 进⾏强制转化,也就是向下转型.
3、记住⼀个很简单⼜很复杂的规则,⼀个类型引⽤只能引⽤引⽤类型⾃⾝含有的⽅法和变量。
你可能说这个规则不对的,因为⽗类引⽤指向⼦类对象的时候,最后执⾏的是⼦类的⽅法的。
其实这并不⽭盾,那是因为采⽤了后期绑定,动态运⾏的时候⼜根据型别去调⽤了⼦类的⽅法。⽽假若⼦类的这个⽅法在⽗类中并没有定义,则会出错。
java重写和重载的区别例如,DerivedC类在继承BaseClass中定义的函数外,还增加了⼏个函数(例如 myFun())
分析:
当你使⽤⽗类引⽤指向⼦类的时候,其实jvm已经使⽤了编译器产⽣的类型信息调整转换了。
这⾥你可以这样理解,相当于把不是⽗类中含有的函数从虚拟函数表中设置为不可见的。注意有可能虚拟函数表中有些函数地址由于在⼦类中已经被改写了,所以对象虚拟函数表中虚拟函数项⽬地址已
经被设置为⼦类中完成的⽅法体的地址了。
4、Java与C++多态性的⽐较
jvm关于多态性⽀持解决⽅法是和c++中⼏乎⼀样的,
只是c++中编译器很多是把类型信息和虚拟函数信息都放在⼀个虚拟函数表中,但是利⽤某种技术来区别。
Java把类型信息和函数信息分开放。Java中在继承以后,⼦类会重新设置⾃⼰的虚拟函数表,这个虚拟函数表中的项⽬有由两部分组成。从⽗类继承的虚拟函数和⼦类⾃⼰的虚拟函数。
虚拟函数调⽤是经过虚拟函数表间接调⽤的,所以才得以实现多态的。
Java的所有函数,除了被声明为final的,都是⽤后期绑定。
四. 1个⾏为,不同的对象,他们具体体现出来的⽅式不⼀样,
⽐如: ⽅法重载 overloading 以及 ⽅法重写(覆盖)override
class Human{
void run(){输出 ⼈在跑}
}
class Man extends Human{
void run(){输出 男⼈在跑}
}
这个时候,同是跑,不同的对象,不⼀样(这个是⽅法覆盖的例⼦)
class Test{
void out(String str){输出 str}
void out(int i){输出 i}
}
这个例⼦是⽅法重载,⽅法名相同,参数表不同
ok,明⽩了这些还不够,还⽤⼈在跑举例
Human ahuman=new Man();
这样我等于实例化了⼀个Man的对象,并声明了⼀个Human的引⽤,让它去指向Man这个对象
意思是说,把 Man这个对象当 Human看了.
⽐如去动物园,你看见了⼀个动物,不知道它是什么, "这是什么动物? " "这是⼤熊猫! "
这2句话,就是最好的证明,因为不知道它是⼤熊猫,但知道它的⽗类是动物,所以,
这个⼤熊猫对象,你把它当成其⽗类 动物看,这样⼦合情合理.
这种⽅式下要注意 new Man();的确实例化了Man对象,所以 ahuman.run()这个⽅法 输出的 是 "男⼈在跑 "如果在⼦类 Man下你 写了⼀些它独有的⽅法 ⽐如 eat(),⽽Human没有这个⽅法,
在调⽤eat⽅法时,⼀定要注意 强制类型转换 ((Man)ahuman).eat(),这样才可以...
对接⼝来说,情况是类似的...
实例:
//定义超类superA
class superA {
int i = 100;
void fun(int j) {
j = i;
System.out.println("This is superA");
}
}
// 定义superA的⼦类subB
class subB extends superA {
int m = 1;
void fun(int aa) {
System.out.println("This is subB");
}
}
// 定义superA的⼦类subC
class subC extends superA {
int n = 1;
void fun(int cc) {
System.out.println("This is subC");
}
}
class Test {
public static void main(String[] args) {
superA a = new superA();
subB b = new subB();
subC c = new subC();
a = b;
a.fun(100);
a = c;
a.fun(200);
}
}
* 上述代码中subB和subC是超类superA的⼦类,我们在类Test中声明了3个引⽤变量a, b, * c,通过将⼦类对象引⽤赋值给超类对象引⽤变量来实现动态⽅法调⽤。也许有⼈会问:* "为什么(1)和(2)不输出:This is superA"。
* java的这种机制遵循⼀个原则:当超类对象引⽤变量引⽤⼦类对象时,
* 被引⽤对象的类型⽽不是引⽤变量的类型决定了调⽤谁的成员⽅法,
* 但是这个被调⽤的⽅法必须是在超类中定义过的,
* 也就是说被⼦类覆盖的⽅法。
* 所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于(1)中的a被b赋值,
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论