码神手记-资深攻城狮的私房笔记,微信公众平台/知乎/头条同步,动动小手,点个关注!
在面向JVM的编译(一)-常量、局部变量以及控制结构的使用 中,介绍过for循环的编译,今天再补充一些其它的控制结构(while循环与if-else)以及方法参数的接收。本篇内容同样需要你先了解JVM运行时数据区域的基础知识。
while循环与if-else
Java代码:
void whileInt() { int i=0; while (i < 100) { i++; }}
JVM代码:
Method void whileInt()0 iconst_0 //把整型常量0压入操作数栈1 istore_1 //从操作数栈中弹出0并存为局部变量12 goto 8 //强制转移控制到指令数组中索引为8的指令5 iinc 1 1 //局部变量1的值加18 iload_1 //将局部变量1的值(0)压入操作数栈9 bipush 100 //将整数100压入操作数栈11 if_icmplt 5 //比较栈顶两个数的大小,如果i<100,控制转移到索引为5的指令。否则继续执行下一条指令14 return //返回Void
与之前for循环的例子是同样的情况,在以上JVM代码中,判断两数大小的指令(if_icmplt)位于JVM代码的底部。使用goto指令强制转移控制,以便在第一次迭代之前先执行判断(或者叫test)。其它的控制结构也是以类似的方式被编译,但需要注意:指令与数据类型要匹配。
将上面例子中的数据类型换为double:
void whileDouble() { double i=0.0; while (i < 100.1) { i++; }}
编译后的JVM代码如下:
Method void whileDouble()0 dconst_0 //把double类型常量0压入操作数栈1 dstore_1 //从操作数栈中弹出0并存入局部变量12 goto 9 //控制转移到索引为9的指令5 dload_1 //将局部变量1压入操作数栈6 dconst_1 //把double类型常量1压入操作数栈7 dadd //弹出栈顶的两个double值,相加结果压入操作数栈8 dstore_1 //弹出相加结果存为局部变量1(原来的局部变量1被覆盖)9 dload_1 //将局部变量1压如操作数栈,i的当前值10 ldc2_w #4 //从运行时常量池中加载索引为4的double值,即100.113 dcmpg //比较栈顶两个double值的大小,即比较i的当前值与100.1的大小。14 iflt 5 //如果i<100.1,则控制转移到索引为5的指令,否则继续执行后续指令17 return //返回Void
每个浮点数据类型都有两个比较指令:fcmpl和fcmpg用于float类型,dcmpl和dcmpg用于double类型,这些指令针对NaN会特殊处理。NaN是无序的,只要在操作数中有一个NaN,浮点比较的结果就会是失败(False)。在数据类型章节中有提到过NaN,NaN属于浮点类型,是浮点值集的一员,代表某些无效操作比如0.0/0.0,注意这里的0是浮点类型。
关于dcmpg指令做一些补充,如果操作数存在NaN或者不满足比较条件,将会向操作数栈压入整数1,如果两个操作数相等则压入0,如果满足比较条件则压入-1。iflt指令判断的是1,0,-1,另外还有ifge、ifle指令,和iflt同理。
方法参数的接收
实例方法
当一个实例方法被调用时,会在栈中创建一个新的栈帧(即当前栈帧)。如果该方法有n个参数,那么这n个参数将会按照顺序存入当前栈帧局部变量表索引1到n的位置。
示例:
int addTwo(int i,int j){ return i+j;}
编译后:
Method int addTwo(int,int)0 iload_1 //将局部变量1(i)压入操作数栈1 iload_2 //将局部变量2(j)压入操作数栈2 iadd //从操作数栈弹出i和j,执行相加操作,将int类型的结果压入操作数栈3 ireturn //从操作数栈弹出计算结果,返回一个int数值
上文提到方法的参数被存入局部变量表索引1到n的位置,那么索引为0的位置存储的是什么?按照约定,索引为0的位置存储的是一个引用,指向的是被调用实例方法所属的对象实例。在Java编程语言中,可以通过this关键字访问到这个对象实例。
类方法
类(static)方法没有实例,因此参数会从索引0开始存入局部变量表。
示例:
static int addTwoStatic(int i,int j){ return i+j;}
编译后:
Method int addTwoStatic(int,int)0 iload_0 //将局部变量0(i)压入操作数栈1 iload_1 //将局部变量2(j)压入操作数栈2 iadd //从操作数栈弹出i和j,执行相加操作,将int类型的结果压入操作数栈3 ireturn //从操作数栈弹出计算结果,返回一个int数值
从例子中可以明显看出类方法与实例方法在参数接收上的区别,类方法的参数直接从局部变量表的索引0开始存储。
码神手记-资深攻城狮的私房笔记,微信公众平台/知乎/头条同步,动动小手,点个关注!