java中的多态分为重载和重写,我们先来看一下这个代码
/**
* Created by kaizige on 2017/8/27.
*/
class Animal{
}
class Cat extends Animal{
}
class Dog extends Animal{}
public class People {
public void say(Animal animal){
System.out.println("animal say");
}
public void say(Cat cat){
System.out.println("cat say");
}
public void say(Dog dog){
System.out.println("dog say");
}
public static void main(String[] args) {
Animal cat=new Cat();
Animal dog=new Dog();
People p=new People();
p.say(cat);
p.say(dog);
}
}
1.上面的代码运行输出会是
animal say
animal say
这是因为在编译时期已经确定了People类调用的会是say(Animal animal)这个方法。在方法的接收者已经确定为People的前提下,根据传入方法的参数的数量和数据类型可以确定使用哪个版本的方法。这其实就是java中方法的重载。
为什么在编译的时候就能确定方法的版本呢?这要从类的加载说起。
java的类加载,链接,初始化,使用,卸载5个阶段。而其中链接阶段又包括验证,准备,解析阶段。
加载阶段需要做三个事情:
1.通过类的全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
3.在内存中生成一个代表这个类的java.lang.Class对象,最为方法区数据的访问入口。
验证阶段:主要验证格式是否符合当前虚拟机的要求。这主要是针对不是从编译器编译来的class文件。比如从网络获取的。
准备阶段:主要是对类变量分配内存并且设置初始值。类变量是指类中的静态字段。初始值是指数据类型的零值(0,0.0,null等)。如果是常量,这一步会直接赋值了。
比如定义为static final 的变量,此时就会直接赋值了。
解析阶段:将常量池内的符号引用转换为直接引用的过程。这一阶段就是之前所说的在编译时期确定方法版本的原因了。
java中虚拟机里面提供了5条方法调用字节码指令。分别是:
invokestatic:调用静态方法,
invokespecial:调用实例构造器,私有方法,父类方法
invodevirtual:调用所有的虚方法,
invokeinterface:调用接口方法
invokedynamic:调用点限定符。。
在解析阶段,虚拟机会把一部分方法调用中的目标方法在常量池中的符号引用转换为直接引用。即invokestatic和invodespecial所对应的方法。这部分方法在编译器可知,运行期不可变。因此在编译时刻就能确定方法的调用了。这部分方法称为非虚方法。相反其他方法称为虚方法(final方法除外,final方法也是确定的)。对虚方法的调用就是重写了。
那么重写又是怎么回事呢?还是先看一下下面的代码:
package com.liu.practice;
/**
* Created by kaizige on 2017/8/27.
*/
class Animal{
public void say(){
System.out.println("animal say");
}
}
class Cat extends Animal{
public void say(){
System.out.println("cat say");
}
}
class Dog extends Animal{
public void say(){
System.out.println("dog say");
}
}
public class People {
public static void main(String[] args) {
Animal cat=new Cat();
Animal dog=new Dog();
cat.say();
dog.say();
}
}
上面的代码会输出:
cat say
dog say
这是因为:在java虚拟机执行引擎执行的时候,是按照栈帧的结构执行的,在调用每一个方法的时候,会在当前线程中加入一个栈帧,栈帧中的数据有:局部变量表,操作数栈,动态链接,返回地址等。这个动态链接存储的就是该栈帧所属方法的引用。这个动态链接就是重写实现的原因了。当调用上面的say方法时,其实就是前文所说的invokevirtual方法调用。invokevirtual方法指令执行的第一步就是在运行期间确定接受者的实际类型,然后把常量池中的符号引用解析到不同的直接引用上去。
而怎么解析的呢:这要从方法区中的方法表说起,父类引用指向子类对象。在方法区中会有父类的方法表和子类的方法表。方法在方法表中是按一定的索引排序的,父类中的方法如果在子类中重写了,则该方法在父类中的索引下标是多少,那么在子类方法表的这个下标也就是对应的子类重写的方法。在上面所说的栈帧中的动态链接就会指向这个方法入口,因此实现了重写。
参考书籍:深入理解java虚拟机