从字节码讲解i++和++i的区别|8月更文挑战

简介: 从字节码讲解i++和++i的区别,i++真的是先赋值再运算吗?让我们从底层一探究竟,彻底弄明白i++和++i的运算过程。

从字节码讲解i++和++i的区别

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

jvm栈

我们知道每一个线程有一个私有的虚拟机栈,在虚拟机栈中每个调用的方法又有一个栈帧,即方法的调用就是栈帧的入栈和出栈。

栈帧是一个内存区块,维系着方法执行的各种数据信息。不同的线程的栈帧不允许相互引用。

栈帧存储着局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息。一些地方将动态链接、方法返回地址、一些附加信息并称为帧数据区。

  • 局部变量表:存储方法的参数、方法内的局部变量(基本数据类型和引用数据类型以及返回值类型的数据),32位的占一个变量槽,64位占两个变量槽。
  • 操作数栈:根据字节码指令,往栈中写入数据或提取数据。
  • 动态链接:指向运行时常量池,因为编译的时候,变量和方法都作为符号引用保存在常量池中,方法之间的调用就是通过这些符号引用来表示,而动态链接就是将这些符号引用,变成直接引用。
  • 方法返回地址:存放的调用该方法的PC寄存器的值,用户方法执行完之后回到被调用的位置。正常退出返回调用者的PC寄存器的值,即调用该方法的指令的下一条指令的地址。异常退出则根据异常表来确定返回的地址,栈帧不保存这部分信息。
  • 一些附加信息:java虚拟机的一些信息。

IDEA中查看类字节码

一、Idea Tools

使用jdk命令结合Idea Tools来查看。

添加工具

在File -> Settings -> Tools ->External Tools点击+号
image-20210730181319409.png

Name:随意填写
Program:$JDKPath$\bin\javap.exe
Arguments:-v -c $FileNameWithoutExtension$.class
Working directory:$OutputPath$$FileDirRelativeToSourcepath$

使用

当你打开java文件后,点击Tools->External Tools ->上述的Name值,即可看见字节码。

二、插件

安装插件

File ->Settings->Plugins搜索插件:jclasslib Bytecode Viewer 并安装

使用

使用之前先编译java文件,编译完之后,在右侧会有Jclasslib点开即可,如果没有,View->Show Bytecode With Jclasslib.

i++和++i的区别

示例一: i++

先通过这个简单的小例子入手,相信这个例子大家大会做。

package com.wangscaler.Increment;
​
/**
 * @author WangScaler
 * @date 2021/7/30 18:00
 */
​
public class Main {
    public static void main(String[] args) {
        int i = 0;
        System.out.println(i++);
        System.out.println(i);
    }
}

打印的结果是0、1。我们通过字节码来看执行过程。

 0 iconst_0
 1 istore_1
 2 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
 5 iload_1
 6 iinc 1 by 1
 9 invokevirtual #3 <java/io/PrintStream.println : (I)V>
12 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
15 iload_1
16 invokevirtual #3 <java/io/PrintStream.println : (I)V>
19 return
  • i代表int,const代表常量,iconst_0就是将常量0入栈。
  • store代表存储,istore_1就是将栈顶的值出栈,放到局部变量1的位置。

    前两步示意图。

image-20210730184300279.png

  • 第三步动态链接运行时常量池#2,这句跟System.out.println()有关,这不过多介绍。
  • load就是加载变量的值,iload_1就是加载局部变量位置1的值到操作栈栈顶。

image-20210730192411568.png

  • inc就是自增操作,iinc 1 by 1 第一个1的意思是局部变量表位置1,所以整句话的意思就是在局部变量表1的值的基础上自增1。

image-20210730192423596.png

  • 调用打印的方法打印,打印的是栈中的值0
  • iload_1,同上将局部变量位置1的值(此时是1)入栈到栈顶
  • 打印1

示例二: ++i

在上述代码的基础上追加代码

System.out.println(++i);
System.out.println(i);

在实例一中i的值是1,所以现在的打印结果是2、2。还是通过字节码一探究竟。

19 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
22 iinc 1 by 1
25 iload_1
26 invokevirtual #3 <java/io/PrintStream.println : (I)V>
29 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
32 iload_1
33 invokevirtual #3 <java/io/PrintStream.println : (I)V>
36 return
  • 从第二句开始,iinc 1 by 1,局部变量表位置1的值自增1。所以现在局部变量表位置1的值为2
  • iload_1。将局部变量表1的值(2)入栈栈顶。所以操作数栈栈顶的值为2。
  • 调用打印,打印栈顶的值2。
  • 下面是重复操作,又将局部变量表的值重新入栈,但是期间值没有变化。

示例三: i= i++

继续追加代码

i = i++;
System.out.println(i);

此时i的初始值是2。打印的结果还是2。字节码如下:

6 iload_1
37 iinc 1 by 1
40 istore_1
41 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
44 iload_1
45 invokevirtual #3 <java/io/PrintStream.println : (I)V>
48 return
  • iload_1。再次将局部变量表1的值(2)入栈栈顶。所以现在局部变量表位置1的值为2。
  • iinc 1 by 1。局部变量表1的值自增1,当前值为3。
  • istore_1。将栈顶的值出栈,放入局部变量表1的位置。所以局部变量表的值又变成了2。
  • iload_1。将局部变量的值2入栈栈顶。
  • 调用打印,打印的值为2

