Java中i++的一些问题总结

参考内容:

  • 深入理解Java虚拟机(JVM高级特性与最佳实践) ——周志明老师
  • 尚硅谷深入理解JVM教学视频——宋红康老师

在本文展开前,读者需要了解一些字节码有关的知识,以及JVM虚拟机栈中栈帧的局部变量表和操作数栈等知识,笔者在这里只给出一些大概的简述。

字节码

  • Java字节码对于虚拟机,就好像汇编语言对于计算机,属于基本执行指令。
  • 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。由于Java虚拟机采用面向操作数栈而不是寄存器的结构,所以大多数的指令都不包含操作数,只有一个操作码。

局部变量表

局部变量表:Local Variables,被称之为局部变量数组或本地变量表

定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress类型。

由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题。

局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。

操作数栈

操作数栈:Operand Stack ,使用数组实现的。

每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last - In - First -Out)的 操作数栈,也可以称之为 表达式栈(Expression Stack)

操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)

  • 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈
  • 比如:执行复制、交换、求和等操作
  • 操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

接下来就是本文的正式内容,首先,我们先给出两个结论:

  • i++与++i在不同情况下可能会有不同的结论;
  • 实例变量/类变量的i++并不是一个原子性的操作。

首先我们看一下i++与++i的解析:

当i++或者++i没有涉及到其他操作时,两者是没有区别的。

// i++
public void method1(){
 int i = 10;
 i++;
}
// ++i
public void method2(){
 int i = 10;
 ++i;
}

对应的字节码指令操作为:

// method1
0 bipush 10 // 将10这个整数压入操作数栈
2 istore_1	// 将操作数栈栈顶元素保存到局部变量表中索引为1处
3 iinc 1 by 1 // 局部变量表中索引为1处的元素,也就是i进行自增(这一步是在局部变量表上直接进行的,与操作数栈无关)
6 return // 方法返回

// method2
0 bipush 10
2 istore_1
3 iinc 1 by 1 // ++i
6 return

其中关于给出的具体字节码细节以及栈帧中操作数栈、局部变量表在本文开头给出了一些简介,具体内容不展开描述,读者可翻阅与之有关的资料。

通过反编译可以看出,i++与++i的字节码在没有和其他操作组合时,字节码是完全相同的。

当i++或者++i涉及到其他操作时,两者的字节码会有一些改变。

public void method7(){
  int i = 10;
  int a = i++;

  int j = 20;
  int b = ++j;
 }

对应的的字节码指令:

 0 bipush 10
 2 istore_1
 3 iload_1		// i++先从局部变量表中读取i到操作数栈
 4 iinc 1 by 1	// i直接在局部变量表上进行自增
 7 istore_2		// 将操作数栈上读取到的i的值赋值给a,也就是10
 8 bipush 20
10 istore_3
11 iinc 3 by 1	// ++j则先在局部变量表上进行自增
14 iload_3		// 再从局部变量表中读取j到操作数栈
15 istore 4		// 将操作数栈上读取到的j的值赋值给b,也就是21
17 returns

通过反编译可以看出,i++与++i的字节码在没有和其他操作组合时,i++是先取值再自增,而++i是先自增再取值。

还有一个关于i=i++的解析:

public void method8(){
  int i = 10;
  i = i++;
  System.out.println(i);//10
 }

对应的字节码指令:

 0 bipush 10
 2 istore_1
 3 iload_1		// 从局部变量表中读取i到操作数栈
 4 iinc 1 by 1	// i直接在局部变量表上进行自增,此时i = 11
 7 istore_1		// 将之前操作数栈上读取到的i的值赋值给i,之前自增的值被覆盖了,i = 10
 8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #5 <java/io/PrintStream.println>
15 return

然后看一下实例变量i++这行代码的对应的字节码

首先我们定义一个类

/**
 * @author XiaoLe
 * @create 2020-12-04 21:15
 * @description
 */
public class Test {
 private int i = 0;
 public void test(){
  i++;
 }
}

