问题描述:
编写一个简单的Java程序,里面需要涉及基本类型、四则运行、if 和for,然后分析对应的字节码。
编写自己的代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int a,b;
String operation;
String result="";
System.out.println("请输入两个整数和一个操作符");
a=scanner.nextInt();
b=scanner.nextInt();
operation=scanner.next();
if(operation.equals("+"))result=OpAdd(a,b);
if(operation.equals("-"))result=OpMinus(a,b);
if(operation.equals("*"))result=OpMultiply(a,b);
if(operation.equals("/"))result=OpDivide(a,b);
System.out.println("运算结果为:"+result);
for (int i = 1; i <= 5; i++) {
System.out.println("i = " + i);
}
}
public static String OpAdd(int p1, int p2){
return String.valueOf(p1+p2);
}
public static String OpMinus (int p1,int p2){
return String.valueOf(p1-p2);
}
public static String OpMultiply (int p1,int p2){
return String.valueOf(p1*p2);
}
public static String OpDivide(int p1,int p2){
return String.valueOf(p1/p2);
}
}
使用Javap -c 指令后输出:
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #7 // class java/util/Scanner
3: dup
4: getstatic #9 // Field java/lang/System.in:Ljava/io/InputStream;
7: invokespecial #15 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
10: astore_1
11: ldc #18 // String
13: astore 5
15: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
18: ldc #24 // String 请输入两个整数和一个操作符
20: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: aload_1
24: invokevirtual #32 // Method java/util/Scanner.nextInt:()I
27: istore_2
28: aload_1
29: invokevirtual #32 // Method java/util/Scanner.nextInt:()I
32: istore_3
33: aload_1
34: invokevirtual #36 // Method java/util/Scanner.next:()Ljava/lang/String;
37: astore 4
39: aload 4
41: ldc #40 // String +
43: invokevirtual #42 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
46: ifeq 56
49: iload_2
50: iload_3
51: invokestatic #48 // Method OpAdd:(II)Ljava/lang/String;
54: astore 5
56: aload 4
58: ldc #54 // String -
60: invokevirtual #42 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
63: ifeq 73
66: iload_2
67: iload_3
68: invokestatic #56 // Method OpMinus:(II)Ljava/lang/String;
71: astore 5
73: aload 4
75: ldc #59 // String *
77: invokevirtual #42 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
80: ifeq 90
83: iload_2
84: iload_3
85: invokestatic #61 // Method OpMultiply:(II)Ljava/lang/String;
88: astore 5
90: aload 4
92: ldc #64 // String /
94: invokevirtual #42 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
97: ifeq 107
100: iload_2
101: iload_3
102: invokestatic #66 // Method OpDivide:(II)Ljava/lang/String;
105: astore 5
107: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
110: aload 5
112: invokedynamic #69, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
117: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
120: iconst_1
121: istore 6
123: iload 6
125: iconst_5
126: if_icmpgt 148
129: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
132: iload 6
134: invokedynamic #73, 0 // InvokeDynamic #1:makeConcatWithConstants:(I)Ljava/lang/String;
139: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
public static java.lang.String OpMultiply(int, int);
Code:
0: iload_0
1: iload_1
2: imul
3: invokestatic #76 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
6: areturn
public static java.lang.String OpDivide(int, int);
Code:
0: iload_0
1: iload_1
2: idiv
3: invokestatic #76 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
6: areturn
}
分析Java字节码:
对于 1: invokespecial #1 // Method java/lang/Object."<init>":()V
在字节码指令
1: invokespecial #1
中,我们可以解读如下:
1
是指令的顺序标记,表示它是第1条指令。invokespecial
是操作码,表示要调用一个特殊的方法。#1
是常量池索引,它指向常量池中的一个方法符号引用,用于确定要调用的方法。Method java/lang/Object."<init>":()V
是对被调用方法的描述,其中java/lang/Object
是所属的类名,"<init>"
是构造函数的名称,()
表示该方法没有参数,V
表示该方法没有返回值。
对于 0: new #7 // class java/util/Scanner
这条指令表示在堆上创建一个
java.util.Scanner
类的新实例。数字7是一个常量池索引,用于引用java.util.Scanner
类。
对于 3: dup
该指令将栈顶的值复制一份并将其副本推入栈顶。这是为了在调用
invokespecial
指令之前,将栈上的引用留在栈上以供后续使用。
对于 15: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
在字节码指令 `15: getstatic #20` 中,我们可以解读如下:
- `15` 是指令的顺序标记,表示它是第15条指令。
- `getstatic` 是操作码,表示获取静态字段的值。
- `#20` 是常量池索引,它指向常量池中的一个字段符号引用,用于确定要获取值的静态字段。
- `Field java/lang/System.out:Ljava/io/PrintStream;` 是对被获取的静态字段的描述,其中 `java/lang/System.out` 是字段所属的类和字段名,`Ljava/io/PrintStream;` 表示该字段的类型为 `java.io.PrintStream`。因此,指令 `15: getstatic #20` 表示获取 `java/lang/System` 类中的静态字段 `out` 的值。在 Java 中,`System.out` 是一个 `PrintStream` 对象,可以用于输出文本到控制台。该指令将 `System.out` 的值推入栈顶,以供后续指令使用,比如在本例中用于调用 `println` 方法打印输出。
简单来说:获取索引为#20的静态字段的值
对于 11: ldc #18 // String
在字节码指令 `11: ldc #18` 中,我们可以解读如下:
- `11` 是指令的顺序标记,表示它是第11条指令。
- `ldc` 是操作码,表示将常量推送至栈顶。
- `#18` 是常量池索引,它指向常量池中的一个字符串常量。因此,指令 `11: ldc #18` 表示将常量池中索引为 #18 的字符串常量推送至栈顶。该指令在这个上下文中用于加载字符串常量。
对于 28: aload_1
29: invokevirtual #32 // Method java/util/Scanner.nextInt:()I
32: istore_3
1. `28: aload_1` 表示将局部变量表中索引为 1 的值加载到操作数栈顶。
- `aload_1` 是操作码,表示加载引用类型的局部变量到操作数栈顶。
- 在这里,索引 1 对应的是局部变量表中的第 2 个位置,即 `main` 方法的参数 `String[]` 类型的局部变量。
- 通过 `aload_1` 指令,将 `String[]` 类型的局部变量加载到操作数栈顶。2. `29: invokevirtual #32` 表示调用对象的实例方法。
- `invokevirtual` 是操作码,表示调用虚方法。
- `#32` 是常量池索引,它指向常量池中的一个方法符号引用。
- 该指令调用位于操作数栈顶的对象上的方法,方法的符号引用由常量池索引指定。
- 在这里,`#32` 指向常量池中的 `java/util/Scanner.nextInt` 方法。3. `32: istore_3` 表示将操作数栈顶的值存储到局部变量表的索引 3 处。
- `istore_3` 是操作码,表示将整型值存储到局部变量表的索引 3 处。
- 在这里,将操作数栈顶的整型值存储到局部变量表中的索引 3 处。综上所述,指令序列 `28: aload_1`、`29: invokevirtual #32` 和 `32: istore_3` 的作用是将 `main` 方法的参数 `String[]` 类型的局部变量加载到操作数栈顶,然后调用 `java.util.Scanner.nextInt()` 方法获取一个整数值,并将该整数值存储到局部变量表的索引 3 处。
对于 88: astore 5
在字节码指令 `88: astore 5` 中,我们可以解读如下:
- `88` 是指令的顺序标记,表示它是第88条指令。
- `astore` 是操作码,表示将操作数栈顶的引用类型值存储到局部变量表中。
- `5` 是局部变量表索引,指示要存储值的位置。指令 `astore 5` 的作用是将操作数栈顶的引用类型值存储到局部变量表的索引为5的位置。在这个上下文中,索引5对应的是一个字符串变量。
根据给出的字节码片段,`88: astore 5` 的目的是将操作数栈顶的引用类型值(在这里是存储操作符的字符串变量)存储到局部变量表中的索引5处。这样可以在后续的代码中使用该存储的值进行进一步的操作或判断。
总结 :
通过实验我学会了怎么使用javap -c 查看Java字节码,并且对字节码中的部分代码进行了分析。这使我对JVM中的操作和查看代码运行的规则有了更深入的了解,使我得到了较大的提升。在后续中我也将多次使用这样的方法来查看程序的可行性,使用这样的方法来提高对代码的效率和优化代码。
一名来自北京印刷学院计科的学生