前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:【int i = 6; i += i - 1; 】i 等于什么?

面试官:【int i = 6; i += i - 1; 】i 等于什么?

原创
作者头像
不惑
发布2024-09-20 08:28:57
2751
发布2024-09-20 08:28:57
举报
文章被收录于专栏:面经

代码 int i = 6; i += i - 1;,我们来逐步分析:

初始赋值int i = 6;,即变量 i 的值初始化为 6。

表达式解析i += i - 1; 这一行等价于 i = i + (i - 1);

  • i - 1:当前 i 的值是 6,因此 i - 1 计算结果为 6 - 1 = 5。
  • i + (i - 1):此时 i 的值仍然是 6,因此 6 + 5 = 11。

赋值:最终,i 被赋值为 11。

因此,在这段代码执行完毕后,i 等于 11

为什么第一个 i 没有变成 5;

要理解为什么 i 的值在表达式中不会中途变化,需要深入了解表达式求值的顺序变量赋值的机制,尤其是在 Java 中是如何处理变量的。

表达式求值顺序

在表达式 i += i - 1; 中,Java 遵循一个确定的求值顺序。这是基于 Java 语言的运算符优先级和求值顺序规则。Java 是一种严格从左到右求值的语言,这意味着:

  • 当执行 i += i - 1; 时,右侧表达式 i - 1 首先被计算,然后将结果赋值给 i
  • 注意:在整个右侧表达式 i - 1 计算过程中,i 的值并不会在中途发生变化。所有在表达式中引用的 i,都是指向它在表达式开始时的值。

具体来说,i - 1 的值是基于 i 的当前值来计算的,而这个计算过程不会影响当前 i 的值。

详细过程:

  • 第一步i 的初始值为 6
  • 第二步:先计算 i - 1。此时,i 还是 6,因此 i - 1 = 6 - 1 = 5
  • 第三步:将 i + 5 进行计算,此时 i 仍然是 6,故 i + 5 = 6 + 5 = 11
  • 第四步:最后,将计算结果 11 赋值给 i,所以 i = 11

整个右侧表达式的计算是在独立的内存空间中完成的,并不影响变量 i 在此时的值,直到计算结果最终赋值给 i

变量和赋值机制

Java 的赋值机制是右结合

  • i += i - 1; 中,表达式右侧 i - 1 的计算先于赋值进行。也就是说,右侧的表达式 i - 1 使用的 i 是表达式开始时的值,并且直到右侧计算完成后,新的值才会覆盖原来的 i

Java 的赋值是后期赋值

  • += 操作符表示的是先计算再赋值。整个右侧表达式(i - 1)必须完全计算完毕,Java 才会将结果写回给左侧的变量 i
  • Java 语言中的赋值操作不会在中途改变左侧变量的值,直到所有右侧的计算完全结束。

这是因为计算右侧表达式时,Java 会先保存当前的 i,并基于这个值计算整个表达式。然后,它再执行最终的赋值操作。即:

  • 计算 i - 1 时,i 的值仍然是 6。
  • 计算 i + (i - 1) 时,i 的值仍然是 6。
  • 最后,才将计算结果 11 赋值给 i

内存模型与表达式求值

在 Java 的内存模型(JMM)中,局部变量存储在每个线程的栈内存中。由于这段代码是在单线程中运行的,并且没有跨线程的可见性问题,局部变量的值始终是对该线程可见且一致的。在整个求值过程中,i 的值不会因为计算的进展而改变。

关键点

  • 表达式右侧的所有运算是在完成后才将结果赋值给 i
  • 直到右侧的整个表达式完成,变量 i 的值在内存中是稳定的,不会中途发生变化。

编译过程中的优化

编译器在处理表达式时,也会进行一定的优化,但这些优化不会影响程序的语义。在这种情况下,编译器可能会将变量 i 的值暂时保存在寄存器中,直到计算完成之后才将最终结果写入内存中的 i。这同样保证了 i 的值在表达式求值过程中不会改变。

JMM 下的多线程场景(补充)

虽然在单线程的情况下不涉及 Java 内存模型(JMM)的复杂性,但在多线程场景下,如果变量 i 是共享的,且没有正确的同步机制(如 volatile 关键字或 synchronized 块),那么不同线程对 i 的操作可能会产生可见性问题,导致线程间的 i 值不一致。但是在当前的单线程环境下,JMM 不是重点。

使用 Java 字节码(JVM 指令)分析

