i = i++ 最终值不变,因执行 iload(取旧值)→ iinc(局部变量自增)→ istore(存旧值覆盖);i = ++i 最终值加1,因执行 iinc(局部变量自增)→ iload(取新值)→ istore(存新值)。

操作数栈是JVM执行引擎的核心组件之一,它是一个后进先出(LIFO)的栈结构,用于暂存计算过程中的中间结果。i=i++ 和 i=++i 表面看只差一个位置,但字节码层面的指令顺序不同,直接导致操作数栈与局部变量表的交互方式不同——这才是差异的本质。
i=i++ 的字节码执行流程
以 int i = 1; i = i++; 为例,关键指令序列如下:
- iload_1:把局部变量表索引1处的当前值(1)压入操作数栈 → 栈顶为 1
- iinc 1 by 1:对局部变量表索引1处的值直接加1 → 此时局部变量表中 i 已变为 2,但操作数栈仍为 1
- istore_1:把操作数栈顶的值(1)弹出,写回局部变量表索引1 → i 被覆盖回 1
整个过程没有“丢值”,而是严格按“取旧值→自增→存旧值”三步走。操作数栈始终只保留了自增前的副本,最终覆盖了刚完成的自增结果。
i=++i 的字节码执行流程
对应 int i = 1; i = ++i;,指令顺序发生调换:
- iinc 1 by 1:先对局部变量表索引1处的值加1 → i 变为 2
- iload_1:再把更新后的值(2)压入操作数栈 → 栈顶为 2
- istore_1:把栈顶值(2)写回局部变量表索引1 → i 保持为 2
关键区别在于:iload_1 发生在 iinc 之后,读取的是已更新的值。操作数栈拿到的是“新值”,所以赋值后结果不变。
为什么不能只看“先加后用”这种说法?
因为“先用后加”或“先加后用”只是语义描述,真正决定行为的是指令在字节码中的**相对顺序**和**数据流向**:
- i++ 的核心是 iload → iinc → istore:操作数栈取的是自增前的快照
- ++i 的核心是 iinc → iload → istore:操作数栈取的是自增后的结果
- 所有自增动作(iinc)都作用于局部变量表,不触碰操作数栈;而赋值(istore)永远以操作数栈顶为准
脱离操作数栈与局部变量表的协作机制去谈“谁先谁后”,容易误判复杂表达式(如 i = i++ + ++i)的行为。
一个直观对比:栈与变量表的状态变化
初始:局部变量表 [?, 1](索引0为args,索引1为i),操作数栈为空
- i=i++ 后:局部变量表 [?, 1],操作数栈经历 [1] → [ ] → [1]
- i=++i 后:局部变量表 [?, 2],操作数栈经历 [ ] → [2] → [ ]
可见,差异不在“是否自增”,而在于**哪一时刻把哪个值送入操作数栈**——这决定了后续 istore 写回去的是什么。

















