深入理解Java中的i++和++i语句

在很多高级编程语言中,都会有i++和++i这种语法。例如在Java语言中,这两条语句都只能作为右值,而不能作为左值。同时,它们都可以作为独立的一条指令执行。下面用示例来讲解一下这两条语句。

int i = 0;
int j = i++; // 正确
int k = ++i; // 正确
i++; // 正确
++i; // 正确

i++ = 3; // 编译不通过
++i = 3; // 编译不通过

关于i++和++i的区别,大家应该都是有所了解的,本文将通过实例来简单地解释一下。

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

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

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

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

本文将在此基础上更加深入地研究其实现原理和陷阱,也有一定的深度。在读本文之前,您应该了解:

  • 多线程相关知识
  • Java编译相关知识
  • JMM(Java内存模型)

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

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

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

为了了解i++和++i的实现原理,方便对比,我将这两个指令分别放在2个不同的方法中执行,源代码如下:

public class Test {

    public void testIPlusPlus() {
        int i = 0;
        int j = i++;
    }

    public void testPlusPlusI() {
        int i = 0;
        int j = ++i;
    }

}

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

...
{
  ... 

  public void testIPlusPlus();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=1
         0: iconst_0               // 生成整数0
         1: istore_1               // 将整数0赋值给1号存储单元(即变量i)
         2: iload_1                // 将1号存储单元的值加载到数据栈(此时 i=0,栈顶值为0)
         3: iinc          1, 1     // 1号存储单元的值+1(此时 i=16: istore_2               // 将数据栈顶的值(0)取出来赋值给2号存储单元(即变量j,此时i=1,j=07: return                 // 返回时:i=1,j=0
      LineNumberTable:
        line 4: 0
        line 5: 2
        line 6: 7

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

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=02: iload_1                           // 将1号存储单元的值加载到数据栈(此时 i=0,栈顶值为0)
3: iinc          1, 1                // 1号存储单元的值+1(此时 i=16: istore_1                          // 将数据栈顶的值(0)取出来赋值给1号存储单元(即变量i,此时i=07: 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操作不是原子操作。

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

取数
累加
存储

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

如果变量i用volatile修饰是否可以保证++i是原子操作呢,实际上这也是不行的。如果要保证累加操作的原子性,可以采取下面的方法:

  1. 将++i置于同步块中,可以是synchronized或者JUC中的排他锁(如ReentrantLock等)。
  2. 使用原子性(Atomic)类替换++i,具体使用哪个类由变量类型决定。如果i是整形,则使用AtomicInteger类,其中的AtomicInteger#addAndGet()就对应着++i语句,不过它是原子性操作。
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 《Java核心技术·卷I(原书第10版)》是由Cay S. Horstmann和Gary Cornell合著的一本Java编程权威教材。该书从Java的起源、基本语法、面向对象编程等基础知识出发,逐步深入讲解了Java程序设计的各个方面,包括异常处理、多线程、集合框架、输入输出和网络编程等内容。 这本书的特点是简洁明了、内容详实,适合初学者学习Java编程。书的示例代码丰富而实用,可以帮助读者理解各种概念和技巧。此外,书还提供了一些练习题和项目实践,可以帮助读者巩固所学知识。 《Java核心技术·卷I(原书第10版)》涵盖了Java SE 8的新特性和改进,包括Lambda表达式、函数式接口、流式API等内容。这些新特性对于现代Java开发非常重要,使用它们可以提高代码的简洁性和可读性。 总的来说,这本书是一本全面系统的Java编程教材,适合想要学习Java编程的初学者和有一定编程经验的开发者。无论是作为学习教材还是作为参考书,都是一本不可或缺的工具书。 ### 回答2: 《Java核心技术·卷 I》(原书第10版)是由Cay S. Horstmann和Gary Cornell合著的一本Java编程教材。该书是Java编程入门级别的经典教材,对于想要学习Java编程语言的初学者非常有用。 该书共分为两个卷,本回答针对第一卷进行介绍。第一卷主要介绍了Java核心技术的基础知识,包括Java语言的基本语法、面向对象编程、异常处理、集合框架、并发编程等内容。 首先,书详细介绍了Java的基本语法,包括数据类型、变量、运算符、控制流程等。通过学习这些基础知识,读者可以掌握Java语言的基本结构和语法规则。 其次,书着重介绍了面向对象编程的思想和实践。读者将学习到如何定义和使用类、对象、继承、多态等面向对象的概念和技术。这些知识对于理解和设计Java程序非常重要。 此外,书还介绍了异常处理、输入输出、集合框架、图形用户界面、网络编程等其他重要的Java编程技术。这些内容使读者能够全面了解Java的核心技术,并能够应用于实际的编程项目。 值得一提的是,第一卷还涵盖了并发编程的基础知识。并发编程是当今互联网时代非常重要的技术之一,书介绍了线程的使用、同步机制、线程池等相关知识,使读者能够编写高效、可靠的并发程序。 总的来说,《Java核心技术·卷 I》(原书第10版)是一本适合初学者学习的Java编程教材。通过阅读本书,读者可以系统地学习和掌握Java编程的基础知识和核心技术,为进一步深入学习和实践打下坚实的基础。 ### 回答3: 《Java核心技术·卷 I (原书第10版)》是由Cay S. Horstmann和Gary Cornell合著的一本Java技术方面的权威教材,是学习Java编程语言的重要参考资料。 这本书分为两部分,第一部分介绍了Java语言的基本知识,包括数据类型、控制语句、类和对象、继承和接口、异常处理等核心概念。读者通过学习这些基础知识,可以获得Java编程的基本能力,掌握面向对象的编程思想和Java的基本语法特性。 第二部分则涵盖了Java的核心技术,包括集合框架、并发编程、I/O流、网络编程和数据库访问等。这些内容是Java编程非常重要的部分,可以帮助开发人员构建复杂的程序和应用。 这本书的特点是结构清晰,内容详细全面,适合初学者和有一定Java基础的开发人员阅读。每个章节都配有大量的示例代码和实际案例,可以帮助读者更好地理解和应用所学知识。 另外,该书还提供了在线资源和练习题,读者可以通过网站在线下载相关源码和资料,并通过练习题测试自己的掌握程度。 总的来说,《Java核心技术·卷 I (原书第10版)》是一本经典的Java编程教材,对于想系统学习Java编程语言的读者来说,是一本不可多得的参考书。无论是自学还是作为教材辅助教学都非常推荐。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RayCheungQT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值