在几乎所有的命令式编程语言中,必然都会有i++和++i这种语法。在编程启蒙教材《C语言程序设计》一书中,也专门解释了这两条语句的区别。有些语言中i++和++i既可以作为左值又可以作为右值,笔者专门测试了一下,在Java语言中,这两条语句都只能作为右值,而不能作为左值。同时,它们都可以作为独立的一条指令执行。

int i = 0;
int j1 = i++; // 正确
int j2 = ++i; // 正确
i++; // 正确
++i; // 正确i++ = 5; // 编译不通过
++i = 5; // 编译不通过

关于i++和++i的区别,稍微有经验的程序员都或多或少都是了解的,为了文章的完整性,本文也通过实例来简单地解释一下。

{int i = 1;int j1 = i++;System.out.println("j1=" + j1); // 输出 j1=1System.out.println("i=" + i); // 输出 i=2
}{int i = 1;int j2 = ++i;System.out.println("j2=" + j2); // 输出 j2=2System.out.println("i=" + i); // 输出 i=2
}

上面的例子中可以看到,无论是i++和++i指令,对于i变量本身来说是没有任何区别,指令执行的结果都是i变量的值加1。而对于j1和j2来说,这就是区别所在。

int i = 1;
int j1 = i++; // 先将i的原始值(1)赋值给变量j1(1),然后i变量的值加1
int j1 = ++i; // 先将i变量的值加1,然后将i的当前值(2)赋值给变量j1(2)

上面的内容是编程基础,是程序员必须要掌握的知识点。本文将在此基础上更加深入地研究其实现原理和陷阱,也有一定的深度。在读本文之前,您应该了解:

  1. 多线程相关知识
  2. Java编译相关知识
  3. JMM(Java内存模型)

本文接下来的主要内容包括:

  1. Java中i++和++i的实现原理
  2. 在使用i++和++i时可能会遇到的一些“坑”

i++和++i的实现原理

接下来让我们深入到编译后的字节码层面上来了解i++和++i的实现原理,为了方便对比,笔者将这两个指令分别放在2个不同的方法中执行,源代码如下:

public class Test {public void testIPlus() {int i = 0;int j = i++;}public void testPlusI() {int i = 0;int j = ++i;}}

将上面的源代码编译之后,使用javap命令查看编译生成的代码(忽略次要代码)如下:

...
{... public void testIPlus();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=3, args_size=10: iconst_0               // 生成整数01: istore_1               // 将整数0赋值给1号存储单元(即变量i)2: iload_1                // 将1号存储单元的值加载到数据栈(此时 i=0,栈顶值为0)3: iinc          1, 1     // 1号存储单元的值+1(此时 i=1)6: istore_2               // 将数据栈顶的值(0)取出来赋值给2号存储单元(即变量j,此时i=1,j=0)7: return                 // 返回时:i=1,j=0LineNumberTable:line 4: 0line 5: 2line 6: 7public void testPlusI();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=3, args_size=10: iconst_0                // 生成整数01: istore_1                // 将整数0赋值给1号存储单元(即变量i)2: iinc          1, 1      // 1号存储单元的值+1(此时 i=1)5: iload_1                 // 将1号存储单元的值加载到数据栈(此时 i=1,栈顶值为1)6: istore_2                // 将数据栈顶的值(1)取出来赋值给2号存储单元(即变量j,此时i=1,j=1)7: return                  // 返回时:i=1,j=1LineNumberTable:line 9: 0line 10: 2line 11: 7
}
...

i++和++i在使用时的一些坑

i++和++i在一些特殊场景下可能会产生意想不到的结果,本节介绍两种会导致结果混乱的使用场景,并剖析其原因。

i = i++的导致的结果“异常”

首先来看一下下面代码执行后的结果。

int i = 0;
i = i++;System.out.println("i=" + i); // 输出 i=0 

正常来讲,执行的结果应该是:i=1,实际结果却是:i=0,这多少会让人有些诧异。为什么会出现这种情况呢?我们来从编码后的代码中找答案。上面的代码编译后的核心代码如下:

0: iconst_0                          // 生成整数0
1: istore_1                          // 将整数0赋值给1号存储单元(即变量i,i=0)
2: iload_1                           // 将1号存储单元的值加载到数据栈(此时 i=0,栈顶值为0)
3: iinc          1, 1                // 号存储单元的值+1(此时 i=1)
6: istore_1                          // 将数据栈顶的值(0)取出来赋值给1号存储单元(即变量i,此时i=0)
7: getstatic     #16                 // 下面是打印到控制台指令
10: new           #22
13: dup
14: ldc           #24
16: invokespecial #26
19: iload_1
20: invokevirtual #29
23: invokevirtual #33
26: invokevirtual #37
29: return

从编码指令可以看出,i被栈顶值所覆盖,导致最终i的值仍然是i的初始值。无论重复多少次i = i++操作,最终i的值都是其初始值。

i++会产生这样的结果,那么++i又会是怎样呢?同样的代码顺序,将i++替换成++i如下:

int i = 0;
i = ++i; // IDE抛出【The assignment to variable i has no effect】警告System.out.println("i=" + i); // 输出i=1

