方法调用
在java中,方法调用有两类,动态方法和静态方法
静态方法:对类的静态方法的调用,在编译时就已经确定好了具体的调用方法,是静态绑定的
动态方法:需要有方法调用所作用的对象,是在调用得时候才确定具体的方法,是动态绑定的
多态就是动态方法的调用
多态概念
多态有两种:类内部之间的多态和类之间的多态
多态是面向对象编程语言的重要特性,他允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定
- java方法的重载(类内部之间的多态): 在类中可以创建多个方法,他们具有相同的名字,但可以有不同的参数列表,返回值类型
- java方法的重写(父类与子类之间的多态):
子类可以继承父类中的方法,但有时子类不想继承,而是做一定的修改,就需要方法的重写,重写的参数列表不可以修改.
jdk7以后返回类型可以是父类返回值的派生类
修饰符不能小于父类方法的修饰符
类之间的多态
继承和接口方式实现多态
public class Father {
public void dealHouse(){
System.out.println("父亲管理房子");
}
public static void main(String[] args) {
Father father=new Father();
Father sonA=new sonA();
Father sonB=new sonB();
father.dealHouse();
sonA.dealHouse();
sonB.dealHouse();
}
}
class sonA extends Father{
@Override
public void dealHouse(){
System.out.println("A儿子管理房子");
}
}
class sonB extends Father{
@Override
public void dealHouse(){
System.out.println("B儿子管理房子");
}
结果
- 父亲管理房子
- A儿子管理房子
- B儿子管理房子
分析多态
- jvm内存
不论是类内部之间还是类之间的多态,发现这些方法的名字都是一样的,那程序在调用的时候是如何区分的呢
java编译器将代码编译成class文件,通过类装载器把class装载到jvm中,最后执行,最关键的是方法区
方法区存的是类的信息
方法区中的类类型信息和堆中的对象是不同的,在方法区中,class的类型信息只有唯一的实例,而在堆中可以有多个该类的对象,方法区中的类型信息就是一个模板
Father sonA=new sonA();
Father sonB=new sonB();
当程序运行到Father sonA=new sonA()这里就出现了多态,这是因为编译时看到Father,但是运行时new出来一个sonA类,两种类型不一样
- Father sonA是一个引用类型,存在了java栈中的本地变量表中
- new sonA其实创建了一个实例对象,存储在了java堆中
- sonA的类类型数据存在方法区中
调用流程:
- 虚拟机通过reference(Father的引用)查询java栈中的本地变量表,得到堆中的对象类型数据的指针
- 通过到对象的指针找到方法区中的对象类类型数据
- 查询方法表定位到sonA类的方法运行
方法表
方法表存储在方法区中,他是实现多态的关键,里面保存的是实例方法的引用,是直接引用,java虚拟机在执行程序的时候通过这个方法表来确定一个多态方法
每个类都有一个方法表,子类中方法表不同的方法指向不同的类类型信息,继承自Object的就指向Object,继承自Father就指向Father(也就是包含了父类的方法dealHouse),那为什么指向的是父类dealHouse,执行的确实子类的dealHouse方法呢,
当Son类的方法表会有一个指向父类dealHouse方法的指针,同时也有指向自己dealHouse方法的指针,这时候新的数据会覆盖原有的数据,也就是说原来指向Father.dealHouse的引用会被替换成son.dealHouse的引用
注意
上面是对继承实现多态的分析,如果是接口实现的,过程不一样.jvm对于接口的方法调用是采用搜索方法表的方式,如,要在Father接口的方法中找到dealHouse方法,必须搜索Father的整个方法表,从效率上说,接口方法的调用慢于类方法的调用