首先上难以理解的结论:
* 成员方法(非静态)(运行看子类
, 编译看父类
):
* 编译时, 如果父类没有, 则编译失败.
* 运行时, 多态调用的方法用的是子类
的方法, 如果子类没有则找父类的.
* 成员变量(运行编译看父类
):
* 编译时, 如果父类没有, 则编译失败.
* 运行时, 多态调用的成员变量用的是父类
的.
再上演示代码:
父类
public class Person {
@Override
public String toString(){
return "I'm a person.";
}
public void eat(){
System.out.println("Person eat");
}
public void speak(){
System.out.println("Person speak");
}
}
子类1: 男孩
public class Boy extends Person{
@Override
public String toString(){
return "I'm a boy";
}
@Override
public void speak(){
System.out.println("Boy speak");
}
public void fight(){
System.out.println("Boy fight");
}
}
子类2: 女孩
public class Girl extends Person{
@Override
public String toString(){
return "I'm a girl";
}
@Override
public void speak(){
System.out.println("Girl speak");
}
public void sing(){
System.out.println("Girl sing");
}
}
测试代码块
public static void main(String[] args) {
Person boy = new Boy();
Person girl = new Girl();
System.out.println(boy); //==> I'm a boy
boy.eat(); //==>Person eat
boy.speak(); //==>Boy speak
//boy.fight();
System.out.println(girl); //==>I'm a girl
girl.eat(); //==>Person eat
girl.speak(); //==>Girl speak
//girl.sing();
}
运行结果
理解了上面的代码, 接下来看下上述代码相关的内存模型
个人觉得这张图非常清晰.
要深入理解多态, 首先应该有下面相关概念:
1. 我认为Java能够实现多态的重要原因是有方法的动态绑定. 这也就是为什么成员变量没有多态调用一说, 方法却有. 实现动态绑定主要是基于下面说的方法表.
2. 方法表
, 简单理解就是方法的列表, 每个类都有对应的方法表, 对于父子类同名方法在方法表中具有相同的索引号(专业点说, 就是具有相同签名的方法,在父类、子类的方法表中具有相同的索引号).
3. 多态调用方法的大致过程: java编译器将java源代码编译成class文件,在编译过程中,会根据静态类型将调用的符号引用写到class文件中。在执行时,JVM根据class文件找到调用方法的符号引用,然后在静态类型的方法表中找到偏移量(我的理解就是索引号),然后根据this指针确定对象的实际类型1,使用实际类型的方法表,偏移量跟静态类型中方法表的偏移量一样,如果在实际类型的方法表中找到该方法,则直接调用,否则,按照继承关系从下往上搜索。
看到这里应该基本对多态调用的机制有了一个大概的认识了, 我这里主要为了做笔记, 如果想进一步深入了解, 请参考下面的博客:
http://www.2cto.com/kf/201603/496508.html
this
指针起了关键作用, 它相当于使得子类即使披着父类的妆容, 也不忘自身到底是谁. ↩