美团一面问我i++跟++i的区别是什么
美团一面问我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的运行时数据区域是分为好几块的,具体分布如下图所示:

现在我们主要关注其中的虚拟机栈,关于虚拟机栈,我们需要了解的是:
- Java虚拟机栈是由一个个栈帧组成,线程在执行一个方法时,便会向栈中放入一个栈帧。
- 每一个方法所对应的栈帧又包含了以下几个部分
- 局部变量表
- 操作数栈
- .........
其中的局部变量表存放了编译期可知的各种基本数据类型(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的层次来讲的话,应该这样说:
- i++是先被操作数栈拿去用了(先执行的load指令),然后再在局部变量表中完成了自增,但是操作数栈中还是自增前的值
- 而++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的区别是什么的更多相关文章
- Java-线程池专题 (美团面试题)
去美团面试,问到了什么是线程池,如何使用,为什么要用,以下做个总结 1.什么是线程池: java.util.concurrent.Executors提供了一个 java.util.concurren ...
- Java-线程池专题 (美团)
实现多线程的三种方式,继承Thread,实现Runnable 和 实现 Executor接口 ,具体参考:Java 多线程 三种实现方式 去美团,问到了什么是线程池,如何使用,为什么要用,以下做个总结 ...
- 关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题
前言 开心一刻 有个同学去非洲援建,刚到工地接待他的施工员是个黑人,他就用英语跟人家交流,黑人没做声. 然后他又用法语,黑人还是没说话. 然后他用手去比划.黑人终于开口了:瞎比划嘎哈,整个工地都中国人 ...
- 《我想进大厂》之JVM夺命连环10问
这是面试专题系列第五篇JVM篇. 说说JVM的内存布局? Java虚拟机主要包含几个区域: 堆:堆Java虚拟机中最大的一块内存,是线程共享的内存区域,基本上所有的对象实例数组都是在堆上分配空间.堆区 ...
- JVM重新认识(一)oop-klass模型--HSDB使用验证
一:oop-kclass模型 思考:我们平时写的java类编译成.class文件,JVM加载.class文件,那么加载.class文件之后在JVM中就是oop-kclass(C++)模型形式存在的. ...
- 浏览器默认样式(user agent stylesheet)+cssreset
每种浏览器都有一套默认的样式表,即user agent stylesheet,在写网页时,没有指定的样式,按浏览器内置的样式表来渲染.这是合理的,像word中也有一些预留样式,可以让我们的排版更美观整 ...
- c/c++面试总结(1)
最近在找新的工作,在找工作中遇到很多面试题,大多数让我很难堪,再次让我认识到自己的知识的匮乏,上份工作是以应届生的身份,所有当时进项目组也没有很多要求,进入项目组后自己还算好学(自己以为),之前也没有 ...
- hdu 5720 BestCoder 2nd Anniversary Wool 推理+一维区间的并
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5720 题意:有n(n <= 105)个数 ,每个数小于等于 1018:问在给定的[L,R]区间中 ...
- 【Linux编程】存储映射I/O
存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,对缓冲区的读.写操作就是对文件的读.写操作,从而能够不再使用read.write系统调用. 将文件映射到存储区的函数由mmap完毕,函数原型 ...
- 前端面试题-----js和jquery的区别是什么?
最近我有一个朋友问我js和jquery的区别是什么,于是我打算写一篇文章说下到底有什么区别. 首先你要知道: 1.js是网页的脚本语言,记住哈,js是语言! 2.jquery是用js语言写出来的一个框 ...
随机推荐
- C++ 模板和泛型编程详解
C++中的模板和泛型编程是非常重要的概念.模板是一种将数据类型作为参数的通用程序设计方法.它们允许开发人员编写可以处理各种数据类型的代码,而无需为每种数据类型编写不同的代码.下面介绍了一些关于C++中 ...
- 【FAQ】获取Push Token失败,如何进行排查?
一. 获取Push Token的方式 获取Push Token有两种方式:一种是调用getToken方法向Push服务端请求Token,当getToken方法返回为空时,Token可通过onNewTo ...
- 华为帐号为AITO问界M5助力,打造懂你的智能座舱
12月23日,在华为冬季旗舰新品发布会上,AITO问界M5正式发布.华为赋能的AITO问界M5搭载HUAWEI DriveONE纯电驱增程平台和HarmonyOS智能座舱,并且带来华为终端云服务软硬协 ...
- C++调用Python-4:调用Python函数,传参数字
# mytest.py def myadd(a, b): print("this is test python print add function") return a+b #i ...
- 【鸿蒙生态千帆起】HarmonyOS系统级地图与位置服务,赋能广大开发者
在"与HarmonyOS同行,开放生态,共赢未来"为主题的HUAWEI Developer Day(简称HDD)沙龙中,Petal Maps为开发者们带来了在HarmonyOS下 ...
- 面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这
写在开头 在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器),而CountDownLatch又是其中典型的代表,我们今天就继续来学一下这 ...
- Java:使用POI和泛型生成excel表格
首先创建一个maven项目,导入POI依赖包 <dependency> <groupId>org.apache.poi</groupId> <artifact ...
- 力扣1083(MySQL)-销售分析Ⅲ(简单)
题目: Table: Product Table: Sales 编写一个SQL查询,报告2019年春季才售出的产品.即仅在2019-01-01至2019-03-31(含)之间出售的商品. 以 任意顺序 ...
- 力扣48(java)-旋转图像(中等)
题目: 给定一个 n × n 的二维矩阵 matrix 表示一个图像.请你将图像顺时针旋转 90 度. 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵.请不要 使用另一个矩阵来旋转图像 ...
- HarmonyOS NEXT应用开发之深色跑马灯案例
介绍 本示例介绍了文本宽度过宽时,如何实现文本首尾相接循环滚动并显示在可视区,以及每循环滚动一次之后会停滞一段时间后再滚动. 效果图预览 使用说明: 1.进入页面,检票口文本处,实现文本首尾相接循环滚 ...