作者Mahmoud Anouti
实例创建:
让我们修改示例代码,引入一个Point类来封XY坐标。
编译后的main方法的字节码如下:
新的指令有new,dup和invokespecial。与编程语言中的new操作符类似,new指令创建一个对象,对象的类型在传递给它的操作对象中指定(对于Point类的一个符号引用)。在堆中为对象分配内存,对象的引用被压入操作数栈。dup指令复制操作数栈顶的值,这意味着,在栈顶有两个指向Point对象的引用。接下来的三个指令把构造器的三个参数(用来实例化对象)入栈,然后调用一个特殊的初始化方法,也就是构造器。下一个方法就是x和y属性初始化的地方。方法结束后,栈顶的三个值被消费,剩下的就是指向创建对象的原始引用(也就是到现在为止的成功初始化的对象)。
接下来,astore_1弹出Point引用并把它赋值给序号为1的本地变量(astore_1中的a表明这是一个引用值)。
创建和初始化的相同过程在第二个Point实例上进行重复,它被赋值给了变量b。
最后一步是从序号为1和2的本地变量中加载两个Point对象的引用(分别使用aload_1和aload_2),并使用invokevirtual指令调用area方法,它基于对象的实际类型,把调用分发到正确的方法上。比如,如果变量a是一个从Point继承的SpecialPoint的实例类型,那么重写方法就会被调用。在这个例子中,没有子类,因而只有一个可用的area方法。
注意,即使area方法只有一个参数,栈顶也会有两个Point引用。第一个(pointA,来自变量a)是被调用方法所属的实例(在编程语言中被称作this),它会被传递给area方法新的栈帧的第一个局部变量。另外一个操作数值(pointB)是area方法的参数。
结论
得益于字节码指令集的简洁性,以及生成字节码时几乎没有进行编译器的优化,所以在没有源码的情况下,拆装class文件可能是一种检查变化的办法,如果确实需要这样做的话。