字节码解释

bipush 6 (0: bipush 6)

  • 指令将数值 6 压入操作数栈中。此时,操作数栈内容为:[6]

istore_1 (2: istore_1)

  • 这一步将操作数栈中的 6 保存到局部变量表的索引 1 处,即 i。同时,栈被清空:
    • 局部变量表:[i=6]
    • 操作数栈:[]

iload_1 (3: iload_1)

  • 从局部变量表加载 i 的值(6)到操作数栈中。此时,操作数栈内容为:[6]

iload_1 (4: iload_1)

  • 再次从局部变量表加载 i 的值(6)到操作数栈中。此时,操作数栈内容为:[6, 6]

iconst_1 (5: iconst_1)

  • 将常量 1 压入操作数栈。此时,操作数栈内容为:[6, 6, 1]

isub (6: isub)

  • isub 是整数减法操作,从操作数栈中弹出两个值,执行减法运算,将结果压回栈顶:
    • 弹出栈顶两个值:61
    • 执行 6 - 1 = 5
    • 将结果 5 压回栈顶。此时,操作数栈内容为:[6, 5]

iadd (7: iadd)

iadd 是整数加法操作,同样从操作数栈中弹出两个值,执行加法运算,将结果压回栈顶:

  • 弹出栈顶两个值:65
  • 执行 6 + 5 = 11
  • 将结果 11 压回栈顶。此时,操作数栈内容为:[11]

istore_1 (8: istore_1)

  • 最后,istore_1 将栈顶的值 11 存回局部变量表的索引 1 处,更新 i 的值为 11
    • 局部变量表:[i=11]
    • 操作数栈:[]

JVM 执行指令

在表达式 i += i - 1 的计算过程中,Java 虚拟机(JVM)按照以下原则来执行指令:

  1. 局部变量的加载与栈操作:在字节码执行 iload_1 时,局部变量表中的 i 的值是 6,这个值被多次加载到操作数栈中。每次从局部变量表加载 i 的值时,加载的是当前时刻局部变量表中的值。因此,即使后面的减法计算结果为 5,它不会立即改变局部变量表中 i 的值。
  2. 操作数栈与局部变量表是独立的:当你看到 i - 1 时,实际上 JVM 是将 i 的值(6)加载到操作数栈中,再执行减法(6 - 1 = 5)。但是,这个减法只是在操作数栈中计算,并不会影响局部变量表中 i 的值,直到整个表达式计算结束(即 i + (i - 1) 完成后)才会通过 istore_1 将结果 11 存入局部变量表,更新 i 的值。
  3. 延迟赋值:在表达式 i += i - 1 中,只有在 i + (i - 1) 的所有计算完成之后,结果 11 才会被赋值给 i。在整个计算过程中,局部变量表中的 i 保持不变,直到 istore_1 执行时才更新局部变量表。

JVM 的工作机制

JVM 使用操作数栈来执行大部分指令,并且局部变量表和操作数栈是相互独立的。每次从局部变量表加载 i 的值到操作数栈时,栈中的操作仅影响栈,而不会影响局部变量表中 i 的值。局部变量表中的 i 只有在 istore_1 这一指令执行后才更新。这就是为什么即使执行了 i - 1 的计算,第一个 i 的值仍然保持为 6,直到所有运算结束。

总结

  • 操作数栈局部变量表 是 JVM 中两个不同的内存区域。i 的值在被加载到操作数栈后,在局部变量表中的值不会改变,直到最终计算完成后通过 istore 进行赋值。
  • JVM 按照从左到右的顺序执行字节码指令,先执行 i - 1 的减法运算,再执行 i + (i - 1) 的加法,整个计算过程使用的是最初从局部变量表中加载的 i 值。
  • 只有当表达式计算结束时,新的结果 11 才会通过 istore_1 更新 i 的值。

因此,i 在中途不会变成 5,而是一直保持为 6,直到计算结果 11 最后被存储到局部变量表中。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么第一个 i 没有变成 5;
    • 表达式求值顺序
      • 详细过程:
    • 变量和赋值机制
      • Java 的赋值机制是右结合的
      • Java 的赋值是后期赋值
    • 内存模型与表达式求值
      • 关键点
    • 编译过程中的优化
      • JMM 下的多线程场景(补充)
      • 使用 Java 字节码(JVM 指令)分析
        • 字节码解释
          • JVM 执行指令
            • JVM 的工作机制
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档