可以看到,使用++i时出现了“正确”的结果,同时Eclipse IDE中抛出【The assignment to variable i has no effect】警告,警告的意思是将值赋给变量i毫无作用,并不会改变i的值。也就是说:i = ++i等价于++i

多线程并发引发的混乱

先来看看之前博客中的一个例子,例子中展示了在多线程环境下由++i操作引起的数据混乱。引发混乱的原因是:++i操作不是原子操作。

虽然在Java++i是一条语句,字节码层面上也是对应iinc这条JVM指令,但是从最底层的CPU层面上来说,++i操作大致可以分解为以下3个指令:

  1. 取数
  2. 累加
  3. 存储

其中的一条指令可以保证是原子操作,但是3条指令合在一起却不是,这就导致了++i语句不是原子操作。

如果变量ivolatile修饰是否可以保证++i是原子操作呢,实际上这也是不行的。至于原因,以后会专门写文章介绍volatile等关键词的意义。如果要保证累加操作的原子性,可以采取下面的方法:

  1. ++i置于同步块中,可以是synchronized或者J.U.C中的排他锁(如ReentrantLock等)。
  2. 使用原子性(Atomic)类替换++i,具体使用哪个类由变量类型决定。如果i是整形,则使用AtomicInteger类,其中的AtomicInteger#addAndGet()就对应着++i语句,不过它是原子性操作。

本文由xialei原创,转载请说明出处http://hinylover.space/2017/07/30/java-i-self-increament/。


深入理解Java中的i++、++i语句相关推荐

  1. 深入理解Java中的内存泄漏

    理解Java中的内存泄漏,我们首先要清楚Java中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容. Java中的内存分配 Java中的内存区域主要分为线程共享的和线程私有的两大区域: ...

  2. java中demo接人_return的用法_如何理解java中return的用法?

    C语言中return用法?(请熟练者进) return是返回值,这个返回值是和函数的类型有关的,函数的类型是什么,他的返回值就是什么 比方主函数intmain() {}这里就必须有一个return,只 ...

  3. java的foreach_深入理解java中for和foreach循环

    •for循环中的循环条件中的变量只求一次值!具体看最后的图片 •foreach语句是java5新增,在遍历数组.集合的时候,foreach拥有不错的性能. •foreach是for语句的简化,但是fo ...

  4. Java中无法到达的语句

    An unreachable statement in Java is a compile-time error. This error occurs when there is a statemen ...

  5. java的goto语句_语法 - Java中是否有goto语句?

    语法 - Java中是否有goto语句? 我对此感到困惑. 我们大多数人都被告知Java中没有任何goto语句. 但我发现它是Java中的关键词之一. 哪里可以使用? 如果它不能使用,那么为什么它作为 ...

  6. 深入理解Java中的String(原地址https://www.cnblogs.com/xiaoxi/p/6036701.html)

    深入理解Java中的String 一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class Stringimplem ...

  7. 理解Java中的弱引用(Weak Reference)

    理解Java中的弱引用(Weak Reference) 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限, ...

  8. 深入理解Java中的final关键字

    深入理解Java中的final关键字 http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什 ...

  9. 如何理解 JAVA 中的 volatile 关键字

    如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...

  10. java弱引用怎么手动释放,十分钟理解Java中的弱引用,十分钟java引用

    十分钟理解Java中的弱引用,十分钟java引用 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,帮助大家理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限 ...

最新文章

  1. 【GZAdmin】开源BS demo快速搭建
  2. Spring + JDK Timer Scheduler Example--reference
  3. c++中创建渐变背景
  4. spring基于纯注解的声明式事务控制
  5. junit 预期错误_谨慎使用JUnit的预期异常
  6. java request获取文件_request获取路径方式
  7. php gps 坐标,php 计算gps坐标 距离
  8. mergesort_Mergesort算法的功能方法
  9. mfa助听器设备能否在android,助听器的蓝牙功能到底有什么用,购买的价格,以及购买时要注意什么等问题...
  10. 正向有功正向无功_电表_正向有功、反向无功
  11. 安装labelImg
  12. centos6.5 tomcat开机启动
  13. 高频量化交易之王--李庆在华尔街
  14. 前端css实现气泡框
  15. Android 根据sensor重力感应 app横竖屏旋转
  16. 【C++】Lambda 表达式详解
  17. 阻止事件冒泡 -- 在antd-mobile中,拦截点击picker后默认打开行为
  18. Asis2016 books null off by one
  19. 《出版专业实务》(2015年版初级)思考与练习答案 第二章
  20. VMware虚拟机提示:无法连接虚拟设备ide1:0,因为主机上没有相应的设备。

热门文章

  1. CDN技术简介及CDN绕过
  2. vCPU估算的几个基本概念
  3. [note] 对于海涅定理(归结原则)的一点理解~
  4. 关于移动端rem与px换算的计算方式
  5. win11环境 cmd 命令窗口 sqlplus 命令无响应
  6. RocketMQ 在网易云音乐的实践
  7. 小米应用商店上传apk报图片格式错误,小米手机调试 DELETE_FAILED_INTERNAL_ERROR错误
  8. 思科静态路由和浮动路由的配置
  9. 公司电脑监控软件有哪些?监控哪些内容?
  10. FMEA软件——什么时候启动FMEA?