当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类:
1、编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法。
2、运行时才能确定是哪个方法,这也正是多态的实现原理。
对于第一种方法的调用,有2个字节码指令:invokestatic,invokespecial
invokestatic:调用static方法(不需要通过类的实例就可以调用),这很好理解。静态方法属于整个类型,就一份,没有歧义。
invokespecial:调用private方法、构造方法,super方法。private方法是不会被子类继承下去的,所以不会有歧义,这和通过super去调用父类的方法是一个道理。
对于第二种方法的调用,有2个字节码指令:invokevirtual,invokeinterface
invokevirtual:这部分的方法运用了方法表,对方法表中的方法的直接引用表现为偏移量(1、2、3.....),我认为这就像是数据库中的索引。在子类的方法表中,从父类继承下来的方法总是在方法表的前面,而子类重写的方法在方法表中指向自己的实现,于是多态得以实现。当调用方法时,JVM通过具体的对象引用找到实际类,进而找到正确的方法。
invokeinterface:和invokevirtual的实现一样,但是针对接口的方法的实现,在各个实现类的方法表中,不能通过偏移量来快速查找,因为各个方法表中的偏移量可能不相同,只能全表扫描,所以速度会慢很多。
tips:A a = new B(),这里的A叫做静态类型,B叫做实际类型,A针对第一种调用,B针对第二种调用。
JVM与对象初始化
一个对象从无到有的过程
A a = new A()
1、JVM遇到new指令就会去堆内存分配一块内存空间,内存的大小在编译期间就可以确定
2、接着调用A的构造函数,这里构造的时候会沿着继承树逆流而上,一直到Object。
先看一段代码:
package jvm.test3;
public class Sup {
public int a;
public String b;
Sup(int a, String b){
System.out.println("Sup constructor!!!");
System.out.println(this.getClass().getName());
this.a = a;
this.b = b;
this.printMsg();
}
private void printMsg(){
System.out.println("Sup: " + "a=" + a + " b=" + b);
}
}
package jvm.test3;
public class Sub extends Sup{
Sub(int a, String b) {
super(a, b);
System.out.println("Sub constructor!!!");
}
}
package jvm.test3;
public class Test {
public static void main(String[] args) {
new Sub(11,"hello");
}
}
输出:
Sup constructor!!!
jvm.test3.Sub
Sup: a=11 b=hello
Sub constructor!!!
这段代码中,我主要想说一下第10行和第13行。
先说说this。在实例方法中,JVM会默认隐藏的传递一个参数,这个参数就是当前调用的实例本身,在方法内就可以通过this操作。
但是等等,在Sup的构造方法中,this却是jvm.test3.Sub类的实例。为什么???
从头再看初始化顺序:new Sub(11,"hello"),new指令会分配一块内存存放Sub对象的数据,然后返回一个引用(假设叫ref)指向这个对象。JVM会去调用Sub的构造方法,并将ref隐藏的传递给构造方法,所以在Sub的构造方法中的this就是ref。但是,Sub的构造方法先去调用父类Sup的构造方法,而JVM这时候隐藏传递进去的还是ref,还是指向Sub实例的引用。
但是又产生一个问题,竟然是Sub的实例,为什么能调用父类Sup的私有方法printMsg???
我想到一种解释:隐藏的参数的类型是本类型。什么意思?就是说在Sup类的所有方法中,隐藏的参数的类型是Sup。所以,当传来一个Sub类的引用ref的时候,进行了一次向上转型。
虽然我觉得非常合理,但是还没有找到验证的方法。大家有懂的或者有其他的想法可以指正。