Scala程序設(shè)計(jì)(Java虛擬機(jī)多核編程實(shí)戰(zhàn))_第1頁(yè)
Scala程序設(shè)計(jì)(Java虛擬機(jī)多核編程實(shí)戰(zhàn))_第2頁(yè)
Scala程序設(shè)計(jì)(Java虛擬機(jī)多核編程實(shí)戰(zhàn))_第3頁(yè)
Scala程序設(shè)計(jì)(Java虛擬機(jī)多核編程實(shí)戰(zhàn))_第4頁(yè)
Scala程序設(shè)計(jì)(Java虛擬機(jī)多核編程實(shí)戰(zhàn))_第5頁(yè)
已閱讀5頁(yè),還剩220頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

Scala程序設(shè)計(jì)Java虛擬機(jī)多核編程實(shí)戰(zhàn)目錄\h第1章簡(jiǎn)介\h第2章起步\h第3章Scala步入正軌\h第4章Scala的類\h第5章自適應(yīng)類型\h第6章函數(shù)值和閉包\h第7章Trait和類型轉(zhuǎn)換\h第8章使用容器\h第9章模式匹配和正則表達(dá)式\h第10章并發(fā)編程\h第11章與Java互操作\h第12章用Scala做單元測(cè)試\h第13章異常處理\h第14章使用Scala

第1章簡(jiǎn)介可以在JVM上編程的語(yǔ)言有很多。通過這本書,我希望讓你相信花時(shí)間學(xué)習(xí)Scala是值得的。Scala語(yǔ)言為并發(fā)、表達(dá)性和可擴(kuò)展性而設(shè)計(jì)。這門語(yǔ)言及其程序庫(kù)可以讓你專注于問題領(lǐng)域,而無(wú)需深陷于諸如線程和同步之類的底層基礎(chǔ)結(jié)構(gòu)細(xì)節(jié)。如今硬件已經(jīng)越來(lái)越便宜,越來(lái)越強(qiáng)大。很多用戶的機(jī)器都裝了多個(gè)處理器,每個(gè)處理器又都是多核。雖然迄今為止,Java對(duì)我們來(lái)說還不錯(cuò),但它并不是為了利用我們?nèi)缃袷诸^的這些資源而設(shè)計(jì)的。而Scala可以讓你運(yùn)用這些資源,創(chuàng)建高響應(yīng)的、可擴(kuò)展的、高性能的應(yīng)用。本章,我們會(huì)快速瀏覽一下函數(shù)式編程和Scala的益處,為你展現(xiàn)Scala的魅力。在本書的其他部分,你將學(xué)會(huì)如何運(yùn)用Scala,利用這些益處。1.1為何選擇ScalaScala是適合你的語(yǔ)言嗎?Scala是一門混合了函數(shù)式和面向?qū)ο蟮恼Z(yǔ)言。用Scala創(chuàng)建多線程應(yīng)用時(shí),你會(huì)傾向于函數(shù)式編程風(fēng)格,用不變狀態(tài)(immutablestate)①編寫無(wú)鎖(lock-free)代碼。Scala提供一個(gè)基于actor的消息傳遞(message-passing)模型,消除了涉及并發(fā)的痛苦問題。運(yùn)用這個(gè)模型,你可以寫出簡(jiǎn)潔的多線程代碼,而無(wú)需顧慮線程間的數(shù)據(jù)競(jìng)爭(zhēng),以及處理加鎖和釋放帶來(lái)的夢(mèng)魘。把synchronized這個(gè)關(guān)鍵字從你的字典中清除,享受Scala帶來(lái)的高效生產(chǎn)力吧。①對(duì)象一旦創(chuàng)建出來(lái),就不再改變其內(nèi)容,這樣的對(duì)象就是不變的。這也就無(wú)需顧慮多線程訪問對(duì)象時(shí)的競(jìng)爭(zhēng)管理。Java的String就是不變對(duì)象一個(gè)非常好的例子。然而,Scala的益處并不僅限于多線程應(yīng)用。你可以用它構(gòu)建出強(qiáng)大而簡(jiǎn)潔的單線程應(yīng)用,或是多線程應(yīng)用中的單線程模塊。你很快就可以用上Scala的強(qiáng)大能力,包括自適應(yīng)靜態(tài)類型、閉包、不變的容器以及優(yōu)雅的模式匹配。Scala對(duì)于函數(shù)式編程的支持讓你可以寫出簡(jiǎn)潔而有表現(xiàn)力的代碼。感謝更高層的抽象,它讓我們可以用更少的代碼做更多的事情。單線程應(yīng)用和多線程應(yīng)用都可以從函數(shù)式風(fēng)格中受益。函數(shù)式編程語(yǔ)言也為數(shù)不少。比如,Erlang就是一個(gè)很好的函數(shù)式編程語(yǔ)言。實(shí)際上,Scala的并發(fā)模型同Erlang的非常相似。然而,同Erlang相比,Scala有兩個(gè)顯著的優(yōu)勢(shì)。第一,Scala是強(qiáng)類型的,而Erlang不是。第二,不同于Erlang,Scala運(yùn)行于JVM之上,可以與Java很好地互操作。就運(yùn)用在企業(yè)級(jí)應(yīng)用的不同層面而言,Scala這兩個(gè)特性使其成為了首選。只要你愿意,就可以用Scala構(gòu)建整個(gè)企業(yè)級(jí)應(yīng)用,或者,也可以把它和其他語(yǔ)言分別用在不同的層上。如果有些層在你的應(yīng)用中至關(guān)重要,你就可以用上Scala的強(qiáng)類型、極佳的并發(fā)模型和強(qiáng)大的模式匹配能力。下圖的靈感源自O(shè)laBini的語(yǔ)言金字塔(參見附錄A的“FractalProgramming”),它展現(xiàn)了Scala在企業(yè)級(jí)應(yīng)用中與其他語(yǔ)言的配合。JVM上的其他語(yǔ)言Groovy,JRuby,Clojure怎么樣呢?目前為止,能夠同時(shí)提供函數(shù)式風(fēng)格和良好并發(fā)支持的強(qiáng)類型語(yǔ)言,唯有Scala;這正是它的卓越之處。JRuby和Groovy是動(dòng)態(tài)語(yǔ)言,它們不是函數(shù)式的,也無(wú)法提供比Java更好的并發(fā)解決方案。另一方面,Clojure是一種混合型的函數(shù)式語(yǔ)言。它天生就是動(dòng)態(tài)的,因此不是靜態(tài)類型。而且,它的語(yǔ)法類似于Lisp,除非你很熟悉,否則這可不是一種易于掌握的語(yǔ)法。如果你是個(gè)有經(jīng)驗(yàn)的Java程序員,正在頭痛用Java實(shí)現(xiàn)多線程應(yīng)用,那么你就會(huì)發(fā)現(xiàn)Scala非常有用。你可以相當(dāng)容易地就把Java代碼封裝到Scala的actor中,從而實(shí)現(xiàn)線程隔離。還可以用Scala的輕量級(jí)API傳遞消息,以達(dá)到線程通信的目的。與“啟動(dòng)線程,立即用同步的方式限制并發(fā)”不同,你可以通過無(wú)鎖消息傳遞享受真正的并發(fā)。如果你重視靜態(tài)類型,喜歡編譯器支持所帶來(lái)的益處,你會(huì)發(fā)現(xiàn),Scala提供的靜態(tài)類型可以很好地為你工作,而不會(huì)阻礙你。你會(huì)因?yàn)槭褂眠@種無(wú)需鍵入太多代碼的類型而感到愜意。如果你喜歡尋求更高層次的抽象和具有高度表現(xiàn)力的代碼,你會(huì)被Scala的簡(jiǎn)潔所吸引。在Scala里,你可以用更少的代碼做更多的事情。了解了運(yùn)算符和記法,你還會(huì)發(fā)現(xiàn)Scala的靈活性,這對(duì)于創(chuàng)建領(lǐng)域?qū)S谜Z(yǔ)言(domain-specificlanguage)非常有用。提醒一下,Scala的簡(jiǎn)潔有時(shí)會(huì)傾向于簡(jiǎn)短生硬,這會(huì)讓代碼變得難以理解。Scala的一些運(yùn)算符和構(gòu)造對(duì)初學(xué)者而言可能一時(shí)難以適應(yīng)②。這樣的語(yǔ)法不是為膽小之人準(zhǔn)備的。隨著你逐漸精通Scala,你會(huì)開始欣賞這種簡(jiǎn)潔,學(xué)會(huì)避免生硬,使得代碼更易于維護(hù),同時(shí)也更易于理解。②我著手學(xué)習(xí)一門新語(yǔ)言時(shí),還沒有哪門語(yǔ)法不讓我頭疼的,包括Ruby。多多練習(xí),很快語(yǔ)法就變得很自然了。Scala不是一種超然物外的語(yǔ)言。你不必拋棄你已經(jīng)為編寫Java代碼所投入的時(shí)間、金錢和努力。Scala和Java的程序庫(kù)是可以混合在一起的。你可以完全用Scala構(gòu)建整個(gè)應(yīng)用,也可以按照你所期望的程度,將它同Java或其他JVM上的語(yǔ)言混合在一起。因此,你的Scala代碼可以小如腳本,也可以大如全面的企業(yè)應(yīng)用。Scala已經(jīng)用于構(gòu)建不同領(lǐng)域的應(yīng)用,包括電信、社交網(wǎng)絡(luò)、語(yǔ)義網(wǎng)和數(shù)字資產(chǎn)管理。ApacheCamel用Scala做DSL創(chuàng)建路由規(guī)則。LiftWebFramework是一個(gè)用Scala構(gòu)建的強(qiáng)大的Web開發(fā)框架,它充分利用了Scala的特性,比如簡(jiǎn)潔、表現(xiàn)力、模式匹配和并發(fā)。1.2何為ScalaScala,是ScalableLanguage的縮寫,它是一門混合型的函數(shù)式編程語(yǔ)言。MartinOdersky③是它的創(chuàng)始人,2003年發(fā)布了第一個(gè)版本。下面是Scala的一些關(guān)鍵特性④:③請(qǐng)閱讀附錄A,了解更多信息。④請(qǐng)參考附錄A,獲得權(quán)威的語(yǔ)言規(guī)范。它擁有基于事件的并發(fā)模型;它既支持命令式風(fēng)格,也支持函數(shù)式風(fēng)格;它是純面向?qū)ο蟮模凰梢院芎玫呐cJava混合;它強(qiáng)制使用自適應(yīng)靜態(tài)類型;它簡(jiǎn)潔而有表現(xiàn)力;它構(gòu)建于一個(gè)微內(nèi)核之上;它高度可擴(kuò)展,可以用更少的代碼創(chuàng)建高性能應(yīng)用。下面的小例子突出了這些特性:Introduction/TopStock.scalaimportscala.actors._

