前言
最近在學(xué)習(xí)Scala語言,雖然還沒有完全學(xué)通, 但是隱約可以體會(huì)到Scala的簡(jiǎn)潔和強(qiáng)大。 它既能讓程序員使用函數(shù)式編程, 也提供了全面的面向?qū)ο缶幊獭?在剛剛開始讀《Scala編程》的時(shí)候, 剛讀了幾頁, 我就被Scala語言吸引住了, 所以就一直讀下去。 在學(xué)習(xí)的過程中, 也會(huì)有一些感悟, 對(duì)于一些原理, 也會(huì)盡量搞明白。 所以打算一邊學(xué)習(xí), 一邊寫博客, 雖然目前還沒有深入, 但是還是有很多東西值得寫下來。
我們知道, Scala也是一種運(yùn)行于Java虛擬機(jī)上的語言, 既然能夠運(yùn)行于虛擬機(jī)之上, 那么它必然可以編譯成class文件, 因?yàn)樘摂M機(jī)只認(rèn)class文件。 所以, scalac編譯器將.scala源文件, 編譯成class文件, 然后這些class文件被虛擬機(jī)加載并執(zhí)行。
所以, 如果你對(duì)class文件格式和java虛擬機(jī)足夠了解的話, 那么學(xué)習(xí)scala語言就會(huì)相對(duì)簡(jiǎn)單。Java文件編譯成class文件, 而Scala源文件也是編譯成class文件, 雖然他們語法大相徑庭, 但是最后殊途同歸。 如果我們能基于class文件分析scala的行為, 有助于理解scala語言。有人說scala的語法很多很難, 其實(shí)語法終歸是寫法, class文件的格式是不變的,
可以把scala的語法看成java語法的高級(jí)語法糖。
本系列博客基于分析class文件, 來分析scala的語法。 如果你對(duì)class文件格式不熟悉, 建議讀一下我的專欄, 該專欄是專門分析class文件和JVM行為的。 專欄地址:
http://blog.csdn.NET/column/details/zhangjg-java-blog.html
Scala的HelloWorld
按照IT界的傳統(tǒng), 下面我們就從HelloWorld開始分析。 下面是scala版的HelloWorld源碼:
- object HelloWorld{
- def main(args : Array[String]){
- println("HelloWorld")
- }
- }
如果對(duì)scala的語法不是很熟悉, 并且對(duì)scala比較感興趣, 建議先熟悉一下scala的基本語法。 這里簡(jiǎn)單說兩以下幾點(diǎn):
1 以object關(guān)鍵字修飾一個(gè)類名, 這種語法叫做孤立對(duì)象,這個(gè)對(duì)象是單例的。 相當(dāng)于將單例類和單例對(duì)象同時(shí)定義。
2 方法聲明以def開頭, 然后是方法名, 參數(shù)列表, 返回值, 等號(hào), 方法體 。如下:
- def doSomeThing(x : Int) : Int = {
- x += 1
- }
如果沒有返回值, 可以省略等號(hào), 直接寫方法體。
3 Array[String]是scala的一種數(shù)據(jù)類型, 可以理解為字符串?dāng)?shù)組。
這篇博客的目的不是詳細(xì)的講解語法, 而是基于class文件來分析scala語法的實(shí)現(xiàn)方式, 所以對(duì)于語法只簡(jiǎn)單提一下 。
反編譯scala HelloWorld
我們所說的反編譯, 是指使用javap工具反編譯class文件, 所以, 在反編譯之前, 要先使用scalac編譯器編譯該源文件:
命令執(zhí)行完成后, 可以看到HelloWorld.scala所在的目錄中多出兩個(gè)class文件:
其中有一個(gè)是和HelloWorld.scala對(duì)應(yīng)的HelloWorld.class 。 那么HelloWorld$.class是什么呢?難道一個(gè)scala類可以生成多個(gè)class嗎? 下面通過反編譯來找到答案。
首先反編譯HelloWorld.class :
- javap -c -v -classpath . HelloWorld
反編譯結(jié)果如下: (為了便于講述, 給出了所有的輸出, 會(huì)有些長(zhǎng))
- Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld.class
- Last modified 2014-4-1; size 586 bytes
- MD5 checksum 2ce2089f345445003ec6b4ef4ed4c6d1
- Compiled from "HelloWorld.scala"
- public final class HelloWorld
- SourceFile: "HelloWorld.scala"
- RuntimeVisibleAnnotations:
- 0: #6(#7=s#8)
- ScalaSig: length = 0x3
- 05 00 00
- minor version: 0
- major version: 50
- flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
- Constant pool:
- #1 = Utf8 HelloWorld
- #2 = Class #1 // HelloWorld
- #3 = Utf8 java/lang/Object
- #4 = Class #3 // java/lang/Object
- #5 = Utf8 HelloWorld.scala
- #6 = Utf8 Lscala/reflect/ScalaSignature;
- #7 = Utf8 bytes
- #8 = Utf8 :Q!\t\t!S3mY><vN7ea ...
- #9 = Utf8 main
- #10 = Utf8 ([Ljava/lang/String;)V
- #11 = Utf8 HelloWorld$
- #12 = Class #11 // HelloWorld$
- #13 = Utf8 MODULE$
- #14 = Utf8 LHelloWorld$;
- #15 = NameAndType #13:#14 // MODULE$:LHelloWorld$;
- #16 = Fieldref #12.#15 // HelloWorld$.MODULE$:LHelloWorld$;
- #17 = NameAndType #9:#10 // main:([Ljava/lang/String;)V
- #18 = Methodref #12.#17 // HelloWorld$.main:([Ljava/lang/String;)V
- #19 = Utf8 Code
- #20 = Utf8 SourceFile
- #21 = Utf8 RuntimeVisibleAnnotations
- #22 = Utf8 ScalaSig
- {
- public static void main(java.lang.String[]);
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- 0: getstatic #16 // Field HelloWorld$.MODULE$:LHelloWorld$;
- 3: aload_0
- 4: invokevirtual #18 // Method HelloWorld$.main:([Ljava/lang/String;)V
- 7: return
- }
從輸出結(jié)果可以看到, 這個(gè)類確實(shí)有傳統(tǒng)意義上的main方法。 這個(gè)main方法中的字節(jié)碼指令大概是這樣:
1 getstatic訪問一個(gè)靜態(tài)字段, 這個(gè)靜態(tài)字段是定義在HelloWorld$類中的MODULE$字段, 這個(gè)字段的類型是HelloWorld$ 。 講到這里, 大概出現(xiàn)了單例的影子。 我們并沒有定義這個(gè)類, 所以這個(gè)類是scala編譯器自動(dòng)生成的, 用來輔佐HelloWorld類。
2 然后使用這個(gè)靜態(tài)對(duì)象調(diào)用main方法, 這個(gè)main方法是HelloWorld$類中的, 而不是當(dāng)前HelloWorld中的。 它不是靜態(tài)的, 而是成員方法。
下面反編譯HelloWorld$類:
- javap -c -v -classpath . HelloWorld$
反編譯結(jié)果如下:
- Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld$.class
- Last modified 2014-4-1; size 596 bytes
- MD5 checksum 7b3e40952539579da28edc84f370ab9b
- Compiled from "HelloWorld.scala"
- public final class HelloWorld$
- SourceFile: "HelloWorld.scala"
- Scala: length = 0x0
-
- minor version: 0
- major version: 50
- flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
- Constant pool:
- #1 = Utf8 HelloWorld$
- #2 = Class #1 // HelloWorld$
- #3 = Utf8 java/lang/Object
- #4 = Class #3 // java/lang/Object
- #5 = Utf8 HelloWorld.scala
- #6 = Utf8 MODULE$
- #7 = Utf8 LHelloWorld$;
- #8 = Utf8 <clinit>
- #9 = Utf8 ()V
- #10 = Utf8 <init>
- #11 = NameAndType #10:#9 // "<init>":()V
- #12 = Methodref #2.#11 // HelloWorld$."<init>":()V
- #13 = Utf8 main
- #14 = Utf8 ([Ljava/lang/String;)V
- #15 = Utf8 scala/Predef$
- #16 = Class #15 // scala/Predef$
- #17 = Utf8 Lscala/Predef$;
- #18 = NameAndType #6:#17 // MODULE$:Lscala/Predef$;
- #19 = Fieldref #16.#18 // scala/Predef$.MODULE$:Lscala/Predef$;
- #20 = Utf8 HelloWorld
- #21 = String #20 // HelloWorld
- #22 = Utf8 println
- #23 = Utf8 (Ljava/lang/Object;)V
- #24 = NameAndType #22:#23 // println:(Ljava/lang/Object;)V
- #25 = Methodref #16.#24 // scala/Predef$.println:(Ljava/lang/Object;)V
- #26 = Utf8 this
- #27 = Utf8 args
- #28 = Utf8 [Ljava/lang/String;
- #29 = Methodref #4.#11 // java/lang/Object."<init>":()V
- #30 = NameAndType #6:#7 // MODULE$:LHelloWorld$;
- #31 = Fieldref #2.#30 // HelloWorld$.MODULE$:LHelloWorld$;
- #32 = Utf8 Code
- #33 = Utf8 LocalVariableTable
- #34 = Utf8 LineNumberTable
- #35 = Utf8 SourceFile
- #36 = Utf8 Scala
- {
- public static final HelloWorld$ MODULE$;
- flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-
-
- public static {};
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=0, args_size=0
- 0: new #2 // class HelloWorld$
- 3: invokespecial #12 // Method "<init>":()V
- 6: return
-
- public void main(java.lang.String[]);
- flags: ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- 0: getstatic #19 // Field scala/Predef$.MODULE$:Lscala/Predef$;
- 3: ldc #21 // String HelloWorld
- 5: invokevirtual #25 // Method scala/Predef$.println:(Ljava/lang/Object;)V
- 8: return
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 9 0 this LHelloWorld$;
- 0 9 1 args [Ljava/lang/String;
- LineNumberTable:
- line 5: 0
- }
從輸出結(jié)果可以知道:
HelloWorld$類有一個(gè)靜態(tài)字段
- public static final HelloWorld$ MODULE$;
- flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
它的訪問修飾符是 public static final , 類型是HelloWorld$ , 字段名是 MODULE$ 。
HelloWorld$類還有一個(gè)靜態(tài)初始化方法:
- public static {};
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=0, args_size=0
- 0: new #2 // class HelloWorld$
- 3: invokespecial #12 // Method "<init>":()V
- 6: return
在這個(gè)靜態(tài)初始化方法中, 使用new指令創(chuàng)建了一個(gè)HelloWorld$對(duì)象, 并且調(diào)用該對(duì)象的構(gòu)造方法<init>初始化這個(gè)對(duì)象。
實(shí)際上就是對(duì)靜態(tài)字段MODULE$ 的賦值。
HelloWorld$類還有一個(gè)main方法:
- public void main(java.lang.String[]);
- flags: ACC_PUBLIC
- Code:
- stack=2, locals=2, args_size=2
- 0: getstatic #19 // Field scala/Predef$.MODULE$:Lscala/Predef$;
- 3: ldc #21 // String HelloWorld
- 5: invokevirtual #25 // Method scala/Predef$.println:(Ljava/lang/Object;)V
- 8: return
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 9 0 this LHelloWorld$;
- 0 9 1 args [Ljava/lang/String;
- LineNumberTable:
- line 5: 0
這個(gè)main方法不是靜態(tài)的, 是一個(gè)實(shí)例方法, 從它的字節(jié)碼指令可以看出, 實(shí)現(xiàn)的是打印字符串HelloWorld的邏輯。
HelloWorld的實(shí)現(xiàn)方式總結(jié)
從上面的講述中, 我們可知, scalac編譯器使用兩個(gè)class文件, 實(shí)現(xiàn)HelloWorld.scala源文件中的邏輯, 除了生成HelloWorld.class外, 還生產(chǎn)一個(gè)HelloWorld$.class 。實(shí)現(xiàn)邏輯如下:
1 傳統(tǒng)意義上的入口main方法被編譯在HelloWorld.class中
2 在HelloWorld.class中的main方法中, 會(huì)訪問HelloWorld$.class中的靜態(tài)字段MODULE$ (這個(gè)字段的類型就是HelloWorld$) ,
并使用這個(gè)字段調(diào)用HelloWorld$中的main方法。
HelloWorld中的邏輯有點(diǎn)像下面這樣(以下偽代碼旨在說明原理, 并不符合java或scala的語法):
- public class HelloWorld{
-
- public static void main(String[] args){
-
-
- HelloWorld$.MODULE$.main(args);
- }
- }
3 真正打印字符串“HelloWorld”的邏輯在HelloWorld$中。 這個(gè)類有一個(gè)main實(shí)例方法, 來處理打印字符串的邏輯, 并且該類中有一個(gè)HelloWorld$類型的靜態(tài)字段MODULE$ 。 上面的HelloWorld類中的入口main方法,
正是通過這個(gè)字段調(diào)用的HelloWorld$的main實(shí)例方法來打印"HelloWorld" 。
HelloWorld$中的代碼有點(diǎn)像這樣(以下偽代碼旨在說明原理,
并不符合java或scala的語法):
- public final class HelloWorld${
-
- public static final HelloWorld$ MODULE$ = new HelloWorld$();
-
- public void main(String[] args){
- println("HelloWorld");
- }
- }
本文基于分析class字節(jié)碼來分析scala, 對(duì)class文件格式不熟悉的同學(xué), 可以參考我的專欄: 深入理解Java語言 。
|