美团一面问我i++跟++i的区别是什么

面试官:“i++跟++i的区别是什么?”

我:“i++是先使用然后再执行+1的操作,++i是先执行+1的操作然后再去使用i”

面试官:“那你看看下面这段代码,运行结果是什么?”

public static void main(String[] args) {
int j = 0;
for (int i = 0; i < 10; i++) {
j = (j++);
}
System.out.println(j);
}

我:“我猜他肯定不是10”

面试官:

我:“哈哈.....,开个玩笑,结果为0啦”

面试官:“为什么呢?”

我:“简单来说的话,j++这个表达式每次返回的都是0,所以最终结果就是0”

对应前文提到过的:i++这种写法是先使用,再执行+1操作,如果不理解请暂停多思考思考

面试官:“小伙子不错,那你能从更底层的角度讲一讲为什么嘛?”

首先我们知道,JVM的运行时数据区域是分为好几块的,具体分布如下图所示:



现在我们主要关注其中的虚拟机栈,关于虚拟机栈,我们需要了解的是:

  1. Java虚拟机栈是由一个个栈帧组成,线程在执行一个方法时,便会向栈中放入一个栈帧。
  2. 每一个方法所对应的栈帧又包含了以下几个部分
    • 局部变量表
    • 操作数栈
    • .........

其中的局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用。

局部变量表的最小存储单元为Slot(槽),其中64位长度的long和double类型的数据会占用2个Slot,其余的数据类型只占用1个。因此可以直接通过下标来进行数据访问

操作数栈对于数据的存储跟局部变量表是一样的,但是跟局部变量表不同的是,操作数栈对于数据的访问不是通过下标而是通过标准的栈操作来进行的(压入与弹出)

数据的计算是由CPU完成的,弹栈的目的就是将数据压入到CPU中

接下来我们分析下面这段代码在字节码层面的执行过程:

// 为方便阅读将对应代码也放到这里
public static void main(String[] args) {
int j = 0;
for (int i = 0; i < 10; i++) {
j = (j++);
}
System.out.println(j);
}

我们进入到这段代码编译好的.class文件目录下执行:javap -c xxx.class,得到其字节码如下:

  public static void main(java.lang.String[]);
Code:
0: iconst_0 // 将常数0压入到操作数栈顶
1: istore_1 // 将操作数栈顶元素弹出并压入到局部变量表中1号槽位,也就是j=0
2: iconst_0 // 将常数0压入到操作数栈顶
3: istore_2 // 将操作数栈顶元素弹出并压入到局部变量表中2号槽位,也就是i=0
4: iload_2 // 将2号槽位的元素压入操作数栈顶
5: bipush 10 // 将常数10压入到操作数栈顶,此时操作数栈中有两个数(常数10,以及i)
7: if_icmpge 21 // 比较操作数栈中的两个数,如果i>=10,跳转到第21行
10: iload_1 // 将局部变量表中的1号槽位的元素压入到操作数栈顶,就是将j=0压入操作数栈顶
11: iinc 1, 1 // 将局部变量表中的1号元素自增1,此时局部变量表中的j=1 14: istore_1 // 将操作数栈顶的元素(此时栈顶元素为0)弹出并赋值给局部变量表中的1号 槽位(一号槽位本来已经完成自增了,但是又被赋值成了0) 15: iinc 2, 1 // 将局部变量表中的2号槽位的元素自增1,此时局部变量表中的2号元素值为1,也就是i=1 18: goto 4 // 第一次循环结束,跳转到第四行继续循环
21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_1
25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
28: return

我们着重关注第10,11,14行字节码指令,用图表示如下:

可以看到本来局部变量表中的j已经完成了自增(iinc指令是直接对局部变量进行自增),但是在进行赋值时是将操作数栈中的数据弹出,但是操作数栈的数据并没有经过计算,所以每次自增的结果都被覆盖了,最终结果就是0。

我们平常说的i++是先使用,然后再自增,而++i是先自增再使用。这个到底怎么理解呢?如果站在JVM的层次来讲的话,应该这样说:

  1. i++是先被操作数栈拿去用了(先执行的load指令),然后再在局部变量表中完成了自增,但是操作数栈中还是自增前的值
  2. 而++1是先在局部变量表中完成了自增(先执行innc指令),然后再被load进了操作数栈,所以操作数栈中保存的是自增后的值

这就是它们的根本区别。

关于i++的执行过程,我这里也给出一个程序及编译后的结果

public static void main(String[] args) {
int i = 0;
i = ++i;
System.out.println(i);
}
>  0 iconst_0
> 1 istore_1
> 2 iinc 1 by 1
> 5 iload_1
> 6 istore_1
> 7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
> 10 iload_1
> 11 invokevirtual #3 <java/io/PrintStream.println : (I)V>
> 14 return

大家可以自行分析


作者简介

大三退学,创业、求职、自考,一路升级

7年it从业经验,多个开源社区contributor

半自由职业,新时代数字游民

自媒体创业,专注分享成长路上的所悟所得

长期探索 个人成长职业发展副业探索