importActor._

valsymbols=List("AAPL","GOOG","IBM","JAVA","MSFT")

valreceiver=self

valyear=2008

symbols.foreach{symbol=>

actor{receiver!getYearEndClosing(symbol,year)}

}

val(topStock,highestPrice)=getTopStock(symbols.length)

printf("Topstockof%dis%sclosingatprice%f\n",year,topStock,highestPrice)

不用想語(yǔ)法,我們先從大處著眼。symbols指向一個(gè)不變的List,其中持有股票代碼。我們對(duì)這些股票代碼進(jìn)行循環(huán),調(diào)用actor。每個(gè)actor在單獨(dú)的線程中執(zhí)行。因此,同actor關(guān)聯(lián)的代碼塊({})運(yùn)行在其自己的線程上。它調(diào)用(尚未實(shí)現(xiàn)的)函數(shù)getYearEndClosing()。這個(gè)調(diào)用的結(jié)果返回發(fā)起請(qǐng)求的actor。這由特殊的符號(hào)(!)實(shí)現(xiàn)?;氐街骶€程,我們調(diào)用(尚未實(shí)現(xiàn)的)函數(shù)getTopStock()。在上面的代碼完全實(shí)現(xiàn)之后,我們就可以并發(fā)地查詢股票收盤價(jià)了。現(xiàn)在,我們看看函數(shù)getYearEndClosing():Introduction/TopStock.scaladefgetYearEndClosing(symbol:String,year:Int)={

valurl="/table.csv?s="+

symbol+"&a=11&b=01&c="+year+"&d=11&e=31&f="+year+"&g=m"

valdata=io.Source.fromURL(url).mkString

valprice=data.split("\n")(1).split(",")(4).toDouble

(symbol,price)

}

在這個(gè)短小可愛的函數(shù)里面,我們向\h發(fā)出了一個(gè)請(qǐng)求,收到了以CSV格式返回的股票數(shù)據(jù)。我們解析這些數(shù)據(jù),提取年終收盤價(jià)?,F(xiàn)在,先不必為收到數(shù)據(jù)的格式操心,它并不是我們要關(guān)注的重點(diǎn)。在第14章,我們還將再用到這個(gè)例子,提供所有與Yahoo服務(wù)交流的細(xì)節(jié)。還需要實(shí)現(xiàn)getTopStock()方法。在這個(gè)方法里,我們會(huì)收到收盤價(jià),確定最高價(jià)的股票。我們看看如何用函數(shù)式風(fēng)格實(shí)現(xiàn)它:Introduction/TopStock.scaladefgetTopStock(count:Int):(String,Double)={

(1tocount).foldLeft("",0.0){(previousHigh,index)=>

receiveWithin(10000){

case(symbol:String,price:Double)=>

if(price>previousHigh._2)(symbol,price)elsepreviousHigh

}

}

}

在這個(gè)getTopStock()方法中,沒有對(duì)任何變量進(jìn)行顯式賦值的操作。我們以股票代碼的數(shù)量作為這個(gè)方法的參數(shù)。我們的目標(biāo)是找到收盤價(jià)最高的股票代碼。因此,我們把初始的股票代碼和高價(jià)設(shè)置為("",0.0),以此作為foldLeft()方法的參數(shù)。我們用foldLeft()方法去輔助比較每個(gè)股票的價(jià)格,確定最高價(jià)。通過receiveWithin()方法,我們接收來(lái)自開始那個(gè)actor的股票代碼和價(jià)格。如果在指定時(shí)間間隔沒有收到任何消息,receiveWithin()方法就會(huì)超時(shí)。一收到消息,我們就會(huì)判斷收到的價(jià)格是否高于我們當(dāng)前的高價(jià)。如果是,就用新的股票代碼及其價(jià)格作為高價(jià),與下一次接收的價(jià)格進(jìn)行比較。否則,我們使用之前確定的(previousHigh)股票代碼和高價(jià)。無(wú)論從附著于foldLeft()的代碼塊(codeblock)中返回什么,它都會(huì)作為參數(shù),用于在下一元素的上下文中調(diào)用代碼塊。最終,股票代碼和高價(jià)從foldLeft()返回。再?gòu)?qiáng)調(diào)一次,從大處著眼,不要管這里的方法的細(xì)節(jié)。隨著學(xué)習(xí)的深入,你會(huì)逐步了解它們的詳細(xì)內(nèi)容。大約25行代碼,并發(fā)地訪問Web,分析選定股票的收盤價(jià)。花上幾分鐘,分析一下代碼,確保你理解了它是如何運(yùn)作的。重點(diǎn)看方法是如何在不改變變量或?qū)ο蟮那闆r下,計(jì)算最高價(jià)的。整個(gè)代碼只處理了不變狀態(tài);變量或?qū)ο笤趧?chuàng)建后就沒有修改。其結(jié)果是,你不需要顧慮同步和數(shù)據(jù)競(jìng)爭(zhēng),代碼也不需要有顯式的通知和等待序列。消息的發(fā)送和接收隱式地處理了這些問題。如果你把上面所有的代碼放到一起,執(zhí)行,你會(huì)得到如下輸出:Topstockof2008isGOOGclosingatprice307.650000

假設(shè)網(wǎng)絡(luò)延遲是d秒,需要分析的是n個(gè)股票代碼。如果編寫代碼是順序運(yùn)行,大約要花n×d秒。因?yàn)槲覀儾⑿袌?zhí)行數(shù)據(jù)請(qǐng)求,上面的代碼只要花大約d秒即可。代碼中最大的延遲會(huì)是網(wǎng)絡(luò)訪問,這里我們并行地執(zhí)行它們,但并不需要寫太多代碼,花太多精力。想象一下,用Java實(shí)現(xiàn)上面的例子,你會(huì)怎么做。上面的代碼的實(shí)現(xiàn)方式與Java截然不同,這主要體現(xiàn)在下面3個(gè)方面。首先,代碼簡(jiǎn)潔。Scala一些強(qiáng)大的特性包括:actor、閉包、容器(collection)、模式匹配、元組(tuple),而我們的示例就利用了其中幾個(gè)。當(dāng)然,我還沒有介紹過它們,這還只是簡(jiǎn)介!因此,不必在此刻就試圖理解一切,通讀本書之后,你就能夠理解它們了。我們使用消息進(jìn)行線程間通信。因此不再需要wait()和notify()。如果你使用傳統(tǒng)Java線程API,代碼會(huì)復(fù)雜幾個(gè)數(shù)量級(jí)。新的Java并發(fā)API通過使用executor服務(wù)減輕了我們的負(fù)擔(dān)。不過,相比之下,你會(huì)發(fā)現(xiàn)Scala基于actor的消息模型簡(jiǎn)單易用得多。因?yàn)槲覀冎惶幚聿蛔儬顟B(tài),所以不必為數(shù)據(jù)競(jìng)爭(zhēng)和同步花時(shí)間或精力(還有不眠夜)。這些益處為你卸下了沉重的負(fù)擔(dān)。要詳細(xì)地了解使用線程到底有多痛苦,請(qǐng)參考BrianGoetz的JavaConcurrencyinPractice[Goe06]。運(yùn)用Scala,你可以專注于你的應(yīng)用邏輯,而不必為低層的線程操心。你看到了Scala并發(fā)的益處。Scala也并發(fā)地⑤為單線程應(yīng)用提供了益處。Scala讓你擁有選擇和混合兩種編程風(fēng)格的自由:Java所用的命令式風(fēng)格和無(wú)賦值的純函數(shù)式風(fēng)格。Scala允許混合這兩種風(fēng)格,這樣,你可以在一個(gè)線程范圍內(nèi)使用你最舒服的風(fēng)格。Scala使你能夠調(diào)用和混合已有的Java代碼。⑤這里一語(yǔ)雙關(guān)?!幷咦⒃赟cala里,一切皆對(duì)象。比如,2.toString()在Java里會(huì)產(chǎn)生編譯錯(cuò)誤。然而,在Scala里,這是有效的——我們調(diào)用Int實(shí)例的toString()方法。同時(shí),為了能給Java提供良好性能和互操作性,在字節(jié)碼層面上,Scala將Int的實(shí)例映射為32位的基本類型int。Scala編譯為字節(jié)碼。你可以按照運(yùn)行Java語(yǔ)言程序相同的方式運(yùn)行它。⑥也可以很好的將它同Java混合起來(lái)。你可以用Scala類擴(kuò)展Java類,反之亦然。你也可以在Scala里使用Java類,在Java里使用Scala類。你可以用多種語(yǔ)言編寫應(yīng)用,成為真正的多語(yǔ)言程序員⑦——在Java應(yīng)用里,在需要并發(fā)和簡(jiǎn)潔的地方,就用Scala(比如創(chuàng)造領(lǐng)域特定語(yǔ)言)吧!⑥你可以把它當(dāng)作腳本運(yùn)行。⑦參見附錄A,也請(qǐng)閱讀NealFord著的TheProductiveProgrammer[For08]。Scala是一個(gè)靜態(tài)類型語(yǔ)言,但是,不同于Java,它擁有自適應(yīng)的靜態(tài)類型。Scala在力所能及的地方使用類型推演。因此,你不必重復(fù)而冗繁地指定類型,而可以依賴語(yǔ)言來(lái)了解類型,在代碼的剩余部分強(qiáng)制執(zhí)行。不是你為編譯器工作;相反,編譯器為你工作。比如,我們定義vari=1,Scala立即就能推演出變量i是Int類型?,F(xiàn)在,如果我們將某個(gè)字符串賦給那個(gè)變量,比如,i="haha",編譯器就會(huì)給出如下的錯(cuò)誤:error:typemismatch;

