Java变量自增表达式 i = i++ 的底层逻辑(简述)

java · 浏览次数 : 5

小编点评

**i = i++ 的底层逻辑** `i = i++` 的运算过程通过操作数栈实现: 1. 将 `i` 的值存储到操作数栈中。 2. 执行 `i++`表达式,更新 `i` 的值。 3. 从操作数栈中取出 `i` 的值并将其赋给 `i`。 **操作数栈的参与** `i = i++` 中,操作数栈扮演着至关重要的角色: * **存储 `i` 的值:** 在 `i = i++` 的运算中,`i` 的值被存储到操作数栈中。 * **保存 `i` 的原始值:** 在自增操作中,`i` 的值是先从操作数栈中取出并赋值给 `i` 的。 **结论** `i = i++` 的结果是 `i` 的值保持不变,因为 `i` 的值在操作数栈中被存储并从操作数栈中取出后被赋值给 `i`。

正文

Java变量自增表达式 i = i++ 的底层逻辑(简述)


前言

很多老师告诉我们,i = i++ 的运算过程是 temp = i; i ++; i = temp; 所以i的值不变。但我总觉得这个temp的出现有些莫名其妙。所以在网上检索之后,把大佬们的解释做了一点总结和简化,权当拾人牙慧。

要搞懂 i = i++ 我们先要简单认识两个东西:局部变量表操作数栈

  • 操作数栈:一个临时的存储空间, 主要用于保存计算过程的变量和中间结果,
  • 局部变量表:也是一个临时的存储空间,它用于保存函数的参数以及局部变量。
  • 对于本文章而言,初学者可以把它看做两个桶:一个用来装i的值(局部变量表),另一个用来装中间过程中用到的数(操作数栈)

正文

了解了以上内容,就可以对i=i++进行理解了。不过在此之前,我们先看看单独的i++ 和 ++i的区别:

i++ 和 ++i

i++ 和 ++i,它们实际上是直接在局部变量表里修改变量的值,原地修改,不需要经过操作数栈。所以,作为语句单独使用时,没有区别。

public static void main(String[] args) {
    int i = 0;
    i++; // 在局部变量表直接自增
    ++i; // 在局部变量表直接自增,没有区别
}

那么当情况是 i = i++时; 为什么结果就是i的值不变呢?

i = i++

public static void main(String[] args) {
    int i = 0;
    i = i++; // i值不变
}

这就是操作数栈参与的结果,上面代码的执行过程,实际上是这样的:

  • 执行 int i = 0;
    • 把0这个常数放到操作数栈中
    • 从操作数栈顶取出常数0,然后存储到局部变量表的索引为1的位置(局部变量表[1]),这个位置就代表i的值(因为局部变量表里有args 和 i 两个元素,args的索引是0)。
  • 执行 i = i++;
    • 计算机首先看见右侧表达式中的i, 所以它把局部变量表[1]的值取出,压入操作数栈。
    • 计算机又看见了符号“++”,于是把局部变量表[1]进行自增
    • 然后计算机看见 “=” ,所以对等号左边的i进行赋值,重点来了
      • 这里赋的是哪个值呢? -- 操作数栈里的值,0。
      • 那么赋值到哪里呢? -- 局部变量表[1]。直接覆盖了自增的结果,也就是说,刚刚的自增操作,增了个寂寞。
    • 所以我们就知道了:由于刚刚是“先压栈,再自增”,所以栈里的值还是原始值,最后又覆盖回去了。
  • 同理,我们也就知道它和 i = ++i 的不同之处在哪里了
    • 计算机这次首先看见的是“++”符号,而不是i, 所以它这次先把局部变量表[1]进行自增
    • 然后计算机才看见了i,此时才把局部变量表[1]的值取出,压入操作数栈,因此,栈里的值也变成了1,最后覆盖回去就是1。

以上顺序的变化,实际上在JVM编译后的字节码文件中能够直观地看到,但是初学者对字节码很陌生,所以采用了以上的描述方式。字节码的区别其实更加直观,如下:

  • i = i++ 的执行顺序:
iconst_0  # 把int常量0压入操作数栈
istore_1  # 把操作数栈顶的值"0"存储到局部变量表[1]
iload_1   # 局部变量表[1]的值压入操作数栈顶,此时操作数栈顶为0
iinc 1 1  # 将局部变量表[1]的值,加上1, 所以此时i的值变成1
istore_1  # 将操作数栈顶的值存储到局部变量表[1], 用0覆盖了1.
  • i = ++i 的执行顺序:
iconst_0  # 同上
istore_1  # 同上
iinc 1 1  # 将局部变量表[1]的值,加上1, 此时i的值变成1 
iload_1   # 局部变量表[1]的值压入操作数栈顶,此时操作数栈顶为1
istore_1  # 将操作数栈顶的值存储到局部变量表[1], 用1覆盖了1.

这个问题的进阶还有 k = i + ++i * i++ 参考文章:https://blog.csdn.net/See_Star/article/details/125206538


作者: 练块儿的程序员

出处:https://www.cnblogs.com/sunyujun16

本文版权归作者和博客园共有,欢迎转载,但必须保留此段声明,且在文章页面明显位置给出原文链接, 如有问题,可邮件sunyujun16@163.com咨询.

与Java变量自增表达式 i = i++ 的底层逻辑(简述)相似的内容:

Java变量自增表达式 i = i++ 的底层逻辑(简述)

Java变量自增表达式 i = i++ 的底层逻辑(简述) 前言 很多老师告诉我们,i = i++ 的运算过程是 temp = i; i ++; i = temp; 所以i的值不变。但我总觉得这个temp的出现有些莫名其妙。所以在网上检索之后,把大佬们的解释做了一点总结和简化,权当拾人牙慧。 要搞懂

Java 自增自减运算符和移位运算符介绍

摘自 JavaGuide (「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!) 自增自减运算符 在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式

读书笔记丨远程服务调用和RESTful,如何分析和抉择?

摘要:相信未来REST规范将会变得更加流行和普及。 本文分享自华为云社区《云原生时代,远程服务调用和RESTful,如何分析和抉择?》,作者:breakDawn 。 随着云原生的概念越来越火,服务的架构应该如何发展和演进,成为很多程序员关心的话题。大名鼎鼎的《深入理解java虚拟机》一书作者于21年

Java静态变量在静态方法内部无法改变值

一、如何解决“Java静态变量在静态方法内部无法改变值”的问题 在Java中,静态变量(也称为类变量)属于类本身,而不是类的任何特定实例。它们可以在没有创建类的实例的情况下访问和修改。如果我们发现在静态方法内部无法改变静态变量的值,这通常是因为我们的代码中有一些逻辑错误或误解。 下面是一个简单的示例

ndk开发之native层访问java层

一.native层访问java层的成员变量 java层的成员变量可以分为实例变量和静态变量,不过他们的访问方法比较类似,可以分为以下三步: 获取java类对应的jclass对象 获取需要访问的成员变量的jfieldID 根据需要访问的变量的类型,调用setXXXField()/getXXXField

[转帖]【JVM】堆内存与栈内存详解

堆和栈的定义 java把内存分成栈内存和堆内存。 (1)栈内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。 当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另

java中判断String类型为空和null的方法

1.判断一个String类型的变量是否为空(即长度为0)或者为null 在Java中,判断一个String类型的变量是否为空(即长度为0)或者为null,通常需要使用两个条件语句来进行检查。这是因为null表示变量没有引用任何对象,而空字符串("")表示变量引用了一个没有内容的字符串对象。 下面是一

ORA-01008:并非所有变量都已绑定-解决办法

近期批量处理数据,后台用JAVA编写,连接Oracle数据库,程序运行报ORA-01008问题。解决这个问题时遇见的坑较多,下面复盘现象、问题提出解决办法,希望能帮到遇见同类问题的你。 调试问题: 后台代码: /** * 插入操作的封装 * **/ public static void insert

@RequiredArgsConstructor和@Authwired

我们在java后端书写接口时,对service层成员变量的注入和使用有以下两种实现方式: **1) @RequiredArgsConstructor** ``` import lombok.RequiredArgsConstructor; import org.springframework.web

Java 21 新特性:Unnamed Patterns and Variables

Java 21中除了推出JEP 445:Unnamed Classes and Instance Main Methods之外,还有另外一个预览功能:未命名模式和变量(Unnamed Patterns and Variables)。该新特性的目的是提高代码的可读性和可维护性。 下面通过一个例子来理解