i = i++

i = i++

一、 从一道笔试题说起

问:下列语句的输出结果是多少

public class Integer_int {
    public static void main(String[] args) {
        int i = 0;
        i = i++;
        System.out.println(i);
    }
}

根据自增后缀形式的语法,i++是先将i的值给外层表达式,然后将i的值加1。按照这种解释,那i = i++的执行过程便是:

  1. 先将i的初始值0赋值给i,此时i的值是0
  2. 然后i的值加1,此时i的值是1

按照上面的过程,输出结果应该是1。可是,实际运行之后上面的输出结果是0,i的值并没有改变!!!

二、 前置知识

要想彻底理解i=i++的执行流程,我们必须从虚拟机层面对其进行探究。在这之前,需要简单了解以下几个前置知识:

2.1 局部变量表和操作数栈

在JVM中,每个方法都会有一个对应的方法调用帧栈,方法调用帧栈包含以下两个重要组成部分(还包含其他部分,但这里只需要知道这两个即可):

  • 局部变量表:是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量
    • 局部变量表的索引从0开始
    • 对于非静态方法,局部变量表索引为0的位置用于存储对当前对象的引用,即this关键字所引用的对象
    • 对于静态方法,包括main方法,局部变量表索引为0的位置并没有特殊的用途,它像其他索引位置一样,用于存储方法的参数或局部变量
    • main方法中,索引为0的位置通常用于存储传递给方法的第一个参数,即字符串数组args
  • 操作数栈:用于存储计算过程中的中间值

2.2 一些JVM指令的简介

  • iconst_0:iconst是一个入栈指令,用于将 int 类型的数压入操作数栈。比如iconst_0便是将 int 类型常量 0 压入操作数栈栈顶
  • istore_1:istore的作用是将操作数栈栈顶的数据弹出,并将其存储到局部变量表中相应的变量中。比如istore_1便是将操作数栈栈顶的数据弹出,然后存放到局部变量表中索引为1的变量中
  • iload_1:iload的作用是将局部变量表中相应位置的 int 类型变量的值加载到操作数栈栈顶。比如iload_1的作用便是将局部变量表中索引为1的 int 值加载到操作数栈顶
  • iinc 1 by 1:innc的作用是对局部变量进行自增减操作,它可以将指定的 int 类型的局部变量增加或减少指定的值。 比如iinc 1 by 1的作用便是将局部变量表中索引为1的 int 变量的值增加1,这条指令通常用于实现变量自增的操作

三、 从JVM角度看 i = i++

public class Integer_int {
    public static void main(String[] args) {
        int i = 0;
        i = i++;
        System.out.println(i);
    }
}

将上述代码进行编译,然后对得到的字节码文件进行反编译可以得到 main 函数对应的指令:

0 iconst_0
1 istore_1
2 iload_1
3 iinc 1 by 1
6 istore_1
7 return

为了方便理解,结合以下示意图对其进行说明:

请添加图片描述

  1. 0 iconst_0:

    将整数 0 压入操作数栈栈顶

  2. 1 istore_1:

    将操作数栈栈顶的整数(0)弹出,并放入局部变量表中索引为 1 的变量,即将0赋值给变量 i(索引为 0 的地方用于存放 main 函数的参数 args)

  3. 2 iload_1:

    将局部变量表中索引为 1 的变量的值(即 i 的值0)取出压入操作数栈栈顶,此时操作数栈栈顶的值是0

  4. 3 iinc 1 by 1

    将局部变量表中索引为 1 的变量的值(即 i 的值0)加1,此时局部变量表中 i 的值是1

  5. 6 istore_1

    将操作数栈栈顶的整数(0)弹出,并放入局部变量表中索引为 1 的变量,即将局部变量表中 i 的值修改为 0,此时 i 的值又变成了0

其中:

  • 步骤1以及步骤2完成了变量i的初始化工作,即对应的是语句int i = 0;
  • 步骤3~步骤4对应的是语句i = i++;

四、 再看题目