found:java.lang.String("haha")

required:Int

i="haha"

在本書后面,你會(huì)看到類型推演超越了簡(jiǎn)單類型定義,也進(jìn)一步超越了函數(shù)參數(shù)和返回值。Scala偏愛簡(jiǎn)潔。在語(yǔ)句結(jié)尾放置分號(hào)是Java程序的第二天性。Scala可以為你的小拇指能從多年的虐待中提供一個(gè)喘息之機(jī)——分號(hào)在Scala中是可選的。但是,這只是個(gè)開始。在Scala中,根據(jù)上下文,點(diǎn)運(yùn)算符(.)也是可選的,括號(hào)也是。因此,不用寫成s1.equals(s2);,我們可以這么寫s1equalss2。去掉了分號(hào)、括號(hào)和點(diǎn),代碼會(huì)有一個(gè)高信噪比。它會(huì)變成更易編寫的領(lǐng)域特定語(yǔ)言。Scala最有趣的一個(gè)方面是可擴(kuò)展性。你可以很好享受到函數(shù)式編程構(gòu)造和強(qiáng)大的Java程序庫(kù)之間的相互作用,創(chuàng)建高度可擴(kuò)展的、并發(fā)的Java應(yīng)用,運(yùn)用Scala提供的功能,充分發(fā)揮多核處理器的多線程優(yōu)勢(shì)。Scala真正的魅力在于它內(nèi)置規(guī)則極少。相比于Java,C#和C++,Scala語(yǔ)言只內(nèi)置了一套非常小的內(nèi)核規(guī)則。其余的,包括運(yùn)算符,都是Scala程序庫(kù)的一部分。這種差異具有深遠(yuǎn)的影響。因?yàn)檎Z(yǔ)言少做一些,你就能用它多做一些。這是真正的可擴(kuò)展,它的程序庫(kù)就是一個(gè)很好的研究案例。1.3函數(shù)式編程我已經(jīng)提過幾次,Scala可以用作函數(shù)式編程語(yǔ)言。我想花幾頁(yè)的篇幅給你一些函數(shù)式編程的感覺。讓我們從對(duì)比Java編程的命令式風(fēng)格開始吧!如果我們想找到給定日期的最高氣溫,可能寫出這樣的Java代碼://Javacode

publicstaticintfindMax(List<Integer>temperatures){

inthighTemperature=Integer.MIN_VALUE;

for(inttemperature:temperatures){

highTemperature=Math.max(highTemperature,temperature);

}

returnhighTemperature;

}

我們創(chuàng)建了一個(gè)可變的變量highTemperature,在循環(huán)中不斷修改它。當(dāng)你擁有可變變量時(shí),你就必須保證正確地初始化它們,在正確的地方將它們改成正確的值。函數(shù)式編程是聲明式風(fēng)格,使用這種風(fēng)格,你要說明做什么,而不是如何去做。如果你用過XSLT,規(guī)則引擎,或是ANTLR,那么你就已經(jīng)用過函數(shù)式風(fēng)格了。我們用函數(shù)式風(fēng)格重寫上面的代碼,不用可變變量,如下代碼所示:Introduction/FindMaxFunctional.scaladeffindMax(temperatures:List[Int])={

temperatures.foldLeft(Integer.MIN_VALUE){Math.max}

}

上面代碼里,你看到了Scala的簡(jiǎn)潔和函數(shù)式編程風(fēng)格的相互作用。這是段高密度的代碼。用幾分鐘時(shí)間沉淀一下。我們創(chuàng)建了一個(gè)函數(shù)findMax(),接收一個(gè)不變的容器(temperatures)為參數(shù),表示溫度值。圓括號(hào)和花括號(hào)之間的“=”告訴Scala推演這個(gè)函數(shù)的返回類型(這里是Int)。在這個(gè)函數(shù)里,我們調(diào)用這個(gè)collection的foldLeft()方法,對(duì)容器中的每個(gè)元素運(yùn)用Math.max()。正如你所知道的,java.lang.Math類的max()方法接收兩個(gè)參數(shù),就是我們要確定最大值的兩個(gè)值。在上面的代碼里,這兩個(gè)參數(shù)是隱式傳遞的。max()的第一個(gè)隱式參數(shù)是之前的高值,第二個(gè)參數(shù)是foldLeft()正在迭代的容器中的當(dāng)前元素。foldLeft()取回調(diào)用max的結(jié)果,這就是當(dāng)前的高值,在接下來(lái)調(diào)用max()時(shí)把它傳進(jìn)去,同下一個(gè)元素比較。foldLeft()的參數(shù)就是高溫的初始值。foldLeft()方法需要花些功夫來(lái)掌握。稍稍做個(gè)假設(shè),把容器中的元素當(dāng)作是站成一排的人,我們要找出年紀(jì)最大的人的年齡。我們?cè)诠P記上寫上0,把它傳給這排的第一個(gè)人。第一個(gè)丟棄這個(gè)筆記(因?yàn)樗?歲年齡大);用他的年齡20創(chuàng)建一個(gè)新的筆記;把它傳給這排的下一個(gè)人。第二個(gè)人,他比20歲年輕,簡(jiǎn)單把筆記傳給下一個(gè)挨著他的人。第三個(gè)人,32歲,丟棄這個(gè)筆記,創(chuàng)建一個(gè)新的傳遞下去。我們從最后一個(gè)人獲得的筆記就會(huì)包含年紀(jì)最大的人的年齡。把這一系列過程可視化,你就知道foldLeft()背后做了些什么。上面的代碼是不是感覺像喝了一小口紅牛?Scala代碼高度簡(jiǎn)潔,非常緊湊。你不得不花些功夫?qū)W習(xí)這個(gè)語(yǔ)言。但是,一旦你掌握了它,你就能夠利用它的威力和表現(xiàn)力了。我們來(lái)看另外一個(gè)函數(shù)式風(fēng)格的例子。假定我們想要一個(gè)List,其元素就是將原List值的翻倍。我們不會(huì)對(duì)每個(gè)元素進(jìn)行循環(huán)來(lái)實(shí)現(xiàn),只要簡(jiǎn)單的說,我們要元素翻倍,讓語(yǔ)言來(lái)循環(huán),如下所示:Introduction/DoubleValues.scalavalvalues=List(1,2,3,4,5)

valdoubleValues=values.map(_*2)

