Scala—Java的避難所之main(String[])

梧桐雨—168發表於2008-05-19
你可能已經使用JAVA若干年了,或許JAVA是你接觸程式設計來的第一門程式語言,或許是作為比C++更好的一個選擇.不管怎麼樣,你已經適應了JAVA,並瞭解她的外觀與內在,能夠體會它的喜怒與哀樂(原文:You’re comfortable with Java, you know its ins and outs, its moods).她就像你相處多年的女朋友,或許剛相識時那種激情已經不再,但你已經熟悉怎樣才能讓她開心.簡單的說,如果你是一位工匠,則JAVA就是你幹活的工具.

  然而,你和JAVA的蜜月期過去了,你在程式語言的選擇上更加務實了.只要有足夠的理由,你就可以毫不猶豫去嘗試一種新的語言,但JAVA還沒有足夠的缺陷讓你下定決心離開她.此時,你聽說了一個叫做Ruby的東東,它的簡潔優雅以及巨大的威力給你留下深刻的印象.但你並不是那麼確定去使用一個腳步語言來構建你的企業應用.動態型別以及TextMate確實都不錯,但對於現實世界,你需要一個更堅實的支柱.作為一個實用主義者,你仍然堅持使用JAVA.

  這時,好訊息傳來了.一門新的程式語言登上了舞臺並在開發者的世界引起了一場風暴.Scala貌似具有你對一門程式語言所期待一切: 靜態型別,編譯為位元組碼,簡潔且富有表現力的語法.你可能已經被它的一些例子給深深吸引了.它看起來和JAVA非常相似,但是剔除了JAVA中冗餘的語法結構.Scala沒有分號,沒有public static void的方法修飾詞,並且具有靜態型別推斷機制.

  現在剩下的唯一問題就是你應該從何開始.你也許嘗試過去Scala的網站檢視相關知識,但是..這一切看起來是那麼的函式化:Lamdas,high-order functions,immutable state,recursion out the wazoo.突然讓人感到前途未卜.

  但是,JavaEE的難民們,不必擔心.Scala確實是一門函式式的程式語言,但它同樣也是命令式的和麵向物件的.也就是說,除了書寫那種晦澀難懂的函式式程式碼(原文: It means that you don’t have to write code with the sole purpose of pleasing Haskell Curry. ),你還有其它的選擇.你可以書寫可讀性很強的程式碼,以便你一個星期之後還能看懂它.將你的程式碼給你那些只使用JAVA的同事看,他們甚至也能看懂它.只需要一個正確的引導,你就可以使用Scala來構建你的的JAVA應用.

  導讀:

  如果符合上面所說的,那麼這個Scala系列就是為你而準備的.我已經閱讀過大量有關Scala的文章以及入門材料(如果你更喜歡函式式的程式設計,我強烈推薦你去看Alex Blewitt寫的系列文章),但很少有文章是為了使JAVA開發比較輕鬆的轉變到Scala來的.我個人缺乏FP(函數語言程式設計)的經驗,因此我肯定不會在文章中介紹如何將Scheme程式碼移植到Scala.該系列文章著重於類比Scala與JAVA,並說明為什麼Scala比JAVA更好.

  起步:

  Java程式碼

    

 object HelloWorld extends Application{
  println("Hello, World!")
  }
  object HelloWorld extends Application{
  println("Hello, World!")
  }

    
    沒有什麼比用程式碼來獲得知識更直接了,注意到上面的程式碼沒有使用分號.當然,如果你願意還是可以像在JAVA中一樣使用它.但除非你想在同一行中書寫多個語句,否則分號不是必須的.這個程式碼示例含義非常清楚,想必你已經看明白了它的功能.沒錯,就是向標準控制檯輸出一行"Hello, World!".你可以將這些程式碼儲存到一個字尾名為scala的檔案中,然後使用scala編譯器編譯它,編譯的結果是一個單獨的class檔案(譯者注:這裡原文似乎有錯,我編譯後事實上生成了兩個class檔案).你可以像執行普通java檔案那樣使用java直譯器來執行它,不過你需要注意一下classpath.最直接的方法是使用scala命令:

  引用

  scalac hello.scala

  scala HelloWorld

  注意到"hello.scala"檔名沒有?Scala並不像Java一樣強制你在定義public class時,必須儲存在檔名與類名相同的檔案中.事實上Scala允許你像C++或Ruby一樣,在同一個檔案中你想定義多少class都可以.但我們最好還是遵循Java的命名規則,因此作為一個具有良好習慣的程式設計師,我們應該將上面的程式碼儲存為"HelloWorld.scala".

  編輯器:

  在剛開始接觸Scala,選擇一個正確的編輯器是個關鍵.就像你在使用Java中所瞭解的那樣,IDE非常有用.但作為一門新生的程式語言,Scala目前還沒有很好的IDE支援,不過這只是時間問題.在目前,你只有非常有限的選擇:

  引用

  Eclipse(有幾個不太成熟的Scala外掛)

  Emacs

  IntelliJ(只支援基本的語法高亮)

  TextMate

  VIM

  jEdit

  (譯者注:我使用的是UltraEdit,自己配置一下,能夠自動縮排與語法高亮以及編譯執行,湊合著用了)

  上面列的是幾個主要的選項,更完整的列表可以去Scala安裝目錄下的misc/scala-tool-support/資料夾下檢視.我個人推薦使用jEidt或者TextMate;如果你有冒險精神,可以去嘗試Eclipse上的幾個外掛.Eclipse上的外掛具有通常IDE所具有的一些功能(至少Beta版是這樣),譬如語義高亮,程式碼自動完成,etc.但根據我的經驗,這些外掛都不夠穩定以至於難以使用.Scala是一門比Java簡潔多的語言,它對IDE的依賴性比Java小得多,因此這些都不是本質問題,你可以照樣做的很好.

  再來一個例子

  Java程式碼

   

 object HelloWorld2{
  def main(args:Array[String]) = {
  var greeting =""
  for(i   greeting += (args(i) + " ")
  }
  if(args.length > 0) greeting =greeting.substring(0, greeting.length -1 )
  println(greeting)
  }
  }
  object HelloWorld2{
  def main(args:Array[String]) = {
  var greeting =""
  for(i   greeting += (args(i) + " ")
  }
  if(args.length > 0) greeting =greeting.substring(0, greeting.length -1 )
  println(greeting)
  }
  }

    
    (譯者注: 可能有讀者會奇怪greeting += (args(i) + " ")這段程式碼為什麼要用括號,注意,雖然與習慣不同,但這裡的括號是必須的.因為在Scala裡面,運算子的優先順序由運算子第一個字元代表的運算子的優先順序確定.就是說"+="的優先順序與"+"的優先順序一樣,然後..自己想吧:-).BTW,Scala裡的運算子也是方法&物件..)

  將它儲存到HelloWorld2.scala,並使用如下命令編譯執行:

  引用

  scalac HelloWorld2.scala

  scala HelloWorld2 Hello, World!

  這次我們使用了不同的方式,通過命令列向程式傳遞引數,其執行的結果同樣是在控制檯輸出"Hello, World".這個程式比上一個複雜一些,首先定義了一個String型別的變數greeting,然後遍歷一個陣列,最後對String進行適當的處理(Scala專家肯定會建議使用Array#deepMkString(String)(類似於Ruby中的Array::join方法),這樣確實沒錯.但我這裡主要是為了介紹一些語言上的知識,而不是API的使用).

  首先應該注意的是,這裡我們定義了一個main方法.在第一個例子中,我們僅僅繼承了Application類,然後把其它的都交給預設建構函式來做.這樣很好很簡潔,但有兩個問題:第一,我們沒有辦法向程式傳遞命令列引數;第二,對Scala新手來說,這樣看起來有點魔幻.我將在後面的文章中揭開第一個例子中背後的把戲,現在你記住它就是了.

  這個例子中的main方法是不是已經讓你聯想到了JAVA中的 public static void main?沒錯,Scala中的main就對應Java中的 public static void main.根據這個資訊,有經驗的程式設計師就可以通過思考知道更多關於Scala的知識.

  一開始,你可能有這樣的結論:Scala中的方法隱含就是public的.這基本正確,Scala中的方法預設為public,這意味著Scala中沒有public方法修飾詞(private與protected都有定義).更進一步的,你會猜測:Scala中的方法預設為static的.然而,這次你的猜想不完全正確.

  Scala並沒有真正意義上的static屬性.你越早認識到這一點,你就越容易理解這門語言.作為替代,Scala有專門的語法讓你實現與使用sigleton模式(這正是object的含義).我們實際上宣告瞭一個具有例項方法main(譯者注:注意,這個main方法並不是static的)的單例項類(singleton class).我將會在以後詳細說明這一點,目前你可以認為object就是一個只具有靜態方法的類.(譯者注:這個描述不甚準確).

  仔細的觀察這個例子,我們還可以得到Scala中陣列的語法,以及如何顯式的指明一個變數的型別.讓我們仔細研究一下宣告main方法的這行程式碼:

  Java程式碼

   

 def main(args:Array[String]) = {
  def main(args:Array[String]) = {

    
    這兒,args是一個Array[String]型別的方法引數.也就是說,args是一個String陣列.在Scala中,Array是一個具有型別引數指明其元素型別的類(一個真正的類,而不是JAVA中那樣).與之對等的JAVA語法應該類似與下面這樣子(假設Java中也有一個Array類):

  Java程式碼

   

 public static void main(Array args)
  public static void main(Array args)

    
    在Scala中,使用variable:Type的語法來指明變數型別.因此,如果我們想顯式宣告一個Int型別的變數,應該通過如下方式

  Java程式碼

   

 var myInteger:Int
  var myInteger:Int

    
    在例子中,我們實際上宣告瞭一個String型別的變數greeting.然而,得益於Scala中的型別推斷機制,我們並沒有顯式指明變數的型別.下面兩種方式在語義上是等價的:

  Java程式碼

   

  var greeting =""
  var greeting:String =""
  var greeting =""
  var greeting:String =""
    
    在第一種方式中,我們顯然知道greeting是一個String,Scala編譯器也能夠推斷出這一點.這兩個變數都是靜態型別檢查的,只不過第一種方式可以少寫7個字元:-)

  細心的讀者可能還注意到了,例子中的main方法並沒有指明返回型別.這是因為Scala同樣可以推斷出來.如果我們想顯式的指明這點,可以像下面這樣:

  Java程式碼

   

 def main(args:Array[String]):Unit = {
  def main(args:Array[String]):Unit = {
    
    如前面一樣,型別在方法名之後,並用冒號隔開.順便提一下,Unit是Scala中的一個型別,它表示"我不關心返回的究竟是什麼東東"(原文:I-really-don't-care-what-I-return),你可以把它想象為JAVA中void型別與物件的混合物(譯者注:但Unit不是void,而是一個實實在在的類,也就是說Unit型別的方法會返回一個Unit物件).

  陣列的遍歷:

  Java程式碼

   

 var greeting =""
  for(i   greeting += (args(i) + " ")
  }
  var greeting =""
  for(i   greeting += (args(i) + " ")
  }
    
    (譯者注: 目前Scala中這種方式遍歷的效能相對低下,速度大概是JAVA中對應for的1/60.這個其實也不會造成太大的影響,因為耗時一般不在於for迴圈本身,而是迴圈體內的操作.更何況在Scala中用for的機會不多,有更多簡單實用的方式去實現同樣的操作)

  這段程式碼比剛才那段稍微複雜點.我們一開始宣告瞭一個String型別的變數greeting,然後使用了一個在Scala中不常用的for迴圈,這裡有一些隱含的型別推斷.有Ruby經驗的知道在Ruby中的等價形式:

  Ruby程式碼

   

 for i in 0..(args.size - 1)
  greeting += args[i] + " "
  end
  for i in 0..(args.size - 1)
  greeting += args[i] + " "
  end

    
    問題的關鍵在於,Scala的for迴圈語句需要一個Range物件,而這個Range物件是由RichInt的until方法建立的.我們可以把上面的程式碼分開寫:

  Java程式碼

   

 val range =0.until(args.length)
  for(i   val range =0.until(args.length)
  for(i
    
    注意,這裡使用"val"而不是"var"宣告變數.使用val,就如java中final的語義,指明range是一個常量.

  在Scala中,我們可以通過多種不同的形式呼叫方法.在這個例子中,我們看到了"value methodName param"與"value.methodName(param)"這兩種形式的語法是等價的.另外一個很重要的事情是,until方法是RichInt具有的,Int並沒有該方法.這裡存在一個隱式型別轉換,將Int型別的0轉換為scala.runtime.RichInt的一個例項.這裡不去深究這個型別轉換是怎樣進行的,我們關心的是RichInt類確實有一個返回Rangle物件的until方法.

  因此,事實上這段程式碼與JAVA中的下列程式碼在邏輯上是等價的:

  Java程式碼

   

 for (int i = 0; i < args.length; i++) {
  for (int i = 0; i < args.length; i++) {

    
    只不過在JAVA中是顯式的遍歷整個區間,而Scala中使用了一個Range物件.

  可能你還注意到了,迴圈體中訪問陣列args元素使用的是圓括號"()",而不是我們習慣的方括號"[]".

  更好的遍歷方式:

  在JAVA 5中引入了一個foreach的語法,像下面這樣:

  Java程式碼

   

 for(String arg: args) {
  greeting += arg + " ";
  }
  for(String arg: args) {
  greeting += arg + " ";
  }

    
    這樣更簡潔明瞭.Scala中使用高階函式(使用函式做引數的函式)實現類似的功能:

  Ruby程式碼

   

 args.foreach {arg =>
  greeting += (arg + " ")   }   args.foreach {arg =>
  greeting += (arg + " ")
  }

    
    我們可以看到,Array類有一個foreach方法,這個方法將一個函式作為引數.foreach方法將會對Array裡的每一個元素呼叫一次這個函式,並在呼叫的時候將這個元素作為函式引數傳遞給函式.這裡引數arg沒有指明型別,但由於我們是對一個String陣列遍歷,編譯器能推斷出它的型別是String.我們已經提到過,Scala中呼叫方法有多鐘不同的方式.這裡我們就省略了圓括號"()",同樣我們也可以寫成:

  Ruby程式碼

   

 args.foreach(arg => {
  greeting += (arg + " ")
  })
  args.foreach(arg => {
  greeting += (arg + " ")
  })
    
    Scala中我們可以使用更加簡潔的書寫方式:

  Ruby程式碼

   

 args.foreach(arg => greeting +=(arg + " "))
  args.foreach(arg => greeting +=(arg + " "))
    
    不錯吧,現在我們將整個例子重寫成:

  Ruby程式碼

   

 object HelloWorld2 {
  def main(args:Array[String]) = {
  var greeting = ""
  args.foreach(arg => greeting += (arg + " "))
  if (args.length > 0) greeting = greeting.substring(0, greeting.length - 1)
  println(greeting)
  }
  }
  object HelloWorld2 {
  def main(args:Array[String]) = {
  var greeting = ""
  args.foreach(arg => greeting += (arg + " "))
  if (args.length > 0) greeting = greeting.substring(0, greeting.length - 1)
  println(greeting)
  }
  }

    
    Scala的內建型別:

  因為Scala是基於JVM的,因此它從JAVA那兒繼承了大量的API.這意味這你能夠與JAVA互動使用.並且,你所寫的Scala程式碼事實上使用的就是Java API.比如上面的HelloWorld2中我們使用了一個字串變數greeting,這個變數事實上就是Java中的String型別.再比如,當你在Scala中宣告一個整數型別(Int),編譯器會自動將它轉換為Java的基本型別int.

  Scala還內建了一些隱式型別轉換,當需要的時候編譯器會自動進行(就像例子中的Int到RichInt型別的轉換).比如在Scala中將Array[String]型別傳遞給需要String[]型別引數的函式時,隱式型別轉換就會發生.甚至Scala中的型別引數可以和Java中的泛型相互轉化.簡單的說,Scala就是一個披著不同語法外衣的Java.

  結束

  Scala不需要複雜高深的理論或學術知識.一個普通的開發者就可以在下一個企業WEB應用裡面使用它.它具有JAVA所缺乏的簡潔明瞭的語法,並且保留了JAVA的高效與可靠性.

  譯者注:本文中的HelloWorld2還有一下一些版本

  Java程式碼

   

 object HelloWorld{
  def main(args: Array[String]) {
  println(args.mkString(" "))
  }
  }
  object HelloWorld{
  def main(args: Array[String]) {
  println(args.mkString(" "))
  }
  }

    
    Java程式碼
    
   
 object HelloWorld{
  def main(args: Array[String]) {
  println(args.reduceLeft((a:String,b:String)=>a+" "+b))
  }
  }
  object HelloWorld{
  def main(args: Array[String]) {
  println(args.reduceLeft((a:String,b:String)=>a+" "+b))
  }
  }
  Java程式碼
  object HelloWorld{
  def main(args: Array[String]) {
  println(args.reduceLeft[String](_+" "+_))
  }
  }

    
    考慮到這篇文章中使用的例子不是那麼有吸引力,我摘一個引自 Scala官方網的例子:快速排序(這個例子其實也不怎麼好,主要體現的是Scala中函數語言程式設計的能力).

  首先是用Scala語言寫的Java風格的快速排序的程式碼

  Java程式碼

   

  def sort(xs: Array[Int]) {
  def swap(i: Int, j: Int) {
  val t = xs(i); xs(i) = xs(j); xs(j) = t
  }
  def sort1(l: Int, r: Int) {
  val pivot = xs((l + r) / 2)
  var i = l; var j = r
  while (i <= j) {
  while (xs(i) < pivot) i += 1
  while (xs(j) > pivot) j -=1
  if (i <= j) {
  swap(i, j)
  i += 1
  j -= 1
  }
  }
  if (l < j) sort1(l, j)
  if (j < r) sort1(i, r)
  }
  sort1(0, xs.length 1)
  }
  def sort(xs: Array[Int]) {
  def swap(i: Int, j: Int) {
  val t = xs(i); xs(i) = xs(j); xs(j) = t
  }
  def sort1(l: Int, r: Int) {
  val pivot = xs((l + r) / 2)
  var i = l; var j = r
  while (i <= j) {
  while (xs(i) < pivot) i += 1
  while (xs(j) > pivot) j -=1
  if (i <= j) {
  swap(i, j)
  i += 1
  j -= 1
  }
  }
  if (l < j) sort1(l, j)
  if (j < r) sort1(i, r)
  }
  sort1(0, xs.length 1)
  }
    
    然後是函式式風格的程式碼:

  Java程式碼

   

 def sort(xs: Array[Int]): Array[Int] =
  if (xs.length <= 1) xs
  else {
  val pivot = xs(xs.length / 2)
  Array.concat(
  sort(xs filter (pivot >)),
  xs filter (pivot ==),
  sort(xs filter (pivot   }
   
  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13270562/viewspace-277835/,如需轉載,請註明出處,否則將追究法律責任。

相關文章