作爲一門解釋執行的語言,java爲什麼不像python那些直接使用源碼文件來執行呢? 爲什麼java要將java文件編譯成class文件呢?
我想主要的原因有3點:
1 減少程序體積
2 作爲強類型語言,編輯過程中發現錯誤
3 加快執行速度
怎麼理解這三點呢? 對於第一點可以理解爲class文件其實是對java文件做了信息壓縮, 比如重複的字符串放到class文件的常量池裏面,使用的時候通過索引去引用, java的代碼翻譯成短小精悍的指令。 這都能大大的減少文件的大小。
對於第二點,在編譯過程中,會檢查出很多語法錯誤,以及提示用戶對受檢異常的處理,這大大減少了運行時的錯誤。
第三點加快執行速度,因爲編譯後的java指令,java虛擬機更容易識別。如果不經過編譯的話就要邊解析源文件邊查表找到對應的操作,這會浪費一部分時間。其實任何語言,最終執行都是cpu去解釋指令, 編譯型語言只是在編譯期間多做了一些事情,來減少運行時所做的工作。所以我們在編譯程序的時候要忍受編譯慢的痛苦,卻在運行時得到高效的回報。
說了這麼多隻是最近學class文件規範的一點心得體會。
在來概括下class文件的格式。
首先一個類會邊編譯成一個class,並不是一個文件被編譯成一個class。
然後對於class文件,主要包含的信息分爲以下三個層面。
1 class文件本身的信息,這包含魔數,class的版本信息,也就是給解釋器看到底能不能解釋。
2 常量池,這部分用於對字符串,引用的方法和類、字段。 其實還是圍繞字符串來展開
3 類,方法,屬性的描述。
另外方法中重要的屬性就是代碼屬性,代碼會使用常量池中的引用類型來引用方法,字段和字符串。 這用於區分指令操作的是字段,方法,還是字符串。
最後查看class文件信息可以用javap命令
javap -verbose xxx.class
附上一個java類和javap反編譯的結果對照
public class TestClass {
private String hello = "hello";
public String world = "world";
protected int a;
private static String test = "test";
public static void main(String[] args) {
System.out.println(test);
}
public void test() {
System.out.println(hello);
}
}
javap反編譯結果
Classfile /Users/ermei/art/TestClass.class
Last modified 2020-2-19; size 659 bytes
MD5 checksum 65b0891d7e686391604e9185073b6eb4
Compiled from "TestClass.java"
public class TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #11.#27 // java/lang/Object."<init>":()V
#2 = String #12 // hello
#3 = Fieldref #10.#28 // TestClass.hello:Ljava/lang/String;
#4 = String #14 // world
#5 = Fieldref #10.#29 // TestClass.world:Ljava/lang/String;
#6 = Fieldref #30.#31 // java/lang/System.out:Ljava/io/PrintStream;
#7 = Fieldref #10.#32 // TestClass.test:Ljava/lang/String;
#8 = Methodref #33.#34 // java/io/PrintStream.println:(Ljava/lang/String;)V
#9 = String #17 // test
#10 = Class #35 // TestClass
#11 = Class #36 // java/lang/Object
#12 = Utf8 hello
#13 = Utf8 Ljava/lang/String;
#14 = Utf8 world
#15 = Utf8 a
#16 = Utf8 I
#17 = Utf8 test
#18 = Utf8 <init>
#19 = Utf8 ()V
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 main
#23 = Utf8 ([Ljava/lang/String;)V
#24 = Utf8 <clinit>
#25 = Utf8 SourceFile
#26 = Utf8 TestClass.java
#27 = NameAndType #18:#19 // "<init>":()V
#28 = NameAndType #12:#13 // hello:Ljava/lang/String;
#29 = NameAndType #14:#13 // world:Ljava/lang/String;
#30 = Class #37 // java/lang/System
#31 = NameAndType #38:#39 // out:Ljava/io/PrintStream;
#32 = NameAndType #17:#13 // test:Ljava/lang/String;
#33 = Class #40 // java/io/PrintStream
#34 = NameAndType #41:#42 // println:(Ljava/lang/String;)V
#35 = Utf8 TestClass
#36 = Utf8 java/lang/Object
#37 = Utf8 java/lang/System
#38 = Utf8 out
#39 = Utf8 Ljava/io/PrintStream;
#40 = Utf8 java/io/PrintStream
#41 = Utf8 println
#42 = Utf8 (Ljava/lang/String;)V
{
public java.lang.String world;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
protected int a;
descriptor: I
flags: ACC_PROTECTED
public TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String hello
7: putfield #3 // Field hello:Ljava/lang/String;
10: aload_0
11: ldc #4 // String world
13: putfield #5 // Field world:Ljava/lang/String;
16: return
LineNumberTable:
line 1: 0
line 2: 4
line 3: 10
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #7 // Field test:Ljava/lang/String;
6: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
LineNumberTable:
line 8: 0
line 9: 9
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field hello:Ljava/lang/String;
7: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 12: 0
line 13: 10
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #9 // String test
2: putstatic #7 // Field test:Ljava/lang/String;
5: return
LineNumberTable:
line 5: 0
}
SourceFile: "TestClass.java"
另外寫了個簡單解析java class的程序 JavaClassParser