關(guān)鍵字val理解為“不變的”。我們告訴Scala,變量values和doubleValues一旦創(chuàng)建就不會(huì)改變。盡管看上去不像,但_*2確實(shí)是一個(gè)函數(shù)。它是個(gè)匿名函數(shù),這表示這個(gè)函數(shù)只有函數(shù)體,而沒有函數(shù)名。下劃線(_)表示傳給這個(gè)函數(shù)的參數(shù)。函數(shù)本身作為參數(shù)傳給map函數(shù)。map()函數(shù)在容器上迭代,對(duì)于容器中的每個(gè)元素,都會(huì)調(diào)用以參數(shù)給出的匿名函數(shù)。其結(jié)果是創(chuàng)建一個(gè)新的List,包含的元素就是原List元素值的翻倍??匆娫趺窗押瘮?shù)(這里就是把一個(gè)數(shù)翻倍)當(dāng)作普通參數(shù)和變量了吧?在Scala里面,函數(shù)是一等公民。因此,雖然獲得了一個(gè)將原List元素值翻倍的List,但我們并沒有修改任何變量和對(duì)象。這種不變的方式是一個(gè)關(guān)鍵概念,它讓函數(shù)式編程成為一種非常有吸引力的并發(fā)編程風(fēng)格。在函數(shù)式編程中,函數(shù)是純粹的。它們產(chǎn)生的輸出只是基于其接收到的輸入,它們不會(huì)受任何狀態(tài)影響或也不會(huì)影響任何狀態(tài),無(wú)論是全局還是局部的。1.4本書的內(nèi)容我寫這本書的目標(biāo)是讓你快速理解Scala,可以用它編寫并發(fā)、可伸縮、有表現(xiàn)力的程序。為了做到這些,你需要學(xué)很多東西,但是還有很多你也不必了解。如果你的目標(biāo)是了解Scala的全部,本書滿足不了你。我們已經(jīng)有了這樣一本書,叫ProgramminginScala[OSV08],由MartinOdersky、LexSpoon和BillVenners編寫,它相當(dāng)深入的介紹了這門語(yǔ)言,非常值得一讀。本書里講述的是開始使用Scala所需的一些必要概念。我假定你非常熟悉Java。因此,你并不會(huì)從這本書里面學(xué)到基本的編程概念。然而,我并不假定你擁有函數(shù)式編程的知識(shí),或是了解Scala語(yǔ)言本身——你會(huì)在本書里學(xué)到。我是為忙碌的Java開發(fā)者編寫的這本書,因此我的目標(biāo)是讓你很快覺得Scala很舒服,以便你可以很快地開始用它構(gòu)建真實(shí)的應(yīng)用。你會(huì)看到概念介紹得相當(dāng)快,但會(huì)提供很多例子幫助你理解它們的。本書的其余部分按照如下方式組織。在每章里,你都會(huì)學(xué)到一些必需的知識(shí)點(diǎn),讓你更接近于用Scala編寫并發(fā)代碼。第2章,起步。這一章我會(huì)帶著你安裝Scala,讓你的第一個(gè)Scala代碼執(zhí)行起來(lái)。我會(huì)為你展示如何把Scala當(dāng)作腳本用,如何像傳統(tǒng)的Java代碼一樣編譯,以及如何使用Java工具運(yùn)行它。第3章,Scala步入正軌。從這一章開始,你會(huì)擁有一次快速Scala之旅,了解它的簡(jiǎn)潔,了解它如何處理Java類和基本類型,如何在已有Java知識(shí)的基礎(chǔ)上學(xué)習(xí)新內(nèi)容。對(duì)于那些毫無(wú)戒心的Java程序員而言,Scala還是有些驚奇的,你會(huì)在這章看到這些驚奇。第4章,Scala的類。作為一門純粹的面向?qū)ο笳Z(yǔ)言,Scala處理類的方式與Java有相當(dāng)大的差異。比如,它沒有static關(guān)鍵字,然而你可以用伴生對(duì)象創(chuàng)建類成員。你會(huì)在這一章中學(xué)到Scala的OO編程方式。第5章,自適應(yīng)類型⑧。Scala是一種靜態(tài)類型語(yǔ)言。它提供了編譯時(shí)檢查,但是與其他靜態(tài)類型語(yǔ)言不同,它沒有繁文縟節(jié)的⑨語(yǔ)法。在這一章中,你會(huì)學(xué)到Scala輕量級(jí)自適應(yīng)類型。⑧自適應(yīng)類型(SensibleTyping),并不是Scala本身的術(shù)語(yǔ),而是作者為了形容Scala類型的特性而想出的一個(gè)說法。這章主要是描述Scala的類型的推演能力和類型體系。從字面理解,是有意識(shí)或有知覺的確定類型,把Scala比作一個(gè)生命體,用以形容Scala的類型特征。這里把它譯作自適應(yīng)類型?!g者注⑨參見附錄A。第6章,函數(shù)值和閉包。函數(shù)值和閉包是函數(shù)式編程的核心概念,也是Scala的一個(gè)最常見特征。在這一章中,我會(huì)帶你領(lǐng)略如何善用它們。第7章,Trait和類型轉(zhuǎn)換。你會(huì)學(xué)到如何抽象行為,并將其混入任意的類中,也會(huì)了解到Scala的隱式類型轉(zhuǎn)換。第8章,使用容器,Scala提供可變和不變的容器。你可以很簡(jiǎn)潔的創(chuàng)建它們,通過閉包進(jìn)行迭代,正如你在這一章所見到的。第9章,模式匹配和正則表達(dá)式。從這章開始,你會(huì)開始探索模式匹配功能。它是Scala最強(qiáng)大的特性之一,也是你需要在并發(fā)編程中依賴的一個(gè)特性。第10章,并發(fā)編程。在這一章,你會(huì)讀到本書中最令人期待的特性。你會(huì)學(xué)習(xí)到強(qiáng)大的基于事件的并發(fā)模型和用做支撐的actorAPI。第11章,與Java混合。一旦你解決了如何使用并發(fā)的問題,你就會(huì)想把它用到你的Java應(yīng)用里面了。這一章會(huì)為你展示如何做到這一點(diǎn)。第12章,Scala的單元測(cè)試。如果你想確保你鍵入的代碼確實(shí)做了你想做的事情,那么Scala擁有的單元測(cè)試可以提供良好的支持。在這一章中,你會(huì)學(xué)習(xí)如何使用JUnit、TestNG和基于Scala的測(cè)試工具,測(cè)試Scala和Java代碼。第13章,異常處理。我知道你能寫出很好的代碼。然而,你還是不得不處理你調(diào)用代碼所拋出的異常。Scala有一種不同于Java的異常處理方法,你會(huì)在這一章中看到。第14章,使用Scala。在這章中,我會(huì)把這本書的概念放到一起,為你展示如何善用Scala構(gòu)建真實(shí)世界的應(yīng)用。最后,在附錄A中,你會(huì)找到本書所引用的一些Web上的文章和blog。1.5本書面向的讀者這本書是為有經(jīng)驗(yàn)的Java程序員準(zhǔn)備的。也就是說你要相當(dāng)熟悉Java語(yǔ)言的語(yǔ)法和JavaAPI,而且你也要有扎實(shí)的面向?qū)ο缶幊讨R(shí)?;谶@樣的前提,你就可以快速領(lǐng)會(huì)到Scala的精髓,用它構(gòu)建真實(shí)的應(yīng)用。熟悉其他語(yǔ)言的程序員也可以使用這本書,但是不得不讀一些Java的好書,補(bǔ)充一些營(yíng)養(yǎng)。對(duì)Scala有幾分了解的程序員也可以使用本書,了解一些他們尚未得到機(jī)會(huì)探索的語(yǔ)言特性。已經(jīng)熟悉Scala的人可以用這本書來(lái)培訓(xùn)他們組織中的其他程序員。1.6致謝編寫本書的過程中,我擁有著一些特權(quán),這些特權(quán)使我能夠從很多智者那里獲得幫助。這群非常有激情的人都是在百忙之中貢獻(xiàn)出他們的時(shí)間評(píng)論本書,告訴我哪里不足,哪里做得好,鼓勵(lì)我繼續(xù)前行。這本書能夠變得更好,我需要鳴謝AlScherer、AndresAlmiray、ArildShirazi、BillVenners、BrianGoetz、BrianSam-bodden、BrianSletten、DanielHinojosa、IanRoughley、JohnD.Heintz、MarkRichards、MichaelFeathers、MikeMangino、NathanielSchutta、NealFord、RajuGandhi、ScottDavis和StuartHalloway。他們影響著這本書向許多好的方面進(jìn)步。你在本書中發(fā)現(xiàn)的任何錯(cuò)誤,責(zé)任完全在我。特別要鳴謝ScottLeberknight;他是我遇到過最細(xì)心的評(píng)論者。他的評(píng)論如此詳盡且見解深刻,他花時(shí)間運(yùn)行了書中的每一段代碼。在一些我需要幫忙的地方,他總是非常友好的幫我再過一遍。一本編程語(yǔ)言書的作者所能要求的,還有什么能比讓語(yǔ)言的創(chuàng)造者對(duì)書進(jìn)行審校更好的呢?我誠(chéng)摯的感謝MartinOdersky,感謝他那無(wú)價(jià)的評(píng)論、修正和建議。你在讀的這本書經(jīng)過了良好的打磨、修正、細(xì)化和重構(gòu)。有一個(gè)人勇于閱讀和編輯每個(gè)單詞,就如同是它們只是通過我指尖流露出來(lái)的一般。他做到了,唯一的延遲是互聯(lián)網(wǎng)強(qiáng)加給我們的。他為我展示一個(gè)人可能對(duì)你是如何的嚴(yán)格,與此同時(shí),又能夠不斷地激勵(lì)你。我承諾再寫一本書,如果他承諾再編輯的話。我從心底里感謝DanielSteinberg。我要特別鳴謝PragmaticProgrammers,AndyHunt和DaveThomas,他們開啟了這本書的項(xiàng)目,并支撐著完成它。感謝你們提供了如此敏捷的環(huán)境和設(shè)置了如此高的標(biāo)準(zhǔn)。很高興再次為你們寫書。感謝JanetFurlow、KimWimpsett、StevePeter以及整個(gè)PragmaticBookshelf團(tuán)隊(duì),有了你們的協(xié)助,才有了這本書。我還要鳴謝DustinWhitney、JonathanSmith、JoshMcDonald、FredJason、VladimirKelman和JeffSack,感謝他們?cè)诒緯搲▍⒁姼戒汚)和email交流中給予我的鼓勵(lì)。我還要鳴謝本書beta版的讀者,他們提供了很有價(jià)值的評(píng)論和反饋。感謝DanielGlauser、DavidBailey、KaiVirkki、LeifJantzen、LudovicKuty、MorrisJones、PeterOlsen和RenaudFlorquin為beta版報(bào)告的錯(cuò)誤。感謝JayZimmerman,NFJS系列大會(huì)(\h)的主管,他為我提供了機(jī)會(huì),展現(xiàn)一些想法和主題,正是這些內(nèi)容幫我塑成了本書。感謝與會(huì)的geek——演講者和參會(huì)者——讓我有機(jī)會(huì)與你們交流。你們是靈感之源,我從你們身上學(xué)到了很多。我還要“并發(fā)”地鳴謝MartinOdersky和Scala社區(qū),他們的付出讓我們擁有了如此美妙的語(yǔ)言。感謝我的妻子Kavitha同兩個(gè)兒子Karthik和Krupakar,沒有你們的巨大支持、耐心和鼓勵(lì),編寫本書是不可能的。這本書始于Krupa問“爸爸,Scala是什么?”,止于Karthik說“我今年夏天要學(xué)Scala”,以及我妻子在其間不斷穩(wěn)定提供的垃圾食品、咖啡因飲料與刨根問底的問題。下面這段完全函數(shù)式的Scala代碼是獻(xiàn)給他們的:("thankyou!"*3)foreachprint。