通过反编译查看test方法中的字节码:

 0 aload_0
 1 dup
 2 getfield #2 <day001/Test.i> // 获取到Test的i变量的值
 5 iconst_1	// 将int类型常量1压入栈
 6 iadd	// 栈顶两个元素相加后返回值入栈
 7 putfield #2 <day001/Test.i>
10 return

可以看到,i++这行代码被拆分为三个字节码,所以在一些并发情况下,i++如果不做同步处理,就可能会出现数据非一致性。

到此这篇关于Java中i++的一些问题总结的文章就介绍到这了,更多相关Java中i++问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中i++与++i的区别和使用

    书上对 i ++  和 ++ i 的解释如下: int i = 3,a = 0 : i ++ : 先赋值再运算:例如:a = i ++; 先赋值 a = i ,再运算 i = i + 1 :所以输出结果为 a ==3; ++ i : 先运算再赋值:例如:a = i ++; 先运算 i = i + 1 ,再 赋值 a = i :所以输出结果为 a ==4; 懂了吗?我想大部分人会跟我一样 一脸懵逼,明明都 + 1 了,咋上面输出结果是 3 下面就是 4 呢?哈哈~不要着急,接下来我将分享一下我对它

  • java中的i++和++i的区别详解

    java中的前加加++和后加加++,有很多人搞的很晕,不太明白!今天我举几个例子说明下前++和后++的区别! 其实大家只要记住一句话就可以了,前++是先自加再使用而后++是先使用再自加! 前++和后++总结:其实大家只要记住一句话就可以了,前++是先自加再使用而后++是先使用再自加! 请大家看下面的例子就明白了! public class Test { public static void main(String[] args) { //测试,前加加和后加加 //前++和后++总结:其实大家只要

  • 详解java面试题中的i++和++i

    代码如下所示: public class TestPlusPlus{ public static void main(String[] args){ int k = addAfterReturn(10); System.out.println(k); //输出 10 int k1 = addbeforeReturn(10); System.out.println(k1); //输出11 } public static int addbeforeReturn(int i){ return ++i;

  • 深入理解java中i++和++i的区别

    今天简单谈谈关于java的一个误区,相信很多刚开始学习java的朋友都会遇到这个问题,虽然问题很简单,但是经常容易搞混,说说java的i++和++i的区别. 先看一下代码: <span style="font-size:18px;">public class test { public static void main(String[] args) { int i = 0; for (int j = 0; j < 10; j++) { i=i++; } System.

  • java中i = i++和i =++i的深入讲解

    public class Count { public static void main(String[] args) { int i = 0; i = i++ ; System.out.println(i); } } 上面代码输出的i为0,如果是把i = i++换成i=++i,又会输出1,这是由于i++是先赋值,再计算导致,但是为什么先赋值呢? public static void main(String[] args) { int i = 0; i++ ; } public static v

  • java中i=i++和j=i++的区别小结

    i=i++;j=i++的区别 i=i++-----------在java中 这个语句的前后顺序应该是这样的(tmp=i;i++;tmp==i) java的编译器在遇到i++和i- -的时候会重新为变量运算分配一块内存空间,以存放原始的值,而在完成了赋值运算之后,将这块内存释放掉,下面首先看一下如果是j=i++的情况: i的原始值存放在后开辟的内存中,最后这个值将赋值给j,这样j=i++后,j就会得到i的值,而i又将自加,所以,在释放内存之后,原来存放j和i的地方将得到值将是:j(此时的值等于初始

  • 详解Java中@Override的作用

    详解Java中@Override的作用 @Override是伪代码,表示重写(当然不写也可以),不过写上有如下好处: 1.可以当注释用,方便阅读: 2.编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错.例如,你如果没写@Override,而你下面的方法名又写错了,这时你的编译器是可以编译通过的,因为编译器以为这个方法是你的子类中自己增加的方法. 举例:在重写父类的onCreate时,在方法前面加上@Override 系统可以帮你检查方法的正确性. @Overr

  • 详解Java中多线程异常捕获Runnable的实现

    详解Java中多线程异常捕获Runnable的实现 1.背景: Java 多线程异常不向主线程抛,自己处理,外部捕获不了异常.所以要实现主线程对子线程异常的捕获. 2.工具: 实现Runnable接口的LayerInitTask类,ThreadException类,线程安全的Vector 3.思路: 向LayerInitTask中传入Vector,记录异常情况,外部遍历,判断,抛出异常. 4.代码: package step5.exception; import java.util.Vector

  • Java中的静态内部类详解及代码示例

    1. 什么是静态内部类 在Java中有静态代码块.静态变量.静态方法,当然也有静态类,但Java中的静态类只能是Java的内部类,也称为静态嵌套类.静态内部类的定义如下: public class OuterClass { static class StaticInnerClass { ... } } 在介绍静态内部类之前,首先要弄清楚静态内部类与Java其它内部类的区别. 2. 内部类 什么是内部类?将一个类的定义放在另一个类的内部,就是内部类.Java的内部类主要分为成员内部类.局部内部类.

  • 浅谈Java中Unicode的编码和实现

    Unicode的编码和实现 大概来说,Unicode编码系统可分为编码方式和实现方式两个层次. 编码方式 字符是抽象的最小文本单位.它没有固定的形状(可能是一个字形),而且没有值."A"是一个字符,"€"也是一个字符.字符集是字符的集合.编码字符集是一个字符集,它为每一个字符分配一个唯一数字. Unicode 最初设计是作为一种固定宽度的 16 位字符编码.也就是每个字符占用2个字节.这样理论上一共最多可以表示216(即65536)个字符.上述16位统一码字符构成基

  • java中初始化MediaRecorder的实现方法

    java中初始化MediaRecorder 实现代码: private boolean initializeVideo() { Log.v(TAG, "initializeVideo"); if (mSurfaceHolder == null) { Log.v(TAG, "SurfaceHolder is null"); return false; } mMediaRecorderRecording = true; if (mMediaRecorder == nul

  • java中的可变参数使用方法

    java中的可变参数使用方法 可变参数时Java 1.5新增的方法,可变参数方法接收0个或者多个指定类型的参数,可变参数机制通过先创建一个数组,数组的大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法.如: public class Test{ public int sum(int... args) { int sum = 0; for (int arg : args) { sum += arg; } return sum; } } 可变参数提供了方便,但是使用可变参

  • Java中JDBC事务与JTA分布式事务总结与区别

    Java事务的类型有三种:JDBC事务.JTA(Java Transaction API)事务.容器事务.常见的容器事务如Spring事务,容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现.所以本文暂不讨论容器事务.本文主要介绍J2EE开发中两个比较基本的事务:JDBC事务和JTA事务. JDBC事务 JDBC的一切行为包括事务是基于一个Connection的,在JDBC中是通过Connection对象进行事务管理.在JDBC中,

  • Java中正则表达式去除html标签

    Java中正则表达式去除html的标签,主要目的更精确的显示内容,比如前一段时间在做类似于博客中发布文章功能,当编辑器中输入内容后会将样式标签也传入后台并且保存数据库,但是在显示摘要的时候,比如显示正文的前50字作为摘要,那么这时需要去除所有html标签,然后在截取50字,所以就通过了Java正则表达式实现了如下方法,代码如下: 注:这是Java正则表达式去除html标签方法. private static final String regEx_script = "<script[^>

  • Java 中的Printstream介绍_动力节点Java学院整理

    PrintStream 介绍 PrintStream 是打印输出流,它继承于FilterOutputStream. PrintStream 是用来装饰其它输出流.它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式. 与其他输出流不同,PrintStream 永远不会抛出 IOException:它产生的IOException会被自身的函数所捕获并设置错误标记, 用户可以通过 checkError() 返回错误标记,从而查看PrintStream内部是否产生了IOException

  • Java中synchronized关键字修饰方法同步的用法详解

    Java的最基本的同步方式,即使用synchronized关键字来控制一个方法的并发访问. 每一个用synchronized关键字声明的方法都是临界区.在Java中,同一个对象的临界区,在同一时间只有一个允许被访问. 静态方法则有不同的行为.用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其他线程可以访问这个对象的非静态的synchronized方法.必须非常谨慎这一点,因为两个线程可以同时访问一个对象的两个不同的synchronized方法,即其中一个是静态s

随机推荐