美团一面问我i++跟++i的区别是什么的更多相关文章

  1. Java-线程池专题 (美团面试题)

    去美团面试,问到了什么是线程池,如何使用,为什么要用,以下做个总结 1.什么是线程池:  java.util.concurrent.Executors提供了一个 java.util.concurren ...

  2. Java-线程池专题 (美团)

    实现多线程的三种方式,继承Thread,实现Runnable 和 实现 Executor接口 ,具体参考:Java 多线程 三种实现方式 去美团,问到了什么是线程池,如何使用,为什么要用,以下做个总结 ...

  3. 关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题

    前言 开心一刻 有个同学去非洲援建,刚到工地接待他的施工员是个黑人,他就用英语跟人家交流,黑人没做声. 然后他又用法语,黑人还是没说话. 然后他用手去比划.黑人终于开口了:瞎比划嘎哈,整个工地都中国人 ...

  4. 《我想进大厂》之JVM夺命连环10问

    这是面试专题系列第五篇JVM篇. 说说JVM的内存布局? Java虚拟机主要包含几个区域: 堆:堆Java虚拟机中最大的一块内存,是线程共享的内存区域,基本上所有的对象实例数组都是在堆上分配空间.堆区 ...

  5. JVM重新认识(一)oop-klass模型--HSDB使用验证

    一:oop-kclass模型 思考:我们平时写的java类编译成.class文件,JVM加载.class文件,那么加载.class文件之后在JVM中就是oop-kclass(C++)模型形式存在的. ...

  6. 浏览器默认样式(user agent stylesheet)+cssreset

    每种浏览器都有一套默认的样式表,即user agent stylesheet,在写网页时,没有指定的样式,按浏览器内置的样式表来渲染.这是合理的,像word中也有一些预留样式,可以让我们的排版更美观整 ...

  7. c/c++面试总结(1)

    最近在找新的工作,在找工作中遇到很多面试题,大多数让我很难堪,再次让我认识到自己的知识的匮乏,上份工作是以应届生的身份,所有当时进项目组也没有很多要求,进入项目组后自己还算好学(自己以为),之前也没有 ...

  8. hdu 5720 BestCoder 2nd Anniversary Wool 推理+一维区间的并

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5720 题意:有n(n <= 105)个数 ,每个数小于等于 1018:问在给定的[L,R]区间中 ...

  9. 【Linux编程】存储映射I/O

    存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,对缓冲区的读.写操作就是对文件的读.写操作,从而能够不再使用read.write系统调用. 将文件映射到存储区的函数由mmap完毕,函数原型 ...

  10. 前端面试题-----js和jquery的区别是什么?

    最近我有一个朋友问我js和jquery的区别是什么,于是我打算写一篇文章说下到底有什么区别. 首先你要知道: 1.js是网页的脚本语言,记住哈,js是语言! 2.jquery是用js语言写出来的一个框 ...

随机推荐

  1. 使用OHOS SDK构建libwebp

    参照OHOS IDE和SDK的安装方法配置好开发环境. 从github下载源码. 执行如下命令: git clone --depth=1 https://github.com/webmproject/ ...

  2. OpenHarmony设备截屏的5种方式

      本文转载自<OpenHarmony设备截屏的5种方式 >,作者westinyang 目录 ● 方式1:系统控制中心 ● 方式2:OHScrcpy投屏工具 `推荐` ● 方式3:DevE ...

  3. OpenHarmony源码解析之电话子系统——通话流程

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 王大鹏 深圳开鸿数字产业发展有限公司 一.简介 OpenAtom OpenHarmony(以下简称"Open ...

  4. Stable diffusion 初学者指南

    1. Stable diffusion 初学者指南 想掌握Stable Diffusion AI技术吗? 这份初学者指南专为完全没接触过Stable Diffusion或任何AI图像生成器的新手设计. ...

  5. 重新点亮linux 基本软件————防火墙[一]

    前言 简单介绍一下linux的防火墙. 正文 防火墙分类: 软件防火墙和硬件防火墙 包过:过滤防火墙和应用层防火墙 iptables 的表和链 规则表: filter nat mangle raw f ...

  6. js es6 delete

    前言 首先delete 不同于nodejs delete,看下有什么不同. 正文 var test=5; delete test; console.log(test); 结果是test没有受到任何影响 ...

  7. JS - JavaScript 主要知识点(基础夯实)

    纲要 基本类型和引用类型 类型判断 强制类型转换 作用域 执行上下文 理解函数的执行过程 this 指向 闭包 原型和原型链 js 的继承 event loop 基本类型和引用类型 js中数据类型分为 ...

  8. React纯组件的使用

    1. 有无必要使用纯组件 如果应用不是很大型,页面渲染效率使用纯组件与非纯组件差别不大,尽量使用组件 应用一定注意,setState时子组件依赖渲染的属性一定要传递给子组件,不然父组件setState ...

  9. ASP.NET Core Web API下基于Keycloak的多租户用户授权的实现

    在上文<Keycloak中授权的实现>中,以一个实际案例介绍了Keycloak中用户授权的设置方法.现在回顾一下这个案例: 服务供应商(Service Provider)发布/Weathe ...

  10. 技术门槛高?来看 Intel 机密计算技术在龙蜥社区的实践 | 龙蜥技术

    简介: 数据可用不可见是怎么做到的? 编者按:龙蜥社区云原生机密计算 SIG 定位于云原生机密计算底层基础设施,专注于机密计算底层技术.在阿里巴巴开源开放周中, 龙蜥社区机密计算 SIG Mainta ...