第2章起步讓我們開始寫一些Scala代碼吧!在這一章里,你會(huì)裝上Scala,確保一切都能在系統(tǒng)中運(yùn)作良好。2.1下載ScalaScala起步很簡(jiǎn)單。首先,訪問\h,點(diǎn)擊“DownloadScala”鏈接,下載最新的穩(wěn)定版本。選擇最適合你所用的平臺(tái)的版本,排在最上面的是當(dāng)前的發(fā)布版本①。在MacOSX上,我下載的是scala-2.7.4.final.tar.gz,在WindowsVista上,我下載的就是scala-2.7.4.final.zip。如果你對(duì)ScalaAPI或者源碼感興趣,就需要下載其他的文件。①如果你想找早期的版本,在頁(yè)面上有一節(jié)是“PreviousReleases”。本書的例子是在Scala2.7.4版本上運(yùn)行通過的。如果你是個(gè)追求最前沿技術(shù)的家伙,穩(wěn)定版本肯定滿足不了你的需求。這個(gè)語(yǔ)言實(shí)現(xiàn)在不斷更新,你所需要的就是它的最新版本。在下載頁(yè)面上往下找,找到“ReleaseCandidate”一節(jié),下載適合你所用平臺(tái)最新的候選版本。當(dāng)然,如果你想要實(shí)時(shí)更新的版本,而且愿意承擔(dān)風(fēng)險(xiǎn),還可以下載每日構(gòu)建的版本。不管你選了哪個(gè)版本,都需要JDK1.4或更高版本②的支持。我推薦你至少要用Java5,這樣才能在Scala中享受到最新的Java語(yǔ)言特性?;c(diǎn)時(shí)間檢查一下,看看Java裝的是哪個(gè)版本,是不是能用。②參見\h/javase/downloads/index.jsp。2.2安裝Scala先把Scala裝起來(lái)吧!前提是你已經(jīng)下載了Scala2.7.4的二進(jìn)制發(fā)行版,并且Java也裝好了。(參見2.1節(jié)。)2.2.1在Windows上安裝Scala把發(fā)行包解壓縮——我是直接右擊scala-2.7.4.final.zip然后選擇“ExtractHere”。接著把解壓縮后的目錄拷貝到合適的位置,比如我就把scala-2.7.4.final挪到了C:\programs\scala目錄下。③③我推薦你選擇一個(gè)中間不帶空格的路徑,因?yàn)橛锌崭竦穆窂矫3?huì)帶來(lái)麻煩。還有一步工作要做。你要設(shè)置Scalabin目錄的路徑。進(jìn)入控制面板,打開“系統(tǒng)”,選擇“高級(jí)系統(tǒng)設(shè)置”,選擇“高級(jí)”,然后選擇“環(huán)境變量”④。修改path這個(gè)變量,把Scala的bin目錄也放進(jìn)去。比如在我的機(jī)器上,我就把C:\programs\scala\scala2.7.4.final\bin加進(jìn)到path里面去了。記住,在path里用分號(hào)(;)分隔目錄。④對(duì)于Vista之外的Windows版本,請(qǐng)選擇適當(dāng)?shù)姆椒▉?lái)修改環(huán)境變量。先驗(yàn)證一下配置是否正確。關(guān)閉所有已經(jīng)打開的命令行窗口,因?yàn)閷?duì)環(huán)境變量的修改要等到你重新打開窗口時(shí)才能生效。在新的命令行窗口里,輸入scala-version,確保顯示的版本跟你安裝的版本一樣?,F(xiàn)在Scala就可以用了!2.2.2在類UNIX系統(tǒng)上安裝Scala在類Unix系統(tǒng)上安裝Scala有好幾種選擇。如果你用的是MacOSX,就可以用MacPorts的sudoportinstallscala命令安裝。你也可以使用下面這個(gè)命令把發(fā)行包解壓縮:gunzipscala-2.7.4.final.tar.gz,然后運(yùn)行tar-xfscala-2.7.4.final.tar,把解壓后的目錄移到一個(gè)合適的位置。在我的系統(tǒng)上,我把scala-2.7.4.final目錄復(fù)制到了/opt/scala目錄下面。還有一步工作要做:設(shè)置Scalabin目錄的路徑。用的shell不一樣,要修改的文件也不一樣。你很可能已經(jīng)知道了要修改哪個(gè)文件——如果需要幫助的話,請(qǐng)參見對(duì)應(yīng)shell的文檔,或者直接找了解的人問。我用的是bash,所以我修改的是~/.bash_profile文件。在該文件中,我把/opt/scala/scala-2.7.4.final/bin加到了path環(huán)境變量里。先驗(yàn)證一下配置是否正確。關(guān)閉所有已經(jīng)打開的命令行窗口,因?yàn)榄h(huán)境變量的修改要等到重新打開窗口時(shí)才能生效⑤。在新的終端窗口中,輸入scala-version,確保顯示的版本跟你安裝的版本一樣?,F(xiàn)在Scala就可以用了?、輳募夹g(shù)上來(lái)說,我們可以對(duì)配置文件執(zhí)行source,但是打開個(gè)新窗口可能更省事。2.3讓Scala跑起來(lái)想快速嘗試一下Scala的話,直接用scala這個(gè)命令行shell就行。你可以在上面嘗試著運(yùn)行一些簡(jiǎn)單的Scala代碼片斷。在編寫應(yīng)用的時(shí)候,這個(gè)有用的工具可以幫你快速試驗(yàn)一些新代碼。在命令行上(不管是終端窗口還是命令提示符),輸入scala。你可以見到下面的介紹信息和一個(gè)提示符:>scala

WelcometoScalaversion2.7.4.final(JavaHotSpot(TM)ClientVM,

Java1.5.0_16).

Typeinexpressionstohavethemevaluated.

Type:helpformoreinformation.

scala>

在提示符后面,輸入valnumber=6,然后回車。Scalashell會(huì)做出如下反應(yīng),表示它把number這個(gè)變量推演為Int類型,因?yàn)槲覀兘o變量的賦值是6。scala>valnumber=6

number:Int=6

scala>

現(xiàn)在試著輸入number=7,Scala會(huì)給出下面的錯(cuò)誤信息:scala>number=7

<console>:5:error:reassignmenttoval

number=7

^

scala>

Scala告訴我們,number這個(gè)常量不能重新賦值。但我們還是可以在控制臺(tái)上重新定義常量與變量。例如輸入valnumber=7,Scala就能接受了。scala>valnumber=7

number:Int=7

scala>

