Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >面试官:【int i = 6; i += i - 1; 】i 等于什么?

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

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

代码 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 删除。

评论
登录后参与评论
1 条评论
热度
最新
我以为10呢
我以为10呢
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
Java基础:深度解析自增操作在JVM底层的执行过程
在 Java 中,前自增和后自增运算符的差异体现在 ‌变量值的修改顺序‌ 和 ‌表达式返回值的时机‌。通过 JVM 字节码分析可以清晰理解其底层逻辑。
Java快速干线
2025/06/11
920
Java基础:深度解析自增操作在JVM底层的执行过程
1、引言
栈帧对应一个线程的一个方法的内容,用于方法的执行,包括方法执行过程中的变量的临时状态。同时栈帧也执行动态链接,方法的返回值以及分发异常。栈帧被包含在JVM栈中。每一个栈帧包括:
文彬
2022/06/06
4010
1、引言
通过字节码理解try-catch-finally
结合这个异常表和Code中的注释,可以发现,如果try语句中发生了Exception及其子类异常,那么执行的字节码为第8-16行,最终返回值为2。其他异常的话,则跳到第17行处理,执行第17-23行,最终将异常抛出,方法值没有返回。
Java架构师必看
2021/11/29
3770
一个try-catch问出这么多花样【面试题】
上面代码的字节码部分如下图所示(其中红色的字为解析,下面会对详细内容进行解释)
CBeann
2023/12/25
1660
一个try-catch问出这么多花样【面试题】
i++和++i傻傻分不清楚?这里给你最清楚的解答
本篇文章将介绍——自增变量,这是面试常见的问题,说难不难,说简单也不简单,需要面试者冷静思考,判断正确符号之间的优先级。
wangweijun
2020/02/11
6290
JVM-虚拟机栈(操作数栈(Operand Stack))
1 操作数栈的特点 每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last - In - First -Out的 操作数栈,也可以称之为表达式栈(Expression Stack) 操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop) 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈,比如:执行复制、交换、求和等操作 2 操作数栈的作用 操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程
utopia
2023/03/21
6820
写Java不懂Java系列之加载和存储
很多Java工程师语法用的很666,但是真的让他说说Java是怎样编译运行的,我相信他会懵逼!!!
shysh95
2021/01/28
4410
写Java不懂Java系列之加载和存储
5.java内存模型详细解析
Description of Java Conceptual Diagram(java结构)
用户7798898
2021/10/13
3470
手写一个简单的JVM--02.模拟运行JVM
虚拟机栈是栈帧的集合的统称,栈帧是虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素,每一个方法对应了一个栈帧。
付威
2020/06/11
1.5K0
JVM - 结合代码示例彻底搞懂Java内存区域_线程栈 | 本地方法栈 | 程序计数器
字节码文件被装载子系统装载到JVM中,字节码执行引擎负责执行这些字节码文件。 装载子系统和执行引擎都是C++的实现。
小小工匠
2021/08/17
3670
敖丙字节一面:能聊聊字节码么?
上一篇《你能和我聊聊Class文件么》中,我们对Class文件的各个部分做了简单的介绍,当时留了一个很重要的部分没讲,不是敖丙不想讲啊,而是这一部分实在太重要了,不独立成篇好好zhejinrong 讲讲都对不起詹姆斯·高斯林
敖丙
2022/04/19
3490
敖丙字节一面:能聊聊字节码么?
程序员进阶系列:年少不懂爱家家,懂了已是猿中人。
时隔多年,回想起那个面试场景,忍不住要感叹:年少不懂i++(爱家家),如今懂了却已是老码农(双鬓白)。
一猿小讲
2020/09/01
3330
程序员进阶系列:年少不懂爱家家,懂了已是猿中人。
初识JVM指令执行流程
摘要: 记录下学习JVM指令执行流程的理解 正文: 初识JVM指令执行流程 /** * 0: aload_0 * 1: invokespecial #1 // Method java/lang/Object."<init>":()V * 4: return * * @author liugang * @since 2018-04-28 */ public class Example1 { /** * 为主方法创建一个frame并将其推入线程
itliusir
2018/05/21
5120
【Java 虚拟机原理】栈帧 | 局部变量表 | 操作数栈 | 方法出口 | JVM 指令逐条解析
" 栈帧 " 中存储的是 局部变量表 , 操作数栈 , 动态链接 , 方法出口 ;
韩曙亮
2023/03/29
4180
【Java 虚拟机原理】栈帧 | 局部变量表 | 操作数栈 | 方法出口 | JVM 指令逐条解析
[JVM] JVM自动内存管理机制(一)
文本主要就JVM结构和字节码文件,进行分析来展开JVM的学习,后续系列文章会从JVM的多个方面的进行知识总结。
架构探险之道
2019/09/09
5520
[JVM] JVM自动内存管理机制(一)
2.1 jvm内存模型
Description of Java Conceptual Diagram(java结构)
用户7798898
2020/09/27
4210
2.1 jvm内存模型
栈帧之操作数栈(Operand Stack)和动态链接(Dynamic Linking)解读
每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的 操作数栈,也可以称之为表达式栈(Expression Stack)
一个风轻云淡
2023/10/15
3810
栈帧之操作数栈(Operand Stack)和动态链接(Dynamic Linking)解读
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
字节码文件中保存了源代码编译之后的内容,以二进制的方式存储,无法直接用记事本打开阅读。通过NotePad++使用十六进制插件查看class文件:
寻求出路的程序媛
2024/09/04
990
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
如何从字节码角度分析Java问题
很简单的两行代码,如果是你遇到这样的问题,你会怎样去把问题解释清楚?是利用Java运算符顺序将式子拆解,然后一步步运算,还是其他什么办法?
叫我阿柒啊
2022/05/09
6470
如何从字节码角度分析Java问题
手把手教你 javap 反编译分解代码,授人以鱼不如授人以渔
我之前写了一篇关于class文件重要性的,并且从宏观角度解释了下class文件的构成,文章直通车(不直通了,都在这个JVM专辑里面)
阿甘的码路
2020/09/26
5960
手把手教你 javap 反编译分解代码,授人以鱼不如授人以渔
推荐阅读
相关推荐
Java基础:深度解析自增操作在JVM底层的执行过程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档