引言
i++和++i的区别在学习程序设计的时候应该就已经学过:一个是用完再加,一个是加完再用。那么考虑一下下面的代码:
int i = 0;
for (int j = 0; j < 100; j++)
i = i++;
这个运行完,i的值应该是多少呢?
深入理解自增
上面的代码运行完,i的值不是100,也不是101,而是0!这个结果的原因搜一下,会看到一个新的专业术语中间缓存变量,i = i++
这行代码相当于:
int tmp = i;
i++;
i = tmp;
单单知道这个也没用,你只是以后遇到i = i++
知道它是怎么处理的,但是换一个i = ++i + i++
,这个又是怎么运算的呢?
所以我们看一下经过javac编译后的int i = 0; i = i++
字节码到底是什么样子的,用javap -verbose
命令得到:
0: iconst_0 常数0入栈
1: istore_1 将栈顶元素移入本地变量1号位置存储
2: iload_1 本地变量1号位置入栈
3: iinc 1, 1 将本地变量1号位置加1
6: istore_1 栈顶元素移入本地变量1号位置
7: return 返回
对于JVM来说,最初的Java代码就是执行这7行字节码,每一行的意思我在后面都解释了,比较简单,主要是要理解,对于一个方法来说,都相当于在栈里压入了一个栈帧,在其中会有局部变量区和操作栈,前者就是存储局部变量的,后者是存操作数的(符合栈的规律,栈顶元素先出)
PC寄存器就是记录当前运行到哪一行指令了。
再比较一下i = ++i
的字节码:
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: istore_1
7: return
字节码对应的操作其实都一样,只不过是顺序不一样。
i++是将局部变量区的值先load到栈中,再对局部变量区的值进行增加,而++i则是先对局部变量区的值进行增加,之后再load到栈中。这也很好的解释了为什么一个是用完再加,一个是加完再用。
再看看i = ++i + i++
的字节码:
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: iload_1
7: iinc 1, 1
10: iadd 取栈顶的两个元素相加,再把结果压入栈
11: istore_1
12: return
是不是也是一样的,只是iinc与iload的顺序不一样而已。所以以后再遇到这种情况,只要自己画一个操作栈,一个局部变量区,结果就显而易见了。
static的自增
static int i;
public static void main(String[] str) {
i = ++i + i++;
}
对于这一段代码,它的字节码是:
0: getstatic #2 // Field i:I
3: iconst_1
4: iadd
5: dup 取当前栈顶元素复制一份,并压入栈中
6: putstatic #2 // Field i:I
9: getstatic #2 // Field i:I
12: dup
13: iconst_1
14: iadd
15: putstatic #2 // Field i:I
18: iadd
19: putstatic #2 // Field i:I
22: return
没有用到iinc,用了dup和iadd替代了。getstatic和putstatic分别是获取指定类的实例域,并压入栈以及为指定的类的静态域赋值。
对于++i,对应的字节码是先iadd,再dup,然后putstatic,这样dup的值就是加后值,而对于i++,对应的字节码是先dup,再iadd,再putstatic,这样dup的值就是原始的值。
虽然类静态变量和局部变量的字节码对应的操作不一样,但是结果都是一样的。
练习
public static void main(String[] str) {
int i = 0;
i = i++ + ++i;
int j = 0;
j = ++j + j++ + j++ + j++;
int k = 0;
k = k++ + k++ + k++ + ++k;
int h = 0;
h = ++h + ++h;
int p1 = 0, p2 = 0;
int q1 = 0, q2 = 0;
q1 = ++p1;
q2 = p2++;
StringBuilder builder = new StringBuilder();
builder.append("i:").append(i).append("\n")
.append("j:").append(j).append("\n")
.append("k:").append(k).append("\n")
.append("h:").append(h).append("\n")
.append("p1:").append(p1).append("\n")
.append("p2:").append(p2).append("\n")
.append("q1:").append(q1).append("\n")
.append("q2:").append(q2);
System.out.println(builder.toString());
}
代码结果是:
i:2
j:7
k:7
h:3
p1:1
p2:1
q1:1
q2:0
如果答案都是正确的那么就差不多可以过关了,如果有什么问题再自己去javap去研究研究字节码吧。