只有在交互式shell上,我們才能在同一個(gè)作用范圍內(nèi)對(duì)常量和變量進(jìn)行重新定義,這在真正的Scala代碼或者腳本中是行不通的——所以說,shell的這種靈活性讓我們可以更輕松地在上面進(jìn)行試驗(yàn)。輸入vallist=List(1,2,3),你會(huì)發(fā)現(xiàn)Scala推演出了List的類型并輸出:List:List[Int]=List(1,2,3)。無(wú)論什么時(shí)候,只要你不確定某個(gè)表達(dá)式會(huì)被推演成什么類型,你都可以很快地在shell里試出結(jié)果。你可以用“up”鍵找到之前輸入的命令。它甚至還可以找到上一次調(diào)用shell時(shí)用過的命令。輸入命令的時(shí)候,你可以用Ctrl+A回到行首,用Ctrl+E到達(dá)行尾。只要你敲下回車鍵,shell就會(huì)立刻執(zhí)行你輸入的東西。如果你的語(yǔ)句沒寫完(例如方法定義寫了一半)就按了回車,shell會(huì)提示一個(gè)豎線(|),讓你把定義寫完。例如下面我就是用了兩行來(lái)完成isPalindrome()方法的定義,然后調(diào)用了兩次并查看結(jié)果:scala>defisPalindrome(str:String)=

|str==str.reverse.toString()

isPalindrome:(String)Boolean

scala>isPalindrome("mom")

res1:Boolean=true

scala>isPalindrome("dude")

res2:Boolean=false

scala>

Shell上的工作做完以后,你可以輸入:quit或者exit退出shell。除了用shell外,我們還可以在命令行上用-e這個(gè)選項(xiàng)(其含義為執(zhí)行參數(shù))把簡(jiǎn)短的語(yǔ)句或是表達(dá)式傳給Scala。GettingStarted/RunScalaOnCommandLine.cmdscala-e"println(\"Hello\"+args(0)+\",\"+args(1))"Buddy

"WelcometoScala"

Scala會(huì)返回下面的信息:HelloBuddy,WelcometoScala

我們用了()而不是[]來(lái)索引args變量——這是個(gè)Scala的慣用法,我們稍后討論。如果你在文件里寫好了Scala代碼,可以用:load選項(xiàng)把它載入shell。比如要加載一個(gè)名為script.scala的文件,就在shell里面輸入:loadscript.scala。這個(gè)選項(xiàng)可以用來(lái)加載預(yù)先寫好的函數(shù)和類,并在shell里試驗(yàn)它們。2.4命令行上的Scala盡管shell和-e選項(xiàng)提供了很便捷的方式試驗(yàn)代碼片斷,但如果你想執(zhí)行文件中Scala代碼,那么就會(huì)用到scala命令。在沒有提供參數(shù)的情況下,它會(huì)以交互模式運(yùn)行;如果提供了文件名,它就會(huì)以批處理模式運(yùn)行。代碼文件可以是腳本,也可以是目標(biāo)文件(目標(biāo)文件是指編譯器生成的.class文件)。默認(rèn)情況下,你都可以讓這個(gè)工具去測(cè)試你所提供的文件是哪種類型,也可以用-howtorun選項(xiàng)來(lái)告訴它,提供的到底是腳本文件,還是目標(biāo)文件。最后一點(diǎn),在給它傳遞Java屬性的時(shí)候,可以用-Dproperty=value格式。假設(shè)我們已經(jīng)有了一個(gè)文件,叫做Helloworld.scala:GettingStarted/HelloWorld.scalaprintln("HelloWorld,WelcometoScala")

我們可以用scalaHelloWorld.scala這個(gè)命令來(lái)執(zhí)行上面的腳本:>scalaHelloWorld.scala

HelloWorld,WelcometoScala

>

不管有什么樣的參數(shù)需要傳入,都可以附加在文件名后面。把Scala代碼寫到文件里面當(dāng)作腳本執(zhí)行,這個(gè)功能是相當(dāng)方便的。你可以寫一些跟系統(tǒng)維護(hù)或者管理任務(wù)相關(guān)的代碼,然后在命令行或者你喜歡的IDE里面運(yùn)行,無(wú)須額外的編譯工作。Scala工具在內(nèi)存里把腳本編譯成字節(jié)碼,然后執(zhí)行。它把代碼放到一個(gè)傳統(tǒng)的main()方法中,這個(gè)方法歸屬于一個(gè)名叫Main的類。所以當(dāng)你執(zhí)行腳本的時(shí)候,實(shí)際上執(zhí)行的是Main這個(gè)類中的main()方法。如果你想看生成的字節(jié)碼,可以在文件名前面加上-savecompiled選項(xiàng),Scala工具會(huì)把字節(jié)碼存成一個(gè)JAR文件。2.5把Scala代碼當(dāng)作腳本運(yùn)行當(dāng)你開始用Scala寫腳本以后,你會(huì)發(fā)現(xiàn)執(zhí)行Scala文件就跟執(zhí)行shell腳本一樣簡(jiǎn)單。2.5.1在類UNIX系統(tǒng)上作為腳本運(yùn)行在類Unix系統(tǒng)上,你可以設(shè)置一個(gè)shell前導(dǎo)詞(preamble)來(lái)執(zhí)行腳本。如下例:GettingStarted/Script.scala#!/usr/bin/envscala

!#

println("Hello"+args(0))

先輸入chmod+xScript.scala,確保你對(duì)Script.scala文件有執(zhí)行權(quán)限。然后執(zhí)行此文件,在命令行上輸入./Script.scalaBuddy——Buddy是傳給腳本的參數(shù)。輸出如下:HelloBuddy

2.5.2在Windows上作為腳本運(yùn)行你可以配置Windows,讓它在運(yùn)行.scala文件的時(shí)候調(diào)用Scala。打開資源瀏覽器,雙擊一個(gè)帶.scala擴(kuò)展名的Scala腳本文件。Windows會(huì)告訴你它打不開這個(gè)文件,并讓你從已安裝的程序列表里面選擇一個(gè)程序去打開它。找到Scala安裝的位置,選擇scala.bat?,F(xiàn)在就可以在資源瀏覽器里面通過雙擊文件來(lái)執(zhí)行程序了。在命令行里面運(yùn)行也行,現(xiàn)在就不用帶命令前綴.scala了。如果在資源瀏覽器里面雙擊程序,你會(huì)發(fā)現(xiàn)有一個(gè)窗口先是彈出來(lái),然后顯示執(zhí)行結(jié)果,接著很快就關(guān)掉了。想讓窗口保持打開狀態(tài)的話,可以把文件指向一個(gè).bat文件,讓這個(gè).bat文件運(yùn)行完Scala之后暫停。右鍵單擊某個(gè)Scala程序,選擇“OpenWith…”,找到該bat文件。下面是.bat文件的一個(gè)例子:GettingStarted/RunScala.batechooff

cls

callscala%1

pause

當(dāng)你雙擊Helloworld.scala以后,上面那個(gè).bat文件會(huì)自動(dòng)執(zhí)行,得到下面的結(jié)果:2.6在IDE里面運(yùn)行Scala作為Java程序員,最有可能是用IDE開發(fā)應(yīng)用程序。Eclipse、IntelliJIDEA、NetBeans這三款I(lǐng)DE都有對(duì)應(yīng)的Scala插件(參見附錄A)。用了這些IDE,就可以在使用Scala的時(shí)候享受跟Java一樣的待遇,如語(yǔ)法高亮,代碼補(bǔ)全,調(diào)試,合適的縮進(jìn),等等。此外,你還可以在同一個(gè)項(xiàng)目中混合或是互相引用Scala和Java代碼。在Scala的網(wǎng)站上有為Eclipse安裝Scala插件的說明文檔,參見\h/node/94。2.7編譯Scala下面講述如何寫一個(gè)類,用scalac編譯器編譯。在下面的例子中,我們定義了一個(gè)對(duì)象,名叫Sample。(你很快會(huì)學(xué)到,Scala不支持靜態(tài)方法,要想寫靜態(tài)的main()方法,就得定義一個(gè)對(duì)象——一個(gè)單例⑥)⑥此處原文為Singletonclass,經(jīng)過與作者溝通,確認(rèn)class這個(gè)詞應(yīng)該去掉。所以譯作“單例”,而非“單例類”?!g者注GettingStarted/Sample.scalaobjectSample{

defmain(args:Array[String])=println("HelloScala")

}

我們可以用scalacSample.scala這個(gè)命令對(duì)它進(jìn)行編譯。執(zhí)行的方法有兩種,一種是用scala工具,一種是用java命令。用scala工具的話,輸入scalaSample就行。用java工具的話,還得需要在classpath里指定scala-library.jar。下面的例子中,先是用了scalac進(jìn)行編譯,然后分別使用scala工具和java工具執(zhí)行;在我的mac上是這樣做的:>scalacSample.scala

>scalaSample

HelloScala

>java-classpath/opt/scala/scala-2.7.4.final/lib/scala-library.jar:.Sample

HelloScala

>

在Windows上,你可以把classpath指向scala-library.jar所在的位置。在我的Vista虛擬機(jī)上,我就設(shè)成了C:\programs\scala\scala-2.7.4.final\lib\scalalibrary.jar;。在本章中,我們裝好了Scala,做了些簡(jiǎn)單嘗試?,F(xiàn)在你應(yīng)該準(zhǔn)備好進(jìn)入Scala編程的具體細(xì)節(jié)了。

