起因是一个群友提出的问题,问以下Java代码的输出的结果是什么?

int i = 1;
int j = ((++i) + (++i));
System.out.println(j);

众所周知,单个的++i赋值是先计算+1然后计算,单个的i++服之是先赋值后+1计算,但,多个操作复合起来就少有人知了。

接下来我们来研究一下。

经过测试,易得,结果是5。那么这个结果是怎么得到的呢?

首先我们将这段代码反编译一下,可得字节码如下:

// access flags 0x9
public static main([Ljava/lang/String;)V
// parameter args
L0
LINENUMBER 5 L0
ICONST_1
ISTORE 1
L1
LINENUMBER 6 L1
IINC 1 1
ILOAD 1
IINC 1 1
ILOAD 1
IADD
ISTORE 2
L2
LINENUMBER 7 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L3
LINENUMBER 12 L3
RETURN
L4
LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
LOCALVARIABLE i I L1 L4 1
LOCALVARIABLE j I L2 L4 2
MAXSTACK = 2
MAXLOCALS = 3

先看这一段字节码:

L0
LINENUMBER 5 L0
ICONST_1
ISTORE 1

可以知道,变量i的指针是1,初始值为1

ICONST 是指使用了int的几个常量(<=5)的指针,同理还有LCONST对应long,DCONST对应double。

注意:这里的_1中的1,是指针,指向了常量。

注意:非常量数值的数据压入操作会有所不同。

ISTORE 是将操作数栈顶上的int数值赋予本地变量,同理还有LSTORE,DSTORE等。

注意:ISTORE参数为指针

再来看来看这一段字节码:

L1
LINENUMBER 6 L1
IINC 1 1
ILOAD 1
IINC 1 1
ILOAD 1
IADD
ISTORE 2

首先将变量进行了IINC操作,给变量+1,其次使用ILOAD操作读取变量数值(当前为2),加入到操作数栈中。然后下面重复这个操作(当前为3)。最后调用了IADD进行了加法操作,将操作数栈中的两个数值2+3的结果赋予变量2(就是代码中的j变量)。

最后输出结果5。

IINC不影响操作数栈,但是会改对应变量的值,所以不需要重新使用ISTORE压入。

同理我们再来测试i++,代码如下:

int i = 1;
int k = ((i++) + (i++) + (i++));
System.out.println(k);

字节码如下:

// access flags 0x9
public static main([Ljava/lang/String;)V
// parameter args
L0
LINENUMBER 9 L0
ICONST_1
ISTORE 1
L1
LINENUMBER 10 L1
ILOAD 1
IINC 1 1
ILOAD 1
IINC 1 1
IADD
ILOAD 1
IINC 1 1
IADD
ISTORE 2
L2
LINENUMBER 11 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 2
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L3
LINENUMBER 12 L3
RETURN
L4
LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
LOCALVARIABLE i I L1 L4 1
LOCALVARIABLE k I L2 L4 2
MAXSTACK = 2
MAXLOCALS = 3

注意这一段字节码:

L1
LINENUMBER 10 L1
ILOAD 1
IINC 1 1
ILOAD 1
IINC 1 1
IADD
ILOAD 1
IINC 1 1
IADD
ISTORE 2

可以看到,这里是先使用ILOAD操作读取数据到操作数栈中,然后再使用IINC进行自增,这里就体现了i++++i的区别。

至此,我们已经彻底了解了i++++i

但是,强烈不推荐这种写法,这种代码难以理解和维护。

参考文档

[1] javase11 jvm doc