學習Scala:從HelloWorld開始

來源:互聯網
上載者:User


前言

最近在學習Scala語言,雖然還沒有完全學通, 但是隱約可以體會到Scala的簡潔和強大。 它既能讓程式員使用函數式編程, 也提供了全面的物件導向編程。 在剛剛開始讀《Scala編程》的時候, 剛讀了幾頁, 我就被Scala語言吸引住了, 所以就一直讀下去。 在學習的過程中, 也會有一些感悟, 對於一些原理, 也會盡量搞明白。 所以打算一邊學習, 一邊寫部落格, 雖然目前還沒有深入, 但是還是有很多東西值得寫下來。


我們知道, Scala也是一種運行於Java虛擬機器上的語言, 既然能夠運行於虛擬機器之上, 那麼它必然可以編譯成class檔案, 因為虛擬機器只認class檔案。 所以, scalac編譯器將.scala源檔案, 編譯成class檔案, 然後這些class檔案被虛擬機器載入並執行。



所以, 如果你對class檔案格式和java虛擬機器足夠瞭解的話, 那麼學習scala語言就會相對簡單。Java檔案編譯成class檔案, 而Scala源檔案也是編譯成class檔案, 雖然他們文法大相徑庭, 但是最後殊途同歸。  如果我們能基於class檔案分析scala的行為, 有助於理解scala語言。有人說scala的文法很多很難, 其實文法終歸是寫法, class檔案的格式是不變的, 可以把scala的文法看成java文法的進階文法糖。


本系列部落格基於分析class檔案, 來分析scala的文法。 如果你對class檔案格式不熟悉, 建議讀一下我的專欄, 該專欄是專門分析class檔案和JVM行為的。 專欄地址:


http://blog.csdn.net/column/details/zhangjg-java-blog.html



Scala的HelloWorld
按照IT界的傳統, 下面我們就從HelloWorld開始分析。 下面是scala版的HelloWorld源碼:
object HelloWorld{def main(args : Array[String]){println("HelloWorld")}}

如果對scala的文法不是很熟悉, 並且對scala比較感興趣, 建議先熟悉一下scala的基本文法。 這裡簡單說兩以下幾點:
1   以object關鍵字修飾一個類名, 這種文法叫做伴生對象,這個對象是單例的。 相當於將單例類和單例對象同時定義。
2   方法聲明以def開頭, 然後是方法名, 參數列表, 返回值, 等號, 方法體 。如下:
def doSomeThing(x : Int) : Int = {x += 1}

 如果沒有返回值, 可以省略等號, 直接寫方法體。
3 Array[String]是scala的一種資料類型, 可以理解為字串數組。
這篇部落格的目的不是詳細的講解文法, 而是基於class檔案來分析scala文法的實現方式, 所以對於文法只簡單提一下 。 



反編譯scala HelloWorld
我們所說的反編譯, 是指使用javap工具反編譯class檔案, 所以, 在反編譯之前, 要先使用scalac編譯器編譯該源檔案:
scalac HelloWorld.scala

命令執行完成後, 可以看到HelloWorld.scala所在的目錄中多出兩個class檔案:


其中有一個是和HelloWorld.scala對應的HelloWorld.class 。 那麼HelloWorld$.class是什麼呢?難道一個scala類可以產生多個class嗎? 下面通過反編譯來找到答案。
首先反編譯HelloWorld.class : 
javap -c -v -classpath . HelloWorld

反編譯結果如下: (為了便於講述, 給出了所有的輸出, 會有些長)
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_SUPERConstant 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}

從輸出結果可以看到, 這個類確實有傳統意義上的main方法。 這個main方法中的位元組碼指令大概是這樣:
1 getstatic訪問一個靜態欄位, 這個靜態欄位是定義在HelloWorld$類中的MODULE$欄位, 這個欄位的類型是HelloWorld$ 。 講到這裡, 大概出現了單例的影子。 我們並沒有定義這個類, 所以這個類是scala編譯器自動產生的, 用來輔佐HelloWorld類。
2 然後使用這個靜態對象調用main方法, 這個main方法是HelloWorld$類中的, 而不是當前HelloWorld中的。 它不是靜態, 而是成員方法。 

下面反編譯HelloWorld$類: 
javap -c -v -classpath . HelloWorld$

反編譯結果如下:
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_SUPERConstant 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}

從輸出結果可以知道:

 HelloWorld$類有一個靜態欄位
  public static final HelloWorld$ MODULE$;    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

它的存取修飾詞是 public static final   , 類型是HelloWorld$ , 欄位名是 MODULE$ 。
  HelloWorld$類還有一個靜態初始化方法:
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

在這個靜態初始化方法中, 使用new指令建立了一個HelloWorld$對象, 並且調用該對象的構造方法<init>初始化這個對象。 實際上就是對靜態欄位MODULE$ 的賦值。
 HelloWorld$類還有一個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

這個main方法不是靜態, 是一個執行個體方法, 從它的位元組碼指令可以看出, 實現的是列印字串HelloWorld的邏輯。

HelloWorld的實現方式總結
從上面的講述中, 我們可知, scalac編譯器使用兩個class檔案, 實現HelloWorld.scala源檔案中的邏輯, 除了產生HelloWorld.class外, 還生產一個HelloWorld$.class 。實現邏輯如下:

1 傳統意義上的入口main方法被編譯在HelloWorld.class中
2  在HelloWorld.class中的main方法中, 會訪問HelloWorld$.class中的靜態欄位MODULE$  (這個欄位的類型就是HelloWorld$) , 並使用這個欄位調用HelloWorld$中的main方法。
HelloWorld中的邏輯有點像下面這樣(以下虛擬碼旨在說明原理, 並不符合java或scala的文法):
public class HelloWorld{public static void main(String[] args){  HelloWorld$.MODULE$.main(args);}}

3 真正列印字串“HelloWorld”的邏輯在HelloWorld$中。 這個類有一個main執行個體方法, 來處理列印字串的邏輯, 並且該類中有一個HelloWorld$類型的靜態欄位MODULE$ 。 上面的HelloWorld類中的入口main方法, 正是通過這個欄位調用的HelloWorld$的main執行個體方法來列印"HelloWorld" 。
HelloWorld$中的代碼有點像這樣(以下虛擬碼旨在說明原理, 並不符合java或scala的文法):

public final class HelloWorld${public static final HelloWorld$ MODULE$ = new HelloWorld$();public void main(String[] args){println("HelloWorld");}}





本文基於分析class位元組碼來分析scala, 對class檔案格式不熟悉的同學, 可以參考我的專欄: 深入理解Java語言 。




聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.