第3章Scala步入正軌你可以基于自己已有的Java技能學(xué)習(xí)Scala。在本章中,我們從熟悉的地方——Java代碼——出發(fā),向Scala前進(jìn)。Scala在一些地方同Java類似,但差異之處更是不勝枚舉。Scala偏愛純粹的面向?qū)ο螅撬矔?huì)盡可能的把類型映射為Java類型。Scala支持類Java的命令式編程風(fēng)格,同時(shí)也支持函數(shù)式風(fēng)格。啟動(dòng)你最喜愛的編輯器,我們要開啟Scala之旅了!3.1把Scala當(dāng)作簡(jiǎn)潔的JavaScala擁有非常高的代碼密度——鍵入更少卻獲得更多。我們從一段Java代碼的例子開始:ScalaForTheJavaEyes/Greetings.java//Javacode

publicclassGreetings{

publicstaticvoidmain(String[]args){

for(inti=1;i<4;i++){

System.out.print(i+",");

}

System.out.println("ScalaRocks!!!");

}

}

輸出如下:1,2,3,ScalaRocks!!!

Scala使上面的代碼變得更簡(jiǎn)潔。首先,它并不關(guān)心是否使用分號(hào)。其次,在如此簡(jiǎn)單的例子里,把代碼放到Greetings類里并沒有什么真正的益處,因此,可以去掉。再次,沒有必要指定變量i的類型。Scala很聰明,足以推演出i是一個(gè)整數(shù)。最后,Scala使用println輸出字符串,不必鍵入System.out.println。把上面的代碼簡(jiǎn)化成Scala,如下:ScalaForTheJavaEyes/Greetings.scalafor(i<-1to3){

print(i+",")

}

println("ScalaRocks!!!")

運(yùn)行上面的Scala腳本,鍵入scalaGreetings.scala,或是在IDE里運(yùn)行。你應(yīng)該看到這樣的輸出:1,2,3,ScalaRocks!!!

Scala的循環(huán)結(jié)構(gòu)相當(dāng)輕量級(jí)。只要說明索引i的值是從1到3即可。箭頭(<-)左邊定義了一個(gè)val,而不是var(參見下面的注解),右邊是一個(gè)生成器表達(dá)式(generatorexpression)。每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的val,用產(chǎn)生出來(lái)的連續(xù)值進(jìn)行初始化。valvs.var不管是val還是var,都可以用來(lái)定義變量。用val定義的變量是不可變的,初始化之后,值就固定下來(lái)了。用var定義的變量是可變的,修改多少次都行。這里的不變性指的是變量本身,而不是變量所引用的實(shí)例。比如說,如果寫valbuffer=newStringBuffer(),就不能把buffer指向其他的引用。但我們依然可以用諸如append()之類的方法來(lái)修改StringBuffer的實(shí)例。另外,如果用valstr="hello"定義了一個(gè)String的實(shí)例,就不能再做修改了,因?yàn)镾tring本身也是不可變的。要想讓一個(gè)類的實(shí)例不可變,可以把它的所有字段都定義為val,然后只提供讀取實(shí)例狀態(tài)的方法,不提供修改的方法。在Scala里,應(yīng)該盡量?jī)?yōu)先使用val,而不是var;這可以提升不變性和函數(shù)式風(fēng)格。上面代碼產(chǎn)生的范圍包含了下界(1)和上界(3)。用until()方法替換to()方法,就可以從范圍內(nèi)排除上界。ScalaForTheJavaEyes/GreetingsExclusiveUpper.scalafor(i<-1until3){

print(i+",")

}

println("ScalaRocks!!!")

你會(huì)看到如下輸出:1,2,ScalaRocks!!!

是的,你沒聽錯(cuò)。我確實(shí)說to()是方法了。實(shí)際上,to()和until()都是RichInt的方法①,這個(gè)類型是由Int隱式轉(zhuǎn)換而來(lái)的,而Int是變量i的推演類型。這兩個(gè)函數(shù)返回的是一個(gè)Range的實(shí)例。因此,調(diào)用1to3等價(jià)于1.to(3),但前者更優(yōu)雅。在下面的注解中,我們會(huì)更多的討論這一迷人的特性。①我們會(huì)在3.2節(jié)“Java基本類型對(duì)應(yīng)的Scala類”中討論富封裝器(richwrapper)。點(diǎn)和括號(hào)是可選的如果方法有0或1個(gè)參數(shù),點(diǎn)和括號(hào)是可以丟掉的。如果方法的參數(shù)多于一個(gè),就必須使用括號(hào),但是點(diǎn)仍然是可選的。你已經(jīng)看到這樣做的益處:a+b實(shí)際上是a.+(b),1to3實(shí)際上是1.to(3)。利用這樣輕量級(jí)語(yǔ)法的優(yōu)勢(shì),可以創(chuàng)建出讀起來(lái)更自然的代碼。比如,假設(shè)為類Car定義了一個(gè)turn()方法:defturn(direction:String)//...

用輕量級(jí)語(yǔ)法調(diào)用上面的方法,如下:carturn"right"

享受可選的點(diǎn)和括號(hào),削減代碼中的雜亂吧!在上面的例子里,看上去是在循環(huán)迭代中給i重新賦了值。然而,i并不是一個(gè)var,而是一個(gè)val。每次循環(huán)都創(chuàng)建一個(gè)不同的val,名字叫做i。注意,我們不可能由于疏忽在循環(huán)里修改了i的值,因?yàn)閕是不變的。這里,我們已經(jīng)悄然向函數(shù)式風(fēng)格邁進(jìn)了一步。使用foreach(),還可以用更貼近函數(shù)式的風(fēng)格執(zhí)行循環(huán):ScalaForTheJavaEyes/GreetingsForEach.scala(1to3).foreach(i=>print(i+","))

println("ScalaRocks!!!")

輸出如下:1,2,3,ScalaRocks!!!

上面的例子很簡(jiǎn)潔,沒有賦值。我們用到了Range類的foreach()方法。這個(gè)方法以一個(gè)函數(shù)值作為參數(shù)。所以,要在括號(hào)里面提供一段代碼體,接收一個(gè)實(shí)參,在這個(gè)例子里面命名為i。=>將左邊的參數(shù)列表和右邊的實(shí)現(xiàn)分離開來(lái)。3.2Java基本類型對(duì)應(yīng)的Scala類Java世界呈現(xiàn)出一個(gè)割裂的現(xiàn)象,有對(duì)象,有基本類型,比如int、double等。Scala把一切都視為對(duì)象。Java把基本類型同對(duì)象區(qū)分對(duì)待。從Java5開始,自動(dòng)裝箱可以為對(duì)象方法傳遞基本類型。然而,Java不支持在基本類型上調(diào)用方法,像這樣:2.toString()。與之不同的是,Scala把一切都視為對(duì)象。也就是說可以在字面量上調(diào)用對(duì)象,就像調(diào)用對(duì)象方法一樣。下面的代碼創(chuàng)建了一個(gè)Scala的Int實(shí)例,將其傳給java.util.ArrayList的ensureCapacity()方法,這個(gè)方法需要傳入一個(gè)Java基本類型int。ScalaForTheJavaEyes/ScalaInt.scalaclassScalaInt{

defplayWithInt(){

valcapacity:Int=10

vallist=newjava.util.ArrayList[String]

list.ensureCapacity(capacity)

}

}

在上面的代碼里②,Scala悄悄地把Scala.Int當(dāng)作Java的基本類型Int。其結(jié)果是不會(huì)在運(yùn)行時(shí)因?yàn)轭愋娃D(zhuǎn)換而帶來(lái)性能損耗。②可以定義成valcapacity=10,讓Scala推演類型,但是也可以顯式指定,說明與Javaint的兼容性。類似的魔法還有,對(duì)Int調(diào)用類似于to()這樣的方法,比如,1.to(3)或是1to3。當(dāng)Scala確定Int無(wú)法滿足要求時(shí),就會(huì)悄悄地應(yīng)用intWrapper()方法把Int轉(zhuǎn)化③為scala.runtime.RichInt,然后調(diào)用它的to()方法。③在7.5節(jié)“隱式類型轉(zhuǎn)換”中探討了隱式類型轉(zhuǎn)換。諸如RichInt,RichDouble,RichBoolean之類的類,叫做富封裝類。這些類表示Java的基本類型和字符串,它們提供了一些便捷方法,可以在Scala類里使用。3.3元組與多重賦值假定有個(gè)函數(shù)要返回多個(gè)值。比如,返回一個(gè)人的名、姓和email地址。如果使用Java的話,一種方式是返回一個(gè)PersonInfo類的實(shí)例,其中包括與那些數(shù)據(jù)對(duì)應(yīng)的字段。另一種方式是返回一個(gè)包含這些值的String[]或ArrayList,然后對(duì)結(jié)果進(jìn)行循環(huán),取出這些值。Scala提供了一種更簡(jiǎn)單的方式做這件事:元組和多重賦值。元組是一個(gè)不變的對(duì)象序列,可以用逗號(hào)分隔的值進(jìn)行創(chuàng)建。比如,下面表示一個(gè)有3個(gè)對(duì)象的元組:("Venkat","Subramaniam","venkats@")。元組元素可以同時(shí)賦給多個(gè)var或val,如下面這個(gè)例子所示:ScalaForTheJavaEyes/MultipleAssignment.scaladefgetPersonInfo(primaryKey:Int)={

//假設(shè)用primaryKey獲取一個(gè)人的信息

//這里的返回值被硬編碼了。

("Venkat","Subramaniam","venkats@")

}