从这可以看出,之前说的i++是先赋值再自增,其实是不对的,i++依然是先自增。但是对于值的计算 ,这个说法还是可以接受的。

示例四: i= ++i

继续追加代码

i = ++i;
System.out.println(i);

此时i的初始值是2,打印的结果是3

48 iinc 1 by 1
51 iload_1
52 istore_1
53 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
56 iload_1
57 invokevirtual #3 <java/io/PrintStream.println : (I)V>
60 return
  • iinc 1 by 1。局部变量表1的值自增1,自增之后的值是3。
  • iload_1。局部变量表1的值3入栈,当前栈顶的值3。
  • istore_1。栈顶元素出栈,放入局部变量1的位置。
  • 入栈栈顶元素值3
  • 调用打印,所以打印的值为3

示例五: int j= i++

追加代码如下:

int j = i++;
System.out.println(j);
System.out.println(i);

此时i的初始值为3。打印的结果你能算出来了吗?没错就是3、4。

字节码如下:

60 iload_1
61 iinc 1 by 1
64 istore_2
65 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
68 iload_2
69 invokevirtual #3 <java/io/PrintStream.println : (I)V>
72 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
75 iload_1
76 invokevirtual #3 <java/io/PrintStream.println : (I)V>
79 return
  • 局部变量表1的值3入栈,当前栈顶的值3。
  • iinc 1 by 1。局部变量表1的值自增1,当前值4。
  • istore_2。栈顶元素出栈,放入到局部变量表2的位置,注意不是位置1
  • iload_2。局部变量表2的值3入栈栈顶。
  • 调用打印语句,所以打印的是3。
  • iload_1。局部变量表1的值4入栈,当前栈顶的值4。
  • 调用打印语句,所以打印的是4。

示例六: j= ++i

追加代码如下,i的初始值4

j = ++i;
System.out.println(j);
System.out.println(i);

字节码如下:

79 iinc 1 by 1
82 iload_1
83 istore_2
84 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
87 iload_2
88 invokevirtual #3 <java/io/PrintStream.println : (I)V>
91 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
94 iload_1
95 invokevirtual #3 <java/io/PrintStream.println : (I)V>
98 return

示例七: i + i++ + ++i

继续追加代码。i的初始值是 5,

System.out.println(i + i++ + ++i);

字节码

 98 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
101 iload_1
102 iload_1
103 iinc 1 by 1
106 iadd
107 iinc 1 by 1
110 iload_1
111 iadd
112 invokevirtual #3 <java/io/PrintStream.println : (I)V>
115 return

牛刀一试

最后两个示例,我没有写结果,便于让你学以致用,自己去试试看看是否真的听懂了。

目录
相关文章
|
7月前
|
存储 缓存 Java
金石原创 |【JVM盲点补漏系列】「并发编程的难题和挑战」深入理解JMM及JVM内存模型知识体系机制(1)
金石原创 |【JVM盲点补漏系列】「并发编程的难题和挑战」深入理解JMM及JVM内存模型知识体系机制(1)
88 1
|
2月前
|
消息中间件
【10月更文挑战第2天】确认机制(Acknowledgements)
【10月更文挑战第2天】确认机制(Acknowledgements)
|
2月前
|
Java
如何从Java字节码角度分析问题|8月更文挑战
如何从Java字节码角度分析问题|8月更文挑战
|
2月前
|
监控 Java
【10月更文挑战第2天】Java线程池的使用
【10月更文挑战第2天】Java线程池的使用
|
2月前
|
设计模式 缓存 Java
从源码学习Java动态代理|8月更文挑战
从源码学习Java动态代理|8月更文挑战
|
4月前
|
算法 测试技术 持续交付
技术感悟:代码之外的智慧
【8月更文挑战第14天】在技术的海洋中,我们常常沉浸于代码的编写和调试,追求着更高效的算法和更优雅的解决方案。然而,技术的世界远不止于此。它还包括了对问题的理解、对工具的运用、以及与他人的协作等多个方面。这些看似与代码无关的技能,实际上对我们的技术成长有着深远的影响。本文将分享一些在代码之外的技术感悟,希望能够为大家提供一些新的视角和思考。
|
7月前
|
机器学习/深度学习 前端开发 Java
Java与前端:揭开技术浪潮背后的真相
Java与前端:揭开技术浪潮背后的真相
|
7月前
|
Java
Java语言测验:技术深度与实践挑战
Java语言测验:技术深度与实践挑战
|
7月前
|
编译器 C语言 Windows
【2月更文挑战第1篇】C程序结构
C 程序结构 在我们学习 C 语言的基本构建块之前,让我们先来看看一个最小的 C 程序结构,在接下来的章节中可以以此作为参考。 C Hello World 实例 C 程序主要包括以下部分: 预处理器指令 函数 变量 语句 & 表达式 注释 让我们看一段简单的代码,可以输出单词 "Hello World": 实例: ```c #include <stdio.h> int main() { /* 我的第一个 C 程序 */ printf("Hello, World! \n"); return 0; } ``` 接下来我们讲解一下上面这段程序: 程序的第一
53 1
|
7月前
|
存储 Java 索引
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,夯实底层基础 —— 吃透class字节码文件技术基底和实现原理(核心结构剖析)
82 0