当字段实际类型与声明类型不同时,方法的调用不同。
字段实际类型与所声明类型不同时,Override与Overload中方法的调用策略的不同。
Father f = new Son();
上述代码中,实际类型是Son,声明类型是Father
表面类型(声明类型):对象声明时的类型
实际类型:对象产生时的类型
例子1
class COne {
public void f() {
System.out.println("COne.f");
}
}
class CTwo extends COne{
public void f() {
System.out.println("CTwo.f");
}
}
class CThree {
public void g(COne one) {
System.out.println("g(Cone)");
one.f();当用对象名.去使用方法的时候,调用的是实际类型的的方法
}
public void g(CTwo two) {
System.out.println("g(Ctwo)");
two.f();
}
}
public class Main {
public static void main(String[] args) {
COne one = new CTwo(); //表面类型是COne,实际类型是CTwo
CThree three = new CThree();
three.g(one);//当传参进来使用时,使用的是表面类型
}
}
输出:
g(Cone)
CTwo.f
分析:表面类型是COne,实际类型是CTwo。
CThree类中使用了重载(Overload),Overload为静态多态,在编译阶段静态类型检查过程中决定了具体执行哪个方法,故其调用了其传入参数声明类型COne类的方法。
Java的方法中的参数传递是按值传递,引用所指向的表明类型的对象。
对象名.方法调用时使用的是实际类型。
子类CTwo中的f()方法重写了父类COne中的此方法,而最终执行结果是是调用的子类的方法,这说明方法的重写是动态。
当one作为参数传入时是作为声明类型COne父类传入的,也说明了重载是静态的。
静态类型和声明类型是在编译期间就是可知的,而实际类型要等到运行期间才可知,
通过继承,子类拥有与父类同名的非私有的属性和方法,子类重新定义某个属性,在父类不会被覆盖,该属性的对象在编译时的类型为父类,运行时为子类。
对于对象调用运行时类型的方法便是出于对多态的考虑。
例子2
class A{
int i=1;
void say(){
System.out.println(i);
}
}
class B extends A{
int i=2;
void say(){
System.out.println(i);
}
}
public class Main{
public static void main(String args[]) {
A a=new B();
System.out.println(a.i); //编译-- 父类
a.say(); //运行-- 子类
}
}
输出:
1
2
在动态运行过程中,JVM调用的方法为其实际类型所重写的方法。
例子3
class Base{
void test() {
System.out.println("Base.test()");
}
}
public class Child extends Base {
void test() {
System.out.println("Child.test()");
}
static public void main(String[] a) {
Child anObj = new Child();
Base baseObj = (Base)anObj; //父类引用指向子类对象 子类对象赋给父类 真实对象是子类
baseObj.test();
}
}
输出:Child.test()
分析:子类对象向上转换成父类(子类对象赋值给父类对象)。
子类重写父类的方法,编译时为父类对象,运行时为子类对象。
- 此时通过父类引用变量调用的方法是子类覆盖或继承父类的方法,不是父类的方法。
- 此时通过父类引用变量无法调用子类特有的方法。
所以调用的是子类方法。