如何查看.java文件的字节码(原码)

出自于:https://www.cnblogs.com/tomasman/p/6751751.html

直接了解foreach底层有些困难,我们需要从更简单的例子着手.下面上一个简单例子:

1 public class Simple {
2
3     public static void main(String[] args) {
4         int i = 5;
5         System.out.println(i);
6     }
7 }

找到其字节码文件所在目录并在目录下打开终端(Windows系统是在目录下shift+鼠标右键选择在此打开powershell窗口)

输入指令:javac -Simple.class >SimpleRunc

目录中得到SimpleRunc文件,打开它,会看到如下代码(里面有我的注释):

 1 Compiled from "Simple.java"
 2 public class cn._012thDay._foreach.Simple {
 3   public cn._012thDay._foreach.Simple();
 4     Code:
 5        0: aload_0                将第一个引用型本地变量推送至栈顶;
 6        1: invokespecial #8                  // Method java/lang/Object."<init>":()V
 7                         调用超类构造方法;
 8        4: return
 9
10   public static void main(java.lang.String[]);
11     Code:
12        0: iconst_5                将int型5推送至栈顶;
13        1: istore_1                将栈顶int型数据存入第二个本地变量;
14                         此处推测:第一个本地变量是超类;
15
16
17        2: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
18                         获取本地静态域并将其压入栈顶;即获取静态属性压入栈顶
19        5: iload_1                将第二个int型本地变量推送至栈顶;
20        6: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
21                         调用实例方法;
22     获取类属性,加载入栈,打印栈顶,即5
23
24        9: return
25 }

如果不懂指令意思,可查询JVM指令表.这里我说明一下步骤:

第一步:加载超类Object类

第二步:将int类型的5压入栈顶,然后将5存入本地变量1

第三部:获取静态属性

第四步:加载本地变量1,即将5推送至栈顶

第五步:打印

for循环

1 public class ForSimple {
2
3     public static void main(String[] args) {
4         for (int i = 5; i > 0; i-=2) {
5             System.out.println(i);
6         }
7     }
8 }

其实这个例子foreach就做不了,因为foreach遍历必须要有一个数组.

javap -c:

Compiled from "ForSimple.java"
public class cn._012thDay._foreach.ForSimple {
  public cn._012thDay._foreach.ForSimple();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: istore_1
       2: goto          15
       5: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       8: iload_1
       9: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
      12: iinc          1, -2
      15: iload_1
      16: ifgt          5
      19: return
}

main方法第2行goto 15代表无条件跳转至第15行加载变量1,值是5

16行:ifgt 5 如果int型大于零则回到第5行,5>0

5~9行:打印,5

12行:变量1自增-2,即5变为3

15行:加载变量1,值是3

16行:ifgt 5 如果int型大于零则回到第5行,3>0

5~9行:打印,3

12行:变量1自增-2,即3变为1

15行:加载变量1,值是1

16行:ifgt 5 如果int型大于零则回到第5行,1>0

5~9行:打印,1

12行:变量1自增-2,即1变为-1

15行:加载变量1,值是-1

16行:ifgt 5 如果int型大于零则回到第5行,-1 <0

19行:return main方法结束

for循环打印结果为5,3,1

foreach遍历

先new一个int[]数组,看看数据是如何存储的:

 1 public class SimpleDemo {
 2
 3     public static void main(String[] args) {
 4         // TODO Auto-generated method stub
 5         int[]arr = new int[3];
 6         arr[0] = 10;
 7         arr[1] = 20;
 8         arr[2] = 30;
 9     }
10 }

Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
  public cn._012thDay._foreach.SimpleDemo();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: return
}

前3行创建基本类型(int)数组,长度为3,存入本地引用型变量1

将索引为0的位置压入10并存储

将索引为1的位置压入20并存储

将索引为2的位置压入30并存储

接下来开始遍历,加入for循环:

for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
  public cn._012thDay._foreach.SimpleDemo();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: iconst_0
      20: istore_2
      21: goto          36
      24: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      27: aload_1
      28: iload_2
      29: iaload
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
      33: iinc          2, 1
      36: iload_2
      37: aload_1
      38: arraylength
      39: if_icmplt     24
      42: return
}

上面代码大家应该不那么陌生了,前面18行存入数组,第19行开始创建了一个新的变量int型值为0,存入变量2.然后用变量2和数组长度作比较,小于数组长度就回到第24行打印,这是一个典型的for循环.

整个遍历中不考虑超类的加载总共创建了两个本地变量,即arr[3]和int i,用arr[3]的长度3和i进行比较,符合条件输出arr[i].输出结果为10,20,30

