原帖地址:http://justt0.iteye.com/admin/blogs/1983634/edit
package com.p1;
public class Father {
void test(){
this.go();//即go();
}
void go(){
System.out.println("father go");
}
}
package com.p1;
public class Son extends Father {
@Override
void test() {
// TODO Auto-generated method stub
//指定super.test();
super.test();
//指定super.go();
super.go();
}
@Override
void go() {
// TODO Auto-generated method stub
System.out.println("son go");
}
public static void main(String[] args) {
Father f = new Son();
f.test();
}
}
提到多态,首先要清楚创建子类的时候都发生了什么.(变量初始化总是在构造方法之前,我们在这里忽略它)我们知道执行new Son();后,由于继承关系,所以会先初始化父类---即会先创建一个父类对象(基对象这个对象是真实存在的,它和直接创建基类对象的区别是,前者的对象是包装在导出类的内部,后者则是外部),然后才是初始化子类--即创建子类对象,最后的结果是导出类对象(Son对象)的内部包装了一个基类对象(Father对象).
我们再说创建对象的方式,Father f = new Son();这是将一个Son对象付给了一个Father引用,事实上f就是一个Son对象,但是这个f只能访问基类对象子类对象都有的方法,然后执行f.test();方法,因为f指向的对象其实还是一个Son对象,所以调用的test方法就是son对象的test方法.
然后我们再说关键字this和super,有了上面的分析,我们可以将this认为是指向导出类对象,super指向的则是积累对象,有了这个思路,理解多态就容易多了.基类有两个方法,test()和go(),test调用go,在子类中我们覆盖这两个方法,父类的go()方法打印的是"father go",而子类的go()方法打印的是"son go",好了,go方法就这样,再说调用go方法的test方法,我们假设两种情况:
情况一:子类的test方法里面调用父类的test方法,即super.test();看到这个super了吧,它指的就是基类对象,好了,这样我们直接找到基类的test方法,它调用的是go();,这个go();我们也可以写成this.go();(这个都清楚吧,两种写法一个意思),好了,this出现了,这个this指的就是子类对象,那我们直接就找子类的go()方法,所以打印的是"son go",怎么样?容易吧?
情况二:子类的test方法里面直接调用父类的go方法,即super.go();看到super了吧,它指的是基类对象,这样我们直接找到父类的go()方法,所以打印的是"father go",即使子类有覆盖go()方法,没用.
说到这里,有朋友可能会想到,如果子类没有覆盖父类的go方法,子类只是覆盖基类的test方法呢? 这其实又是两种情况,理解方法也很简单:
情况一:如果子类的test方法执行的是super.test(),由于父类的test方法调用的是go()(即this.go();),这个this代指子类对象,但是子类对象并没有自己的go()方法,怎么办? 其实我们可以把它看成是一层一层的,如果子类没有go方法,那么它就会向里面找,所以就会找到父类的go()方法,所以就会打印出"father go"的结果.
情况二:如果子类的test方法执行的是super.go(),直接找到父对象的go方法,没说的,打印的是"father go".
可能有人又会想到,如果子类什么都没覆盖,只是继承了父类呢? 这其实又是一种情况,f.test();调用子类的test()方法,由于子类没有明确覆盖test方法,所以,向里找,在基类找到了test方法,然后test方法里调用了this.go();方法,this代指Son对象,在Son对象里又没找到明确覆盖的go()方法,所以又向里找,所以找到了基类对象的go()方法,所以打印的是father go.
[2013.12.03]更新
今天看到IBM官网上的一篇文章,http://www.ibm.com/developerworks/cn/java/j-lo-polymorph/,印证了我的理解,首先,this指的就是实际的对象,不管引用对象的引用是什么,对象的类型是不变的,比如,Father f = new Son();即使引用Son对象的是Father的引用,但如果你试一下将f打印出来,然后再将f向上转型为son,然后将转型后对象打印出来,你就会发现两者打出来的结果是一致的,就是com.p1.Son@142c63f,为什么呢? 看下源码就会发现,如果system.out.println();打印一个object的话,调用的是该object的toString方法,源码是这样的:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
意思是类的名字+@+对象的hashcode的16进制字符串表现形式,hashcode大家都知道,是唯一的,再看上面的结果com.p1.Son@142c63f,首先,即使是Father引用指向的他,但他还是一个Son对象,再看hashcode部分,转型前后@后面的都一样,说明,对象还是那个对象,没有产生新对象.
接着,我们回到主题,http://www.ibm.com/developerworks/cn/java/j-lo-polymorph/,这篇文章有一幅描绘方法表的图:
大概意思是,girl和boy都继承自object,我们拿girl来说,首先,girl复写了object的toString()方法,然后,girl又多了两个方法,eat()和speak(),对于一个girl对象来说如果调用girl的eat()方法,哪个箭头(我们把他看做指针),就会直接指向girl的eat();如果调用的是toStirng();方法,但是object有同名方法,怎么办? 答案是,指针会直接指向girl里面的toString();方法,而忽略掉object里面toStirng方法,这其实就是多态的实现,很清晰的思路,再说如果调用没有被girl复写的object方法,指针会直接指向object的方法代码,也很简单.
至于变量的覆盖,可以参照这篇文章,http://developer.51cto.com/art/201204/327772.htm和这篇文章http://blog.csdn.net/tryandlearn/article/details/8742367