首先,在学习JAVA的多态机制时,我们知道子类对象是可以向上自动转换为超类对象的,例如:
public class Father {
public int a=0;
public int b=2;
public int getA() {
return a;
}
public void print(){
System.out.println("123");
}
}
public class Son extends Father{
public int a=1;
public int c=3;
public int getA(){
return a;
}
public void pr(){
System.out.println("456");
}
}
Father t=new Son(); //该操作允许
然后,我们试着用该对象调用一些方法和变量:
t.print(); // 123
System.out.println(t.b); // 2
发现该对象调用超类特有的方法和变量正常
t.pr(); // 报错
System.out.println(t.c); // 报错
发现该对象不能调用子类特有的方法和变量
java核心技术里说,对象调用方法时,首先从该对象的方法表中找到名称一致的,再通过参数类型和个数确立方法。从上述结果来看,t对象并不能调用子类中特有的方法,所以对于这种对象,我的理解是:调用方法时应该编译器是从声明类型类的方法表中寻找,而不是实际类型类。没找到,就会报错。
然后,我们再做个实验
System.out.println(t.getA()); // 1
System.out.println(t.a); // 0
该对象的getA方法和a变量是超类和子类都有的,发生了覆盖。
当t调用getA方法时结果为1,而超类中的getA方法应该是返回0的,说明此时t对象调用的getA方法属于子类。而对于a变量结果为0,说明此时t对象调用的变量a是超类的。按理说,是从声明类型的方法表中寻找,应该都是调用的超类的方法才对,
其实呢,原因在于java中存在静态绑定和动态绑定,对于getA方法,的确会从声明类型中查找,但是实际运行时,java虚拟机会根据对象的实际类型来运行方法,这便是动态绑定。很显然,对于变量a,它是属于静态绑定的,不仅如此,对于final、static、private的方法都是属于静态绑定的。当然,动态绑定主要是指发生了方法覆盖的情况,例如print方法,超类中独有而没被子类覆盖,调用时肯定是超类的。
到此,我们针对子类转换来的父类对象,可以总结一下:
- 该对象不能调用子类中独有的方法和变量,只能调用声明类型方法表中的方法和变量
- 该对象调用方法时会发生动态绑定,根据对象实际类型从而运行方法,主要针对发生了覆盖的方法
- 该对象调用成员变量以及final、static、private的方法时,发生的是静态绑定
- 对于成员变量,t对象调用的即是声明类型Father类中的变量a,该变量可以是Father自己声明,也可以是继承得来,也可以发生覆盖。
- final、static、private方法很好理解