下面终于轮到我们的主角foreach登场了,删除for循环,新增foreach迭代:

for (int item : arr) {
            System.out.println(item);
        }

Compiled from "SimpleDemo.java"
public class cn._012thDay._foreach.SimpleDemo {
  public cn._012thDay._foreach.SimpleDemo();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: aload_1            load local1 :0
      20: dup                copy
      21: astore        5        int[] local5 = local1
      23: arraylength            3
      24: istore        4        int local4 = 3
      26: iconst_0            0
      27: istore_3            int local3 = 0
      28: goto          46
      31: aload         5        load local5 : int[3]
      33: iload_3            load local3 : 0..
      34: iaload            arr[0..]进栈
      35: istore_2            int local2 = arr[0..]
      36: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      39: iload_2            load local2 :arr[0..]
      40: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
      43: iinc          3, 1        local3 +=1
      46: iload_3            load local3 :0..
      47: iload         4        load local4 :3
      49: if_icmplt     31        local3 < local4 ? go line31:next line
      52: return
}

以上代码我加入了注释,这里大家应该可以看懂了,不考虑超类加载,foreach总共创建了5个本地变量:

local1是原始数组,引用类型

local5是原始数组副本,引用类型

local4是副本数组长度,int类型

local3是0,int类型

local2是arr[i]的副本,int类型

总结:

1.for循环和foreach循环底层创建变量数不同,对于遍历int[]类型数组,for循环底层创建2个本地变量,而foreach底层创建5个本地变量;

2.for循环直接对数组进行操作,foreach对数组副本进行操作;

由于foreach是对数组副本操作,开发中可能导致的问题:

附上java代码和javap -c代码

 1 public class ForeachDemo {
 2
 3     public static void main(String[] args) {
 4         // TODO Auto-generated method stub
 5
 6         String[] s1 = new String[3];
 7         for (String item : s1) {//这里的s1实际是s1副本
 8             item = new String("b");
 9             System.out.println(item);//这里可以把副本中的每项打印出来
10         }
11         print(s1);//打印s1是null,因为s1在内存地址中没有任何变化
12
13     }
14
15     private static void print(String[] s) {
16         // TODO Auto-generated method stub
17         for (int i = 0; i < s.length; i++) {
18             System.out.print(s[i]+" ");
19         }
20     }
21
22 }

Compiled from "ForeachDemo.java"
public class cn._012thDay._foreach.ForeachDemo {
  public cn._012thDay._foreach.ForeachDemo();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: anewarray     #16                 // class java/lang/String
       4: astore_1
       5: aload_1
       6: dup
       7: astore        5
       9: arraylength
      10: istore        4
      12: iconst_0
      13: istore_3
      14: goto          42
      17: aload         5
      19: iload_3
      20: aaload
      21: astore_2
      22: new           #16                 // class java/lang/String
      25: dup
      26: ldc           #18                 // String b
      28: invokespecial #20                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
      31: astore_2
      32: getstatic     #23                 // Field java/lang/System.out:Ljava/io/PrintStream;
      35: aload_2
      36: invokevirtual #29                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      39: iinc          3, 1
      42: iload_3
      43: iload         4
      45: if_icmplt     17
      48: aload_1
      49: invokestatic  #34                 // Method print:([Ljava/lang/String;)V
      52: return
}

javap -c代码第7行新建了String[]数组副本变量5,之后一直在对副本进行操作,直到48行aload_1,然后打印,此时不难看出foreach中进行的所有操作都没有对本地变量1(即原数组)的值产生任何影响.

所以main方法最后一行打印数组s1,其结果一定是3个null

原文地址:https://www.cnblogs.com/Pjson/p/8419650.html

时间: 2024-10-23 04:21:28

如何查看.java文件的字节码(原码)的相关文章

java 变量及数据类型、原码、反码、补码

Java基础——变量及数据类型 变量的概念 内存中的一个存储区域 变量名+数据类型 可在同一类型范围内不断变化 为什么定义变量: 用于不断的存放同一类型的常量,并可以重复使用 使用变量注意: 变量的作用范围,一对{}之间有效 初始化值 定义变量的格式: 数据类型  变量名=初始化值 eg: int x = 4 注:格式固定 理解:变量就如同数学中的未知数. 数据类型: 整数默认类型为:int 小数默认类型为:double 占用内存及取值范围 整数默认类型为:int 小数默认类型为:double

命令查看java的class字节码文件

