面试官:你说你懂i++跟++i的区别,那你知道下面这段代码的运行结果吗?面试官:“说一说i++跟++i的区别” 我:“i++是先把i的值拿出来使用,然后再对i+1,++i是先对i+1,然后再去使用i” 面试官:“那你看看下面这段代码,运行结果是什么?” public static void main(String[] args) { int j = 0; for (int i = 0; i < 10; i++) { j = (j++); } System.out.println(j);}
“以我多年的开发经验来看,它必然不会是10” 面试官: 我:“哈哈…,开个玩笑,结果为0啦” 面试官:“你能说说为什么吗?” 我:“因为j++这个表达式每次返回的都是0,所以最终结果就是0” 面试官:“小伙子不错,那你能从JVM的角度讲一讲为什么嘛?” 我心想:这货明显是在搞事情啊,这么快就到JVM了?还好我有准备。 首先我们知道,JVM的运行时数据区域是分为好几块的,具体分布如下图所示:
那么现在虚拟机栈就可以表示成下面这个样子: 其中的局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
操作数栈对于数据的存储跟局部变量表是一样的,但是跟局部变量表不同的是,操作数栈对于数据的访问不是通过下标而是通过标准的栈操作来进行的(压入与弹出),之后在分析字节码指令时我们会很明显的感觉到这一点。另外还有,对于数据的计算是由CPU完成的,所以CPU在执行指令时每次会从操作数栈中弹出所需的操作数经过计算后再压入到操作数栈顶。 以执行下面这段代码为例:
这个过程如下所示 这两步完成了局部变量a的赋值,同理b的赋值也一样,a,b完成赋值后此时的状态如下图所示 此时要执行a+b的运算了,所以首先要将需要的操作数加载到操作数栈,执行运算时再将操作数从栈中弹出,由CPU完成计算后再将结果压入到栈中,整个过程如下: 到这里还没有完哦,还剩最后一步,需要将计算后的结果赋值给c,也就是要将操作数栈的数据弹出并赋值给局部变量表中的第三个槽位 OK,到这一步整个过程就完成了 面试官:“嗯,说的不错,但是你还是没解释为什么最开始的那个问题,为什么 我:“面试官您好,要解释这个问题上面的知识都是基础,真正要说明白这个问题我们需要从字节码入手。” 我们进入到这段代码编译好的.class文件目录下执行:
我们着重关注第10,11,14行字节码指令,用图表示如下: 可以看到本来局部变量表中的j已经完成了自增( 我们平常说的i++是先拿去用,然后再自增,而++i是先自增再拿去用。这个到底怎么理解呢?如果站在JVM的层次来讲的话,应该这样说:
这就是它们的根本区别。 最后我这里放出一段代码及其字节码,我相信看完这篇文章你对于i++及++i的理解绝对跟原来不一样了 public static void main(String[] args) { int i = 4; int b = i++; int a = ++i;}public static void main(java.lang.String[]);Code: 0: iconst_4 1: istore_1 2: iload_1 3: iinc 1, 1 6: istore_2 7: iinc 1, 1 10: iload_1 11: istore_3 12: return
这段代码大家自行思考,有任何问题可以给我留言哦~ 码字不易,记得点个赞哈~ ps:
|
|