val(firstName,lastName,emailAddress)=getPersonInfo(1)

println("FirstNameis"+firstName)

println("LastNameis"+lastName)

println("EmailAddressis"+emailAddress)

執(zhí)行這段代碼的輸出如下:FirstNameisVenkat

LastNameisSubramaniam

EmailAddressisvenkats@

如果嘗試將方法結(jié)果賦給數(shù)量不一致的變量會(huì)怎么樣呢?Scala會(huì)密切地關(guān)注你,一旦這種情況發(fā)生,它就會(huì)報(bào)錯(cuò)。如果是編譯代碼,而不是作為腳本執(zhí)行,Scala在編譯時(shí)就會(huì)提示錯(cuò)誤。比如,下面這個(gè)例子,將方法調(diào)用的結(jié)果賦值給數(shù)量少于元組個(gè)數(shù)的變量。ScalaForTheJavaEyes/MultipleAssignment2.scaladefgetPersonInfo(primaryKey:Int)={

("Venkat","Subramaniam","venkats@")

}

val(firstName,lastName)=getPersonInfo(1)

Scala會(huì)報(bào)告這樣的錯(cuò)誤:(fragmentofMultipleAssignment2.scala):5:error:

constructorcannotbeinstantiatedtoexpectedtype;

found:(T1,T2)

required:(java.lang.String,java.lang.String,java.lang.String)

val(firstName,lastName)=getPersonInfo(1)

^

...

就算不賦值,也可以訪問元組里的單個(gè)元素。比如,如果執(zhí)行了valinfo=getPer-sonInfo(1),就可以用這樣的語(yǔ)法info._1,訪問第一個(gè)元素,第二個(gè)用info._2,以此類推。元組不僅僅對(duì)多重賦值中有用。在并發(fā)編程里,使用元組可以把一組數(shù)據(jù)值作為消息在Actor之間傳遞(它們不變的屬性剛好在這里派得上用場(chǎng))。如此簡(jiǎn)潔的語(yǔ)法會(huì)讓消息發(fā)送端的代碼極為精煉。在接收端,使用模式匹配會(huì)讓接收和處理消息變得簡(jiǎn)單,在9.3節(jié)“匹配元組和list”中會(huì)詳細(xì)介紹。3.4字符串與多行原始字符串Scala的字符串只不過是java.lang.String,可以按照J(rèn)ava的方式使用字符串。不過,Scala還為使用字符串提供了一些額外的便利。Scala可以自動(dòng)把String轉(zhuǎn)換成scala.runtime.RichString——這樣你就可以無(wú)縫地使用諸如capitalize()、lines()和reverse這樣一些便捷的方法④。④不過,這個(gè)無(wú)縫轉(zhuǎn)換后的結(jié)果可能會(huì)讓你大吃一驚。比如,"mom".reverse=="mom"的結(jié)果是false,因?yàn)樽罱K比較的是RichString的實(shí)例和String的實(shí)例。不過,"mom".reverse.toString=="mom"的結(jié)果是true。在Scala里,創(chuàng)建多行字符串真的很容易,只要把多行字符串放在3個(gè)雙引號(hào)間("""...""")即可。這是Scala對(duì)于heredocument,或者叫heredoc的支持。這里,我們創(chuàng)建了一個(gè)3行長(zhǎng)的字符串:ScalaForTheJavaEyes/MultiLine.scalavalstr="""Inhisfamousinauguralspeech,JohnF.Kennedysaid

"Andso,myfellowAmericans:asknotwhatyourcountrycando

foryou-askwhatyoucandoforyourcountry."Hethenproceeded

tospeaktothecitizensoftheWorld..."""

println(str)

輸出如下:Inhisfamousinauguralspeech,JohnF.Kennedysaid

"Andso,myfellowAmericans:asknotwhatyourcountrycando

foryou-askwhatyoucandoforyourcountry."Hethenproceeded

tospeaktothecitizensoftheWorld...

Scala允許在字符串里嵌入雙引號(hào)。Scala會(huì)將三個(gè)雙引號(hào)里的內(nèi)容保持原樣,在Scala里,稱為原始字符串。實(shí)際上,Scala處理字符串有些望文生義;如果不想把代碼里的縮進(jìn)帶到字符串里,可以用RichString的便捷方法stripMargin(),像這樣:ScalaForTheJavaEyes/MultiLine2.scalavalstr="""Inhisfamousinauguralspeech,JohnF.Kennedysaid

|"Andso,myfellowAmericans:asknotwhatyourcountrycando

|foryou-askwhatyoucandoforyourcountry."Hethenproceeded

|tospeaktothecitizensoftheWorld...""".stripMargin

println(str)

stripMargin()會(huì)去掉先導(dǎo)管道符(|)前所有的空白或控制字符。如果出現(xiàn)在其他地方,而不是每行的開始,就會(huì)保留管道符。如果出于某種原因,這個(gè)符號(hào)有特殊的用途,可以用stripMargin()方法的變體,接收你所選擇的其他邊緣(margin)字符。上面代碼的輸出如下:Inhisfamousinauguralspeech,JohnF.Kennedysaid

"Andso,myfellowAmericans:asknotwhatyourcountrycando

foryou-askwhatyoucandoforyourcountry."Hethenproceeded

tospeaktothecitizensoftheWorld...

創(chuàng)建正則表達(dá)式時(shí),你會(huì)發(fā)現(xiàn)原始字符串非常有用。鍵入和閱讀"""\d2:\d2"""可比"\\d2:\\d2"容易。3.5自適應(yīng)的默認(rèn)做法Scala有一些默認(rèn)做法,會(huì)讓代碼更簡(jiǎn)潔、更易讀寫。下面列了幾個(gè)這樣的特性:它支持腳本,無(wú)需將所有的代碼都放到類里。如果腳本可以滿足需求,就把可執(zhí)行代碼直接放到文件里,而不必弄出一個(gè)沒必要的垃圾類。return是可選的。方法調(diào)用會(huì)自動(dòng)返回最后求值的表達(dá)式,假定它符合方法聲明的返回類型。不顯式地放置return會(huì)使代碼更簡(jiǎn)潔,特別是傳閉包做方法參數(shù)時(shí)。分號(hào)(;)是可選的。不必在每個(gè)語(yǔ)句的后面都寫上分號(hào)⑤,這會(huì)使代碼更簡(jiǎn)潔。如果想在同一行內(nèi)放多條語(yǔ)句,可以用分號(hào)進(jìn)行分隔。Scala很聰明,能識(shí)別出語(yǔ)句是否完整,如果語(yǔ)句包含多行可以在下一行繼續(xù)輸入。⑤參見3.7節(jié),“分號(hào)是半可選的”。類和方法默認(rèn)是public,因此不必顯式使用public關(guān)鍵字。Scala提供了輕量級(jí)的語(yǔ)法創(chuàng)建JavaBean——用更少的代碼創(chuàng)建變量和final屬性(參見4.1節(jié),“創(chuàng)建類”)。不會(huì)強(qiáng)制捕獲一些不關(guān)心的異常(參見13.1節(jié),“異常處理”),降低了代碼規(guī)模,也避免了不恰當(dāng)?shù)漠惓L幚?。另外,默認(rèn)情況下,Scala會(huì)導(dǎo)入兩個(gè)包和scala.Predef對(duì)象,以及相應(yīng)的類和成員。只要用類名,就可以引用這些預(yù)導(dǎo)入的包。Scala按如下順序?qū)?nèi)容全部導(dǎo)入:java.langscalascala.Predef包含java.lang讓你無(wú)需在腳本中導(dǎo)入任何東西就可以使用常用的Java類型。比如,使用String時(shí),無(wú)需加上java.lang的包名,也不必導(dǎo)入。因?yàn)閟cala包中的所有內(nèi)容都導(dǎo)入了,所以也可以很容易地使用Scala的類型。Predef對(duì)象包含了類型,隱式轉(zhuǎn)換,以及Scala中常用的方法。既然這些類是默認(rèn)導(dǎo)入的,不需要任何前綴,也無(wú)需導(dǎo)入,即可使用這些方法和轉(zhuǎn)換。這些操作非常便捷,以至于剛開始,你會(huì)把它們當(dāng)作是語(yǔ)言的一部分,實(shí)際上,它們是Scala程序庫(kù)的一部分。Predef對(duì)象也為諸如scala.collection.immutable.Set和scala.collection.immutable.Map這樣的東西提供了別名。比如,引用Set或Map,實(shí)際上引用的是他們?cè)赑redef中的定義,它們會(huì)依次轉(zhuǎn)換為其在scala.collection.immutable包里的定義。3.6運(yùn)算符重載從技術(shù)的角度來(lái)看,Scala沒有運(yùn)算符,提及“運(yùn)算符重載”時(shí),指的是重載像+

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論