查看java字节码 1 javac –verbose查看运行类是加载了那些jar文件 HelloWorld演示: javac –verbose HelloWorld.java 可以看到虚拟机编译时做了那些事情-- java –verbose HelloWorld 可以看到虚拟机运行一个程序时加载的jar包 更多内容查看javac –help命令 2  javap查看字节码 首先使用javap –help可以查看到各种命令,各个命令什么作用,可以自己运行试试-- 这里只测试javap –c和jav

字节,原码,反码,补码概念。

8个位 = 1字节 ,分配空间时,至少是1字节. 最前面的位表示符号位 0正   1负   (如:0000 0110是6  ,1000 0110是负6) 正数的原码就是反码也就是补码三位一体. 负数的反码,符号位不变,其它位取反   (e.g. 负5的原码是: 1000 0101  反码是:1111 1010) 负数的补码是反码加1   (e.g. 负5的反码是:1111 1010  补码:1111 1011) 无论正数,负数在内存中都是以补码形式储存的. 反码没有任何意义,只在补码和原码中间起

在Myeclipse下查看Java字节码指令信息

在实际项目开发中,有时为了了解Java编译器内部的一些工作,需要查看Java文件对应的具体的字节码指令集,这里提供两种方式供参考. 一.使用javap命令 javap是JDK提供的一个原生工具,它可以反汇编class文件得到对应的字节码信息,通过调节命令参数,可以获取类的package.fileds和methods等的字节码信息,用的最多的参数也就是-verbose了: 二.Bytecode Outline插件 javap固然可以实现反编译字节码的效果,但是每次都要在命令行中执行,显得有点麻烦,

用Eclipse插件Bytecode Outline来查看Java字节码

在遇到一些小问题的时候我们经常会使用Javap反编译取得字节码来分析,虽然Javap能完成这个工作,但是有两个缺点,一方面操作麻烦,需要很多步骤,一方面没有文档注释,对新手来说看起字节码来比较麻烦. 这里推荐一个工具,Bytecode Outline .它是Eclipse的插件,可以把当前的正在编辑Java的文件或者class文件直接显示出其相应的字节码出来,而且可以进行两个Java文件的字节码比较或者两个class文件的字节码比较或一个Java文件与一个class文件进行字节码的比较. 安装方

原码,反码,补码详解及 Java中&gt;&gt;和&gt;&gt;&gt;的区别

前两天分析 HashMap 的 hash 算法的时候,遇见了 >> 和 >>> 这两个符号,当时查了下资料,在脑子中过了一下.今天又碰到了,没想到竟然忘了  0-0........ 我这记忆力哎,不说了.只好做个笔记,提醒自己,遇到啥不会的最好记下来,好记性不如烂博客啊~ 既然涉及到位运算,我们追本索源,就先从最基础的原码,补码和反码学起.搜了一下这方面的资料,发现一篇专门介绍这方面的文章,写的很是透彻,便直接引用过来了,原文地址是:http://www.cnblogs.co

计算机原码、补码、反码与java移位运算符(&lt;&lt;/&gt;&gt;/&gt;&gt;&gt;)

一.机器值和真值 1.机器值 一个数在计算机中的二进制表示形式,  叫做这个数的机器数.机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1. 比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011.如果是 -3 ,就是 10000011 . 那么,这里的 00000011 和 10000011 就是机器数. 2.真值 因为第一位是符号位,所以机器数的形式值就不等于真正的数值.例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3

Java基础——原码, 反码, 补码 详解

上一篇提到了原码.反码和补码(见 http://www.linuxidc.com/Linux/2015-02/113862.htm),可是自己又捋了半天,有点懂了的样子,可是又不能清晰的表达.暂且记住以下两点吧: 正数的反码和补码都与原码一样: 负数的反码.补码与原码不同,负数的反码:原码中除去符号位,其他的数值位取反,0变1,1变0.负数的补码:其反码+1. 做个小Demo,分别写出7和-7的原码.反码.补码.(其中第一位是符号位,0表示正数,1表示负数) Demo 7 -7 原码 00000

【java】javac编译多个有依赖关系的java文件为class文件

历史文章: [java]javac命令在win10不可用,提示javac不是内部或外部命令,也不是可运行的程序[解决方法] [javac命令不能使用,提示不是内部命令或外部命令,请查看历史文章] =================需求说明========================== 之前的文章中,仅说明了单个的java文件,没有多的外部依赖的单个java文件编译成class文件. 1.现在有个需求: 在使用一个mybatis-gen-1.2.jar包完成 自动代码生成过程中,[jar包中