深入理解Java中的自增

引言

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去研究研究字节码吧。

猜你喜欢

转载自blog.csdn.net/wf632856695/article/details/77478535