举例来说。
public void store(int age, String name) { int temp = age + 2; String str = name; }
通过 jclasslib 看一下 store() 方法的字节码指令。
istore_3:从操作数中弹出一个整数,并把它赋值给局部变量表中索引为 3 的变量。
astore 4:从操作数中弹出一个引用数据类型,并把它赋值给局部变量表中索引为 4 的变量。
通过查看局部变量表就能关联上了。
02、算术指令
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。可以分为两类:整型数据的运算指令和浮点数据的运算指令。
需要注意的是,数据运算可能会导致溢出,比如两个很大的正整数相加,很可能会得到一个负数。但 Java 虚拟机规范中并没有对这种情况给出具体结果,因此程序是不会显式报错的。所以,大家在开发过程中,如果涉及到较大的数据进行加法、乘法运算的时候,一定要注意!
当发生溢出时,将会使用有符号的无穷大 Infinity 来表示;如果某个操作结果没有明确的数学定义的话,将会使用 NaN 值来表示。而且所有使用 NaN 作为操作数的算术操作,结果都会返回 NaN。
举例来说。
public void infinityNaN() { int i = 10; double j = i / 0.0; System.out.println(j); // Infinity double d1 = 0.0; double d2 = d1 / 0.0; System.out.println(d2); // NaN }
任何一个非零的数除以浮点数 0(注意不是 int 类型),可以想象结果是无穷大 Infinity 的。
把这个非零的数换成 0 的时候,结果又不太好定义,就用 NaN 值来表示。
Java 虚拟机提供了两种运算模式:
向最接近数舍入:在进行浮点数运算时,所有的结果都必须舍入到一个适当的精度,不是特别精确的结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值接近,将优先选择最低有效位为零的(类似四舍五入)。
向零舍入:将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果(类似取整)。
我把所有的算术指令列一下:
加法指令:iadd、ladd、fadd、dadd
减法指令:isub、lsub、fsub、dsub
乘法指令:imul、lmul、fmul、dmul
除法指令:idiv、ldiv、fdiv、ddiv
求余指令:irem、lrem、frem、drem
自增指令:iinc
举例来说。
public void calculate(int age) { int add = age + 1; int sub = age - 1; int mul = age * 2; int div = age / 3; int rem = age % 4; age++; age--; }
通过 jclasslib 看一下 calculate() 方法的字节码指令。
iadd,加法
isub,减法
imul,乘法
idiv,除法
irem,取余
iinc,自增的时候 +1,自减的时候 -1
03、类型转换指令
可以分为两种:
1)宽化,小类型向大类型转换,比如 int–>long–>float–>double,对应的指令有:i2l、i2f、i2d、l2f、l2d、f2d。
从 int 到 long,或者从 int 到 double,是不会有精度丢失的;
从 int、long 到 float,或者 long 到 double 时,可能会发生精度丢失;
从 byte、char 和 short 到 int 的宽化类型转换实际上是隐式发生的,这样可以减少字节码指令,毕竟字节码指令只有 256 个,占一个字节。
2)窄化,大类型向小类型转换,比如从 int 类型到 byte、short 或者 char,对应的指令有:i2b、i2s、i2c;从 long 到 int,对应的指令有:l2i;从 float 到 int 或者 long,对应的指令有:f2i、f2l;从 double 到 int、long 或者 float,对应的指令有:d2i、d2l、d2f。
窄化很可能会发生精度丢失,毕竟是不同的数量级;
但 Java 虚拟机并不会因此抛出运行时异常。
举例来说。
public void updown() { int i = 10; double d = i; float f = 10f; long ong = (long)f; }
通过 jclasslib 看一下 updown() 方法的字节码指令。
i2d,int 宽化为 double
f2l, float 窄化为 long