从上面的JVM指令可以清晰的看到,虽然i的值也经过了自增的操作,即3 iinc 1 by 1。但继续往下执行,当执行到第五步6 istore_1时,i的值又被修改回了最初的值。因此 i = i++并不会修改 i 的值!!!

五、 从JVM角度看 i = ++i

将上面代码中的i = i++改为i = ++i,编译之后反编译得到以下指令:

0 iconst_0
1 istore_1
2 iinc 1 by 1
5 iload_1
6 istore_1
7 return

同样,结合以下示意图对其进行说明:

请添加图片描述

  1. 0 iconst_0:

    将整数 0 压入操作数栈栈顶

  2. 1 istore_1:

    将操作数栈栈顶的整数(0)弹出,并放入局部变量表中索引为 1 的变量,即将0赋值给变量 i(索引为 0 的地方用于存放 main 函数的参数 args)

  3. 2 iinc 1 by 1

    将局部变量表中索引为 1 的变量的值(即 i 的值0)加1,此时局部变量表中 i 的值是1

  4. 5 iload_1:

    将局部变量表中索引为 1 的变量的值(即 i 的值1)取出压入操作数栈栈顶,此时操作数栈栈顶的值是1

  5. 6 istore_1

    将操作数栈栈顶的整数(1)弹出,并放入局部变量表中索引为 1 的变量,此时 i 的值还是1

其中:

  • 步骤1以及步骤2完成了变量i的初始化工作,即对应的是语句int i = 0;
  • 步骤3~步骤4对应的是语句i = ++i;

六、 自增操作再理解

基于上面的理解,我们使用表格对比了k = i++k = ++i的执行过程:

k = i++k = ++i
第一步将局部变量表中i的值放入操作数栈将局部变量表中i的值加1
第二步将局部变量表中i的值加1将局部变量表中i的值放入操作数栈
第三步将操作数栈中的值赋值给k将操作数栈中的值赋值给k

从上面的表格可以看出,在JVM的角度,变量 i 中存放的值并不是直接赋值给了 k, 而是经过了操作数栈。只是:

  • 对于i++,是先将 i 的值放入操作数栈再自增。因此,k 从操作数栈中获取到的是未自增前的 i 的值,这也正好对应了语法层面上 i 先将值传给外部表达式再自增的效果
  • 对于++i,是 i 的值先自增再将自增后的值放入操作数栈中。因此,k 从操作数栈中获取到的是自增后的 i 的值,这也正好对应了语法层面上 i 先自增再将自增后的值传给外部表达式的效果

进一步地,当把k换成i后,也便有了i = i++执行完之后 i 的值不变的情况,因为最后 i 的值是来自操作数栈,而操作数栈中的值是 i 未自增前的值

七、 笔/面试题目

  1. 问:变量 i 的初始值为10,分别执行完以下语句后 i 的值是多少

    A. i = i++;
    B. i = ++i
    

    答:根据上面的内容,可知:执行完语句 A 后 i 的值不变仍然是10,执行完语句 B 后 i 的值为 11

  2. 问:下列语句的输出结果

    public class Integer_int {
        public static void main(String[] args) {
            int i = 10;
            i = i++ + ++i + i++ + ++i;
            System.out.println(i);
        }
    }
    

    答:

    1. 首先,确定i = i++ + ++i + i++ + ++i的执行顺序。在Java中,自增运算符的优先级高于加法运算符,而赋值运算符的优先级最低。因此,可以确定该语句的执行顺序为:
      1. 先执行自增操作
      2. 将每次自增操作的返回值相加
      3. 将相加结果赋值给i
    2. 计算过程如下
      1. 当第一个 i++执行完后,返回10 ,i的值自增为11
      2. 当第一个 ++i执行完后,i的值自增为12,返回12
      3. 当第二个 i++执行完后,返回12,i的值自增为13
      4. 当第二个 ++i执行完后,i的值自增为14,返回14
      5. 将上述结果的返回值相加:10+12+12+14=48
    3. 最终结果为48
  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值