在讲 i++与++i以前先看两个在笔试面试中常常遇到的题目: java
题目1:面试
//代码1
int x=2;
int b=(x++)*3;
System.out.println(b);
/*
输出结果为:6
*/
复制代码
题目2:bash
//代码1
int i=0;
for(int j=0;j<100;j++)
i=i++;
System.out.println(i);
/*
输出结果为:0
*/
复制代码
题目3:jvm
public int inc()
{
int x;
try {
x=1;
return x;
} catch (Exception e) {
x=2;
return x;
}
finally{
x=3;
}
}
复制代码
上面的输出结果在Java中是正确的,跟Java自身的处理机制有关,Java虚拟机执行字节码是基于栈的体系结构。ui
下面就来具体分析一下为何会有如此奇怪的结果,在Java中变量在运算的时候涉及到两个区域这两个区域分别为 stack 区域和 local variable 区域,stack 在变量进行运算时会用到,而local variable区域用来保存局部变量的值。spa
先看看题目1的字节码:3d
L0
LINENUMBER 7 L0
ICONST_2
ISTORE 1
L1
LINENUMBER 8 L1
ILOAD 1
IINC 1 1
ICONST_3
IMUL
ISTORE 2
L2
LINENUMBER 9 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L3
LINENUMBER 14 L3
RETURN
复制代码
先对上面的指令进行一个讲解:指针
① ICONST_2:将2压入到stack中。code
②ISTORE 1:将stack中的值取出存放到 local variable 区域的位置1处。orm
③ILOAD 1:local variable 区域的位置1处的值压入到stack中。
④IINC 1 1:将local variable 区域的位置1处的值加1。
⑤ICONST_3:将3压入到stack中。
⑥IMUL:弹出stack中的两个元素,相乘将结果压入到stack中。
⑦ISTORE 2:将stack中的结果取出存放到local variable区域。
下面是题目1运行时stack和local variable区域的值的状况:
① ICONST_2 :将2压入到stack中。
②ISTORE 1:将stack中的值取出存放到 local variable 区域的位置1处。
③ILOAD 1:local variable 区域的位置1处的值压入到stack中。
④IINC 1 1:将local variable 区域的位置1处的值加1。
⑤ICONST_3:将3压入到stack中。
⑥IMUL:弹出stack中的两个元素,相乘将结果压入到stack中。
⑦ISTORE 2:将stack中的结果取出存放到local variable区域。
最后输出的是local variable中位置2的值,上面计算结果比较好理解关键在于④IINC 1 1这一步直接将local variable中位置1的值加1并非放到stack中加1,stack中的2后面一直未改变。也就是说stack至关于一个中转站。
题目1清楚后题目2也是比较好理解的,下面直接上题目2的字节码,对照着字节码看更清楚整个流程。
L0
LINENUMBER 7 L0
ICONST_0
ISTORE 1
L1
LINENUMBER 8 L1
ICONST_0
ISTORE 2
L2
GOTO L3
L4
LINENUMBER 9 L4
FRAME APPEND [I I]
ILOAD 1
IINC 1 1
ISTORE 1
L5
LINENUMBER 8 L5
IINC 2 1
L3
FRAME SAME
ILOAD 2
BIPUSH 100
IF_ICMPLT L4
L6
LINENUMBER 10 L6
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L7
LINENUMBER 11 L7
RETURN
//-----------------------------------
字节码
public int inc();
Code:
Stack=1,Locals=5,Args_size=1
0:iconst_1//try块中的x=1
1:istore_1
2:iload_1//保存x到returnValue中,此时x=1
3:istore 4
5:iconst_3//finaly块中的x=3
6:istore_1
7:iload 4//将returnValue中的值放到栈顶,准备给ireturn返回
9:ireturn
10:astore_2//给catch中定义的Exception e赋值,存储在Slot 2中
11:iconst_2//catch块中的x=2
12:istore_1
13:iload_1//保存x到returnValue中,此时x=2
14:istore 4
16:iconst_3//finaly块中的x=3
17:istore_1
18:iload 4//将returnValue中的值放到栈顶,准备给ireturn返回
20:ireturn
21:astore_3//若是出现了不属于java.lang.Exception及其子类的异常才会走到这里
22:iconst_3//finaly块中的x=3
23:istore_1
24:aload_3//将异常放置到栈顶,并抛出
25:athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any
复制代码
题目3中涉及到异常,可是对题目3的分析可以让咱们更加理解jvm的运行机制。这段代码的返回值应该是多少?
对Java语言熟悉的读者应该很容易说出答案:若是没有出现异常,返回值是1;若是出现了Exception异常,返回值是2;若是出现了Exception之外的异常,方法非正常退出,没有返回值。
分析一下题目3字节码的执行过程,从字节码的层面上看看为什么会有这样的返回结果。
字节码中第0~4行所作的操做就是将整数1赋值给变量x,而且将此时x的值复制一份副本到最后一个本地变量表的returnValue中若是这时没有出现异常,则会继续走到第5~9行,将变量x赋值为3,而后将以前保存在returnValue中的整数1读入到操做栈顶,最后ireturn指令会以int形式返回操做栈顶中的值,方法结束。
若是出现了异常,PC寄存器指针转到第10行,第10~20行所作的事情是将2赋值给变量x,而后将变量x此时的值赋给returnValue,最后再将变量x的值改成3。
方法返回前一样将returnValue中保留的整数2读到了操做栈顶。
从第21行开始的代码,做用是变量x的值赋为3,并将栈顶的异常抛出,方法结束。
注意:理解整个过程须要注意的一点是return x不是直接返回x的值返回的是x的一个副本,遇到return x时就local variable中保存一个x的副本,而后再返回 x的副本。