看了这篇文章:
https://www.ibm.com/developerworks/cn/java/j-lo-polymorph/
有一些疑问。
class Person {
public String toString(){
return "I'm a person.";
}
public void eat(){}
public void speak(){}
}
class Boy extends Person{
public String toString(){
return "I'm a boy";
}
public void speak(){}
public void fight(){}
}
class Girl extends Person{
public String toString(){
return "I'm a girl";
}
public void speak(){}
public void sing(){}
}
//最后这么用的话,
Person girl = new Girl();
girl.speak();
按照作者的说法,girl引用变量指向堆中的 Girl对象,所以运行的时候知道当前对象是Girl对象,找Girl的方法表,
找到后执行。这对于speak方法来说是行得通的。
但是如果这样呢:
girl.sing();
按照作者的说法,去Girl方法表找到sing方法,然后执行。
实际情况大家都知道有错误,编译不通过。
这中间肯定漏了点我不知道的什么?希望可以赐教。
换个方式问也就是:
当发生向上转型的时候,栈和堆中分别发生了什么?
PHPz2017-04-17 17:21:39
源代码就是这个样子,还是有省略的东西,把完整代码贴一下吧,按你代码现在这个样子,你是没把
Person girl = new Girl()
girl.speak()
这两句应该放在main里面,你直接在类中调用,结构不对呀
黄舟2017-04-17 17:21:39
是变量的声明类型决定了它能调用哪些方法,而不是它指向的堆中的对象的实际类型。
像你这样:
Person girl = new Girl();
girl.speak();
girl
的声明类型是Person
,而Person
并没有speak
这个方法,所以会报错。注意是编译器报的编译期的错误,编译器是不管你那个girl
执行的类型到底是什么。
运行期,虚拟机当然会知道girl
执行的堆中对象是Girl
类型。
编译器为什么要根据变量的声明类型来决定能不能调用某个方法?其中一个原因就是,编译期间可能根本不知道这个变量在运行期绑定的到底是什么类型的对象。举个例子:
Person p = Math. random() > 0.5 ? new Girl() : Boy();
这个例子中的p
不到运行时根本没法确定它绑定的对象的真实类型。
ringa_lee2017-04-17 17:21:39
题主的问题涉及到了多态概念。下面用题主的例子来说明:
假设使用题主定义的三个类Person
、Girl
和Boy
。当
Person ps = new Girl();
定义一个对象ps
以后,它在编译时的类型是Person
,而实际运行时的类型是Girl
。
在使用ps
这个对象时,程序只知道它是个Person
,而一个Person
只有eat
和speak
两个方法,所以在调用对象成员的时候只能调用eat
和speak
两个方法;程序并不知道ps
对象的实际类型是Girl
,所以不能调用Girl
独有的sing
方法。即:
ps.eat(); // OK,调用父类实现
ps.speak(); // OK,调用子类实现
ps.sing(); // Error
那么,既然程序并不知道ps
对象的实际动态类型是Girl
,为什么ps.speak();
还能调用Girl
中的实现呢?这就是多态的表现。
对于一个指向子类的父类指针/引用,如果子类中重写了父类的方法,那么在通过父类指针/引用调用这个方法的时候,编译器会在编译时把这个父类的方法调用动态绑定到实际类型的方法上去。而那些没有被子类重写的方法,则会静态绑定到父类的方法上去。
这是从抽象角度来理解,更底层的实现方法就要参考具体实现了,在题主提供的文章中讲的比较清楚。