Scala詳細總結(jié)(精辟版++)_第1頁
Scala詳細總結(jié)(精辟版++)_第2頁
Scala詳細總結(jié)(精辟版++)_第3頁
Scala詳細總結(jié)(精辟版++)_第4頁
Scala詳細總結(jié)(精辟版++)_第5頁
已閱讀5頁,還剩23頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

Scala總結(jié)--2015-1-1912:33:54 本文檔針對scala2.10.x,由于scala目前發(fā)展迅速,因此可能會和其他版本的不同。===概述scala是一門以java虛擬機(JVM)為目標運行環(huán)境并將面向?qū)ο蠛秃瘮?shù)式編程的最佳特性結(jié)合在一起的靜態(tài)類型編程語言。scala是純粹的面向?qū)ο蟮恼Z言。java雖然是面向?qū)ο蟮恼Z言,但是它不是純粹的,因為java的基本數(shù)據(jù)類型不是類,并且在java中還有靜態(tài)成員變量和靜態(tài)方法。相反,scala是純粹面向?qū)ο蟮?,每個值都是對象,每個操作都是方法調(diào)用。scala也是一個成熟的函數(shù)式語言。函數(shù)式編程有兩個指導(dǎo)思想:①函數(shù)是頭等值,也就是說函數(shù)也是值,并且和其他類型(如整數(shù)、字符串等)處于同一地位,函數(shù)可以被當作參數(shù)傳遞,也可以被當作返回值返回,還可以在函數(shù)中定義函數(shù)等等;②程序的操作應(yīng)該把輸入值映射為輸出值而不是就地修改,也就是說函數(shù)調(diào)用不應(yīng)產(chǎn)生副作用,雖然函數(shù)式編程語言鼓勵使用“無副作用”的方法,但是scala并不強制你必須這么做。scala允許你使用指令式的編程風(fēng)格,但是隨著你對scala的深入了解,你可能會更傾向于一種更為函數(shù)式的編程風(fēng)格。向函數(shù)式編程轉(zhuǎn)變,你就應(yīng)該盡量去使用val、不可變對象、無副作用方法,而不是var、可變對象、有副作用方法。要明白的是,從指令式編程向函數(shù)式編程的轉(zhuǎn)變會很困難,因此你要做好充分的準備,并不斷的努力。scala運行于JVM之上,并且它可以訪問任何的java類庫并且與java框架進行互操作,scala也大量重用了java類型和類庫。第一個scala程序:objectScalaTest{ defmain(args:Array[String]){ println("helloscala.") }}===scala解釋器 安裝好scala并配置好PATH環(huán)境變量之后,就可以在終端中輸入“scala”命令打開scala解釋器。在其中,你可以像使用shell一樣,使用TAB補全、Ctrl+r搜索、上下方向鍵切換歷史命令等等。退出scala解釋器,可以使用命令:“:q”或者“:quit”。 由于解釋器是輸入一句執(zhí)行一句,因此也常稱為REPL。REPL一次只能看到一行代碼,因此如果你要在其中粘貼代碼段的話,可能會出現(xiàn)問題,這時你可以使用粘貼模式,鍵入如下語句: :paste然后把代碼粘貼進去,再按下Ctrl+d,這樣REPL就會把代碼段當作一個整體來分析。===scala作為腳本運行 scala代碼也可以作為腳本運行,只要你設(shè)置好代碼文件的shell前導(dǎo)詞(preamble),并將代碼文件設(shè)置為可執(zhí)行。如下: #!/usr/bin/envscala println("這是scala腳本")設(shè)置代碼文件為可執(zhí)行,即可執(zhí)行之啦。scala腳本的命令行參數(shù)保存在名為args的數(shù)組中,你可以使用args獲取命令行輸入的程序參數(shù)。===scala編譯運行 scala編譯器scalac會將scala代碼編譯為jvm可以運行的字節(jié)碼,然后就可以在jvm上執(zhí)行了。假設(shè)有一個Hello.scala文件,我們就可以使用scalacHello.scala編譯,然后使用scalaHello運行。當然也可以使用java工具來運行,但需要在classpath里指定scala-library.jar。對于classpath,在Unix家族的系統(tǒng)上,類路徑的各個項目由冒號“:”分隔,在MSWindows系統(tǒng)上,它們由分號“;”分隔。例如,在linux上你可以輸入這樣的命令來運行(注意classpath最后加一個“.”):java-classpath/usr/local/scala-2.10.4/lib/scala-library.jar:.Hello===scalaIDE開發(fā)環(huán)境你可以使用eclipse或者intellijidea作為scala的IDE開發(fā)環(huán)境,但都需要安裝scala插件才行。下面分別介紹這兩種方式:eclipse開發(fā)環(huán)境配置:scalaideforeclipse(下載地址:)中集成了scala插件,你可以直接使用它進行開發(fā),不過它包含的可能不是我們想要的scala版本,因此,還是在該網(wǎng)站上下載對應(yīng)的scala插件,插在eclipse上,這樣更好啊。我們先安裝eclipsejuno,然后下載eclipsejuno以及scala2.10.4對應(yīng)的scalasdk插件升級包:update-site.zip。將插件解壓縮,將features和plugins目錄下的所有東東都復(fù)制到eclipse中的對應(yīng)目錄中,重啟eclipse即可。然后就可以新建scalaproject了。intellijidea開發(fā)環(huán)境配置:我們先安裝好intellijidea,然后安裝scala插件,自動安裝插件有時會非常慢,尤其是在china。我們還是手動配置插件吧。請注意插件的版本,必須與當前idea版本兼容。手動配置插件方法如下: (1)進入setting>plugins>browserepositorits搜索你要下載的插件名稱,右側(cè)可以找到下載地址。 (2)解壓插件壓縮包,把插件的全部文件都復(fù)制到IntelliJIDEA安裝程序的plugins文件夾中,注意插件最好以一個單獨的文件夾放在plugins目錄下。 (3)一般重啟intellijidea就會自動加載插件,進入setting>plugins看看有木有。如果不自動加載的話,進入setting>plugins>installpluginfromdisk,找到剛才復(fù)制的插件位置,再然后就好了。接下來就可以新建scalaproject,新建時我選擇的是“Scala”(不是sbt,因為我這選擇sbt之后,等半天sbt都不會配置好,郁悶?。?。相關(guān)姿勢: 什么是SBT?SBT=(notso)SimpleBuildTool,是scala的構(gòu)建工具,與java的maven地位相同。其設(shè)計宗旨是讓簡單的項目可以簡單的配置,而復(fù)雜的項目可以復(fù)雜的配置。===scala特點 在scala中,語句之后的“;”是可選的,這根據(jù)你的喜好。當有多個語句在同一行時,必須加上分號,但不建議把多個語句放在一行。 在scala中,建議使用2個空格作為代碼縮進,不過我咋喜歡一個tab呢⊙﹏⊙! 在scala中,符號“_”相當于java中的通配符“*”。 scala類似于c++、java,索引也是從0開始,但元組是個例外,它從1開始。===數(shù)據(jù)類型 scala有7種數(shù)值類型:Byte、Char、Short、Int、Long、Float和Double,以及2種非數(shù)值類型:Boolean和Unit(只有一個值“()”,相當于java和c++中的void,即空值)。這些類型都是抽象的final類(不能使用new新建,也不能被繼承),在scala包中定義,是對java基本數(shù)據(jù)類型的包裝,因此與java基本數(shù)據(jù)類型有相同的長度。同時,scala還提供了RichInt、RichChar等等,它們分別提供Int、Char等所不具備的便捷方法。 另外,scala沿用了java.lang包中的String。在scala中,常量也稱作字面量,字符串字面量由雙引號包含的字符組成,同時scala提供了另一種定義字符串常量的語法——原始字符串,它以三個雙引號作為開始和結(jié)束,字符串內(nèi)部可以包含無論何種任意字符。 在scala中,我們使用方法,而不是強制類型轉(zhuǎn)換,來做數(shù)值類型之間的轉(zhuǎn)換,如99.44.toInt、97.toChar。另外也可以參見顯式類型轉(zhuǎn)換和隱式轉(zhuǎn)換。===變量 scala有兩種變量:val和var。val如同java中的final變量,var如同java中的非final變量。由于scala是完全面向?qū)ο蟮?,因此val和var只是聲明了對象的引用是不可變的還是可變的,并不能說明引用指向的對象的可變性。聲明變量的同時需要初始化之,否則該變量就是抽象的。如果不指定變量的類型,編譯器會從初始化它的表達式中推斷出其類型。當然你也可以在必要的時候指定其類型,但注意,在scala中變量或函數(shù)的類型總是寫在變量或函數(shù)的名稱的后邊。示例如下: valanswer=“yes” valanswer,message:String=“yes”===標識符scala標識符有四種形式:字母數(shù)字標識符、操作符標識符、混合標識符、字面量標識符。字母數(shù)字標識符:跟其他語言類似,由字母、數(shù)字和下劃線組成,但需注意“$”字符被保留作為scala編譯器產(chǎn)生的標識符之用,你不要隨意使用它啊。操作符標識符:由一個或多個操作符字符組成。scala編譯器將在內(nèi)部“粉碎”操作符標識符以轉(zhuǎn)換成合法的內(nèi)嵌“$”的java標識符。若你想從java代碼中訪問這個標識符,就應(yīng)該使用這種內(nèi)部表示方式。混合標識符:由字母數(shù)字以及后面跟著的下劃線和一個操作符標識符組成。如unary_+定義了一個前綴操作符“+”。字面量標識符:是用反引號`…`包含的任意字符串,scala將把被包含的字符串作為標識符,即使被包含字符串是scala的關(guān)鍵字。例如:你可以使用Thread.`yield`()來訪問java中的方法,即使yield是scala的關(guān)鍵字。===操作符 scala的操作符和你在java和C++中的預(yù)期效果是一樣的,但注意scala并不提供++、--操作符。不過,scala中的操作符實際上都是方法,任何方法都可以當作操作符使用,如a+b相當于a.+(b)。 需要注意的是:對于不可變對象(注:對象的不可變并不是說它的引用變量是val的),并不真正支持類似于“+=”這樣以“=”結(jié)尾的操作符(即方法),不過scala還是提供了一些語法糖,用以解釋以“=”結(jié)尾的操作符用于不可變對象的情況。假設(shè)a是不可變對象的引用,那么在scala中a+=b將被解釋為a=a+b,這時就相當于新建一個不可變對象重新賦值給引用a,前提是引用變量a要聲明為var的,因為val變量定義之后是不可變的。更多信息參見函數(shù)(方法)部分。===塊表達式與賦值 在scala中,{}塊包含一系列表達式,其結(jié)果也是一個表達式,塊中最后一個表達式的值就是其值。在scala中,賦值語句本身的值是Unit類型的。因此如下語句的值為“()”: {r=r*n;n-=1}正是由于上述原因,scala中不能多重賦值,而java和c++卻可以多重賦值。因此,在scala中,如下語句中的x值為“()”: x=y=1===控制結(jié)構(gòu) scala和其他編程語言有一個根本性差異:在scala中,幾乎所有構(gòu)造出來的語法結(jié)構(gòu)都有值。這個特性使得程序結(jié)構(gòu)更加精簡。scala內(nèi)建的控制結(jié)構(gòu)很少,僅有if、while、for、try、match和函數(shù)調(diào)用等而已。如此之少的理由是,scala從語法層面上支持函數(shù)字面量。if表達式:scala的if/else語法結(jié)構(gòu)與java等一樣,但是在scala中if/else表達式有值,這個值就是跟在if/esle后邊的表達式的值。如下: vals=if(x>0)1else-1同時注意:scala的每個表達式都有一個類型,比如上述if/esle表達式的類型是Int。如果是混合類型表達式,則表達式的類型是兩個分支類型的公共超類型。String和Int的超類型就是Any。如果一個if語句沒有else部分,則當if條件不滿足時,表達式結(jié)果為Unit。如: if(x>0)1就相當于: if(x>0)1else()while循環(huán):scala擁有與java和c++中一樣的while和do-while循環(huán),while、do-while結(jié)果類型是Unit。for表達式:scala中沒有類似于for(;;)的for循環(huán),你可以使用如下形式的for循環(huán)語句: for(i<-表達式)該for表達式語法對于數(shù)組和所有集合類均有效。具體介紹如下:枚舉:for(i<-1to10),其中“i<-表達式”語法稱之為發(fā)生器,該語句是讓變量i(注意此處循環(huán)變量i是val的(但無需你指定),該變量的類型是集合的元素類型)遍歷表達式中的所有值。1to10產(chǎn)生的Range包含上邊界,如果不想包含上邊界,可以使用until。過濾:也叫守衛(wèi),在for表達式的發(fā)生器中使用過濾器可以通過添加if子句實現(xiàn),如:for(i<-1to10ifi!=5),如果要添加多個過濾器,即多個if子句的話,要用分號隔開,如:for(i<-1to10ifi!=5;ifi!=6)。嵌套枚舉:如果使用多個“<-”子句,你就得到了嵌套的“循環(huán)”,如:for(i<-1to5;j<-1toi)。流間變量綁定:你可以在for發(fā)生器以及過濾器等中使用變量保存計算結(jié)果,以便在循環(huán)體中使用,從而避免多次計算以得到該結(jié)果。流間變量綁定和普通變量定義相似,它被當作val,但是無需聲明val關(guān)鍵字。制造新集合:for(…)yield變量/循環(huán)體,最終將產(chǎn)生一個集合對象,集合對象的類型與它第一個發(fā)生器的類型是兼容的。 實際上:for表達式具有等價于組合應(yīng)用map、flatMap、filter和foreach這幾種高階函數(shù)的表達能力。實際上,所有的能夠yield(產(chǎn)生)結(jié)果的for表達式都會被編譯器轉(zhuǎn)譯為高階方法map、flatMap及filter的組合調(diào)用;所有的不帶yield的for循環(huán)都會被轉(zhuǎn)譯為僅對高階函數(shù)filter和foreach的調(diào)用。正是由于這幾個高階函數(shù)支持了for表達式,所以如果一個數(shù)據(jù)類型要支持for表達式,它就要定義這幾個高階函數(shù)。有些時候,你可以使用for表達式代替map、flatMap、filter和foreach的顯式組合應(yīng)用,或許這樣會更清晰明了呢。scala中沒有break和continue語句。如果需要類似的功能時,我們可以: 1)使用Boolean類型的控制變量 2)使用嵌套函數(shù),你可以從函數(shù)當中return 3)...match表達式與模式匹配:scala中沒有switch,但有更強大的match。它們的主要區(qū)別在于:任何類型的常量/變量,都可以作為比較用的樣本;在每個case語句最后,不需要break,break是隱含的;更重要的是match表達式也有值;如果沒有匹配的模式,則MatchError異常會被拋出。match表達式的形式為:選擇器match{備選項}。一個模式匹配包含了一系列備選項,每個都開始于關(guān)鍵字case。每個備選項都包含了一個模式以及一到多個表達式,它們將在模式匹配過程中被計算。箭頭符號“=>”隔開了模式和表達式。按照代碼先后順序,一旦一個模式被匹配,則執(zhí)行“=>”后邊的表達式((這些)表達式的值就作為match表達式的值),后續(xù)case語句不再執(zhí)行。示例如下: amatch{ case1=>"match1" case_=>"match_" }match模式的種類如下:通配模式:可以匹配任意對象,一般作為默認情況,放在備選項最后,如:case_=>變量模式:類似于通配符,可以匹配任意對象,不同的是匹配的對象會被綁定在變量上,之后就可以使用這個變量操作對象。所謂變量就是在模式中臨時生成的變量,不是外部變量,外部變量在模式匹配時被當作常量使用,見常量模式。注意:同一個模式變量只能在模式中出現(xiàn)一次。常量模式:僅匹配自身,任何字面量都可以作為常量,外部變量在模式匹配時也被當作常量使用,如:case"false"=>"false"casetrue=>"truth"caseNil=>"emptylist"對于一個符號名,是變量還是常量呢?scala使用了一個簡單的文字規(guī)則對此加以區(qū)分:用小寫字母開始的簡單名被當作是模式變量,所有其他的引用被認為是常量。如果常量是小寫命名的外部變量,那么它就得特殊處理一下了:如果它是對象的字段,則可以加上“this.”或“obj.”前綴;或者更通用的是使用字面量標識符解決問題,也即用反引號“`”包圍之。抽取器模式:抽取器機制基于可以從對象中抽取值的unapply或unapplySeq方法,其中,unapply用于抽取固定數(shù)量的東東,unapplySeq用于抽取可變數(shù)量的東東,它們都被稱為抽取方法,抽取器正是通過隱式調(diào)用抽取方法抽取出對應(yīng)東東的。抽取器中也可以包含可選的apply方法,它也被稱作注入方法,注入方法使你的對象可以當作構(gòu)造器來用,而抽取方法使你的對象可以當作模式來用,對象本身被稱作抽取器,與是否具有apply方法無關(guān)。樣本類會自動生成伴生對象并添加一定的句法以作為抽取器,實際上,你也可以自己定義一個任意其他名字的單例對象作為抽取器使用,以這樣的方式定義的抽取器對象與樣本類類型是無關(guān)聯(lián)的。你可以對數(shù)組、列表、元組進行模式匹配,這正是基于抽取器模式的。類型模式:你可以把類型模式當作類型測試和類型轉(zhuǎn)換的簡易替代,示例如下:cases:String=>s.length變量綁定:除了獨立的變量模式之外,你還可以把任何其他模式綁定到變量。只要簡單地寫上變量名、一個@符號,以及這個模式。模式守衛(wèi):模式守衛(wèi)接在模式之后,開始于if,相當于一個判斷語句。守衛(wèi)可以是任意的引用模式中變量的布爾表達式。如果存在模式守衛(wèi),只有在守衛(wèi)返回true的時候匹配才算成功。 Option類型:scala為可選值定義了一個名為Option的標準類型,一個Option實例的值要么是Some類型的實例,要么是None對象。分離可選值最通常的辦法是通過模式匹配,如下: caseSome(s)=>s caseNone=>“?” 模式無處不在:在scala中,模式可以出現(xiàn)在很多地方,而不單單在match表達式里。比如:模式使用在變量定義中,如下:valmyTuple=(123,“abc”)val(number,string)=myTuple模式匹配花括號中的樣本序列(即備選項)可以用在能夠出現(xiàn)函數(shù)字面量的任何地方,實質(zhì)上,樣本序列就是更普遍的函數(shù)字面量,函數(shù)字面量只有一個入口點和參數(shù)列表,樣本序列可以有多個入口點,每個都有自己的參數(shù)列表,每個樣本都是函數(shù)的一個入口點,參數(shù)被模式所特化。如下:valwithDefault:Option[Int]=>String={ caseSome(x)=>"isint" caseNone=>"?"}for表達式里也可以使用模式。示例如下:for((number,string)<-myTuple)println(number+string) 模式匹配中的中綴標注:帶有兩個參數(shù)的方法可以作為中綴操作符使用,使用中綴操作符時實際上是其中一個操作數(shù)在調(diào)用操作符對應(yīng)的方法,而另一個操作數(shù)作為方法的參數(shù)。但對于模式來說規(guī)則有些不同:如果被當作模式,那么類似于popq這樣的中綴標注等價于op(p,q),也就是說中綴標注符op被用做抽取器模式。===函數(shù)函數(shù)定義: 定義函數(shù)時,除了遞歸函數(shù)之外,你可以省略返回值類型聲明,scala會根據(jù)=號后邊的表達式的類型推斷返回值類型,同時=號后邊表達式的值就是函數(shù)的返回值,你無需使用return語句(scala推薦你使用表達式值代替return返回值,當然根據(jù)你的需要,也可以顯式使用return返回值)。示例如下: defabs(x:Double)=if(x>=0)xelse-x deffac(n:Int)={ varr=1 for(i<-1ton)r=r*i r }對于遞歸函數(shù)必須指定返回值類型,如下: deffac(n:Int):Int=if(n<=0)1elsen*fac(n-1)但你要知道的是:聲明函數(shù)返回類型,總是有好處的,它可以使你的函數(shù)接口清晰。因此建議不要省略函數(shù)返回類型聲明。 函數(shù)體定義時有“=”時,如果函數(shù)僅計算單個結(jié)果表達式,則可以省略花括號。如果表達式很短,甚至可以把它放在def的同一行里。 去掉了函數(shù)體定義時的“=”的函數(shù)一般稱之為“過程”,過程函數(shù)的結(jié)果類型一定是Unit。因此,有時定義函數(shù)時忘記加等號,結(jié)果常常是出乎你的意料的。 沒有返回值的函數(shù)的默認返回值是Unit。函數(shù)調(diào)用:scala中,方法調(diào)用的空括號可以省略。慣例是如果方法帶有副作用就加上括號,如果沒有副作用就去掉括號。如果在函數(shù)定義時,省略了空括號,那么在調(diào)用時,就不能加空括號。另外,函數(shù)作為操作符使用時的調(diào)用形式參見相應(yīng)部分。函數(shù)參數(shù):一般情況下,scala編譯器是無法推斷函數(shù)的參數(shù)類型的,因此你需要在參數(shù)列表中聲明參數(shù)的類型。對于函數(shù)字面量來說,根據(jù)其使用環(huán)境的不同,scala有時可以推斷出其參數(shù)類型。 scala里函數(shù)參數(shù)的一個重要特征是它們都是val(這是無需聲明的,在參數(shù)列表里你不能顯式地聲明參數(shù)變量為val),不是var,所以你不能在函數(shù)里面給參數(shù)變量重新賦值,這將遭到編譯器的強烈反對。重復(fù)參數(shù): 在scala中,你可以指明函數(shù)的最后一個參數(shù)是重復(fù)的,從而允許客戶向函數(shù)傳入可變長度參數(shù)列表。要想標注一個重復(fù)參數(shù),可在參數(shù)的類型之后放一個星號“*”。例如: defecho(args:String*)=for(arg<-args)println(arg)這樣的話,echo就可以被零至多個String參數(shù)調(diào)用。在函數(shù)內(nèi)部,重復(fù)參數(shù)的類型是聲明參數(shù)類型的數(shù)組。因此,echo函數(shù)里被聲明為類型“String*”的args的類型實際上是Array[String]。然而,如果你有一個合適類型的數(shù)組,并嘗試把它當作重復(fù)參數(shù)傳入,會出現(xiàn)編譯錯誤。要實現(xiàn)這個做法,你需要在數(shù)組名后添加一個冒號和一個_*符號,以告訴編譯器把數(shù)組中的每個元素當作參數(shù),而不是將整個數(shù)組當作單一的參數(shù)傳遞給echo函數(shù),如下: echo(arr:_*)默認參數(shù)與命名參數(shù): 函數(shù)的默認參數(shù)與java以及c++中相似,都是從左向右結(jié)合。另外,你也可以在調(diào)用時指定參數(shù)名。示例如下: deffun(str:String,left:String=“[”,right:String=“]”)=left+str+right fun(“hello”) fun(“hello”,“<<<”) fun(“hello”,left=“<<<”)函數(shù)與操作符:從技術(shù)層面上來說,scala沒有操作符重載,因為它根本沒有傳統(tǒng)意義上的操作符。諸如“+”、“-”、“*”、“/”這樣的操作符,其實調(diào)用的是方法。方法被當作操作符使用時,根據(jù)使用方式的不同,可以分為:中綴標注(操作符)、前綴標注、后綴標注。中綴標注:中綴操作符左右分別有一個操作數(shù)。方法若只有一個參數(shù)(實際上是兩個參數(shù),因為有一個隱式的this),調(diào)用的時候就可以省略點及括號。實際上,如果方法有多個顯式參數(shù),也可以這樣做,只不過你需要把參數(shù)用小括號全部括起來。如果方法被當作中綴操作符來使用(也即省略了點及括號),那么左操作數(shù)是方法的調(diào)用者,除非方法名以冒號“:”結(jié)尾(此時,方法被右操作數(shù)調(diào)用)。另外,scala的中綴標注不僅可以在操作符中存在,也可以在模式匹配、類型聲明中存在,參見相應(yīng)部分。前綴標注:前綴操作符只有右邊一個操作數(shù)。但是對應(yīng)的方法名應(yīng)該在操作符字符上加上前綴“unary_”。標識符中能作為前綴操作符用的只有+、-、!和~。后綴標注:后綴操作符只有左邊一個操作數(shù)。任何不帶顯式參數(shù)的方法都可以作為后綴操作符。在scala中,函數(shù)的定義方式除了作為對象成員函數(shù)的方法之外,還有內(nèi)嵌在函數(shù)中的函數(shù),函數(shù)字面量和函數(shù)值。嵌套定義的函數(shù):嵌套定義的函數(shù)也叫本地函數(shù),本地函數(shù)僅在包含它的代碼塊中可見。函數(shù)字面量:在scala中,你不僅可以定義和調(diào)用函數(shù),還可以把它們寫成匿名的字面量,也即函數(shù)字面量,并把它們作為值傳遞。函數(shù)字面量被編譯進類,并在運行期間實例化為函數(shù)值(任何函數(shù)值都是某個擴展了scala包的若干FunctionN特質(zhì)之一的類的實例,如Function0是沒有參數(shù)的函數(shù),F(xiàn)unction1是有一個參數(shù)的函數(shù)等等。每一個FunctionN特質(zhì)有一個apply方法用來調(diào)用函數(shù))。因此函數(shù)字面量和值的區(qū)別在于函數(shù)字面量存在于源代碼中,而函數(shù)值作為對象存在于運行期。這個區(qū)別很像類(源代碼)和對象(運行期)之間的關(guān)系。以下是對給定數(shù)執(zhí)行加一操作的函數(shù)字面量: (x:Int)=>x+1 其中,=>指出這個函數(shù)把左邊的東西轉(zhuǎn)變?yōu)橛疫叺臇|西。在=>右邊,你也可以使用{}來包含代碼塊。 函數(shù)值是對象,因此你可以將其存入變量中,這些變量也是函數(shù),你可以使用通常的括號函數(shù)調(diào)用寫法調(diào)用它們。如: valfun=(x:Int)=>x+1 vala=fun(5) 有時,scala編譯器可以推斷出函數(shù)字面量的參數(shù)類型,因此你可以省略參數(shù)類型,然后你也可以省略參數(shù)外邊的括號。如: (x)=>x+1 x=>x+1 如果想讓函數(shù)字面量更簡潔,可以把通配符“_”當作單個參數(shù)的占位符。如果遇見編譯器無法識別參數(shù)類型時,在“_”之后加上參數(shù)類型聲明即可。如: List(1,2,3,4,5).filter(_>3) valfun=(_:Int)+(_:Int)部分應(yīng)用函數(shù): 你還可以使用單個“_”替換整個參數(shù)列表。例如可以寫成:List(1,2,3,4,5).foreach(println(_))或者更好的方法是你還可以寫成:List(1,2,3,4,5).foreach(println_) 以這種方式使用下劃線時,你就正在寫一個部分應(yīng)用函數(shù)。部分應(yīng)用函數(shù)是一種表達式,你不需要提供函數(shù)需要的所有參數(shù),代之以僅提供部分,或不提供所需參數(shù)。如下先定義一個函數(shù),然后創(chuàng)建一個部分應(yīng)用函數(shù),并保存于變量,然后該變量就可以作為函數(shù)使用: defsum(a:Int,b:Int,c:Int)=a+b+c vala=sum_ println(a(1,2,3)) 實際發(fā)生的事情是這樣的:名為a的變量指向一個函數(shù)值對象,這個函數(shù)值是由scala編譯器依照部分應(yīng)用函數(shù)表達式sum_,自動產(chǎn)生的類的一個實例。編譯器產(chǎn)生的類有一個apply方法帶有3個參數(shù)(之所以帶3個參數(shù)是因為sum_表達式缺少的參數(shù)數(shù)量為3),然后scala編譯器把表達式a(1,2,3)翻譯成對函數(shù)值的apply方法的調(diào)用。你可以使用這種方式把成員函數(shù)和本地函數(shù)轉(zhuǎn)換為函數(shù)值,進而在函數(shù)中使用它們。不過,你還可以通過提供某些但不是全部需要的參數(shù)表達一個部分應(yīng)用函數(shù)。如下,此變量在使用的時候,可以僅提供一個參數(shù): valb=sum(1,_:Int,3) 如果你正在寫一個省略所有參數(shù)的部分應(yīng)用函數(shù)表達式,如println_或sum_,而且在代碼的那個地方正需要一個函數(shù),你就可以省略掉下劃線(不是需要函數(shù)的地方,你這樣寫,編譯器可能會把它當作一個函數(shù)調(diào)用,因為在scala中,調(diào)用無副作用的函數(shù)時,默認不加括號)。如下代碼就是: List(1,2,3,4,5).foreach(println)閉包:閉包是可以包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)。比如說,在函數(shù)字面量中使用定義在其外的局部變量,這就形成了一個閉包。如下代碼foreach中就創(chuàng)建了一個閉包:varsum=0 List(1,2,3,4,5).foreach(x=>sum+=x) 在scala中,閉包捕獲了變量本身,而不是變量的值。變量的變化在閉包中是可見的,反過來,若閉包改變對應(yīng)變量的值,在外部也是可見的。尾遞歸: 遞歸調(diào)用這個動作在最后的遞歸函數(shù)叫做尾遞歸。scala編譯器可以對尾遞歸做出重要優(yōu)化,當其檢測到尾遞歸就用新值更新函數(shù)參數(shù),然后把它替換成一個回到函數(shù)開頭的跳轉(zhuǎn)。 你可以使用開關(guān)“-g:notailcalls”關(guān)掉編譯器的尾遞歸優(yōu)化。 別高興太早,scala里尾遞歸優(yōu)化的局限性很大,因為jvm指令集使實現(xiàn)更加先進的尾遞歸形式變得困難。尾遞歸優(yōu)化限定了函數(shù)必須在最后一個操作調(diào)用本身,而不是轉(zhuǎn)到某個“函數(shù)值”或什么其他的中間函數(shù)的情況。 在scala中,你不要刻意回避使用遞歸,相反,你應(yīng)該盡量避免使用while和var配合實現(xiàn)的循環(huán)。高階函數(shù): 帶有其他函數(shù)作為參數(shù)的函數(shù)稱為高階函數(shù)??吕锘?柯里化(Currying)是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。如下就是一個柯里化之后的函數(shù): defcurriedSum(x:Int)(y:Int)=x+y這里發(fā)生的事情是當你調(diào)用curriedSum時,實際上接連調(diào)用了兩個傳統(tǒng)函數(shù)。第一個調(diào)用的函數(shù)帶單個名為x的參數(shù),并返回第二個函數(shù)的函數(shù)值;這個被返回的函數(shù)帶一個參數(shù)y,并返回最終計算結(jié)果。你可以使用部分應(yīng)用函數(shù)表達式方式,來獲取第一個調(diào)用返回的函數(shù),也即第二個函數(shù),如下: valonePlus=curriedSum(3)_高階函數(shù)和柯里化配合使用可以提供靈活的抽象控制,更進一步,當函數(shù)只有一個參數(shù)時,在調(diào)用時,你可以使用花括號代替小括號,scala支持這種機制,其目的是讓客戶程序員寫出包圍在花括號內(nèi)的函數(shù)字面量,從而讓函數(shù)調(diào)用感覺更像抽象控制,不過需要注意的是:花括號也就是塊表達式,因此你可以在其中填寫多個表達式,但是最后一個表達式的值作為該塊表達式的值并最終成為了函數(shù)參數(shù)。如果函數(shù)有兩個以上的參數(shù),那么你可以使用柯里化的方式來實現(xiàn)函數(shù)。傳名參數(shù):對于如下代碼,myAssert帶有一個函數(shù)參數(shù),該參數(shù)變量的類型為不帶函數(shù)參數(shù)的函數(shù)類型: myAssert(predicate:()=>Boolean)={ if(!predicate()) thrownewAssertionError}在使用時,我們需要使用如下的語法: myAssert(()=>5>3)這樣很麻煩,我們可以使用如下稱之為“傳名參數(shù)”的語法簡化之: myAssert(predicate:=>Boolean)={ if(!predicate) thrownewAssertionError}以上代碼在定義參數(shù)類型時是以“=>”開頭而不是“()=>”,并在調(diào)用函數(shù)(通過函數(shù)類型的變量)時,不帶“()”?,F(xiàn)在你就可以這樣使用了: myAssert(5>3)其中,“predicate:=>Boolean”說明predicate是函數(shù)類型,在使用時傳入的是函數(shù)字面量。注意與“predicate:Boolean”的不同,后者predicate是Boolean類型的(表達式)。偏函數(shù): 偏函數(shù)和部分應(yīng)用函數(shù)是無關(guān)的。偏函數(shù)是只對函數(shù)定義域的一個子集進行定義的函數(shù)。scala中用scala.PartialFunction[-T,+S]來表示。偏函數(shù)主要用于這樣一種場景:對某些值現(xiàn)在還無法給出具體的操作(即需求還不明朗),也有可能存在幾種處理方式(視乎具體的需求),我們可以先對需求明確的部分進行定義,以后可以再對定義域進行修改。PartialFunction中可以使用的方法如下:isDefinedAt:判斷定義域是否包含指定的輸入。orElse:補充對其他域的定義。compose:組合其他函數(shù)形成一個新的函數(shù),假設(shè)有兩個函數(shù)f和g,那么表達式f_composeg_則會形成一個f(g(x))形式的新函數(shù)。你可以使用該方法對定義域進行一定的偏移。andThen:將兩個相關(guān)的偏函數(shù)串接起來,調(diào)用順序是先調(diào)用第一個函數(shù),然后調(diào)用第二個,假設(shè)有兩個函數(shù)f和g,那么表達式f_andTheng_則會形成一個g(f(x))形式的新函數(shù),剛好與compose相反。===類(class)和對象(object)類(class)和構(gòu)造器: 類的定義形式如下:classMyClass(a:Int,b:Int){ println(a.toString)}在scala中,類也可以帶有類參數(shù),類參數(shù)可以直接在類的主體中使用,沒必要定義字段然后把構(gòu)造器的參數(shù)賦值到字段里,但需要注意的是:類參數(shù)僅僅是個參數(shù)而已,不是字段,如果你需要在別的地方使用,就必須定義字段。不過還有一種稱為參數(shù)化字段的定義形式,可以簡化字段的定義,如下: classMyClass(vala:Int,valb:Int){ println(a.toString)}以上代碼中多了val聲明,作用是在定義類參數(shù)的同時定義類字段,不過它們使用相同的名字罷了。類參數(shù)同樣可以使用var作前綴,還可以使用private、protected、override修飾等等。scala編譯器會收集類參數(shù)并創(chuàng)造出帶同樣的參數(shù)的類的主構(gòu)造器,并將類內(nèi)部任何既不是字段也不是方法定義的代碼編譯至主構(gòu)造器中。除了主構(gòu)造器,scala也可以有輔助構(gòu)造器,輔助構(gòu)造器的定義形式為defthis(…)。每個輔助構(gòu)造器都以“this(…)”的形式開頭以調(diào)用本類中的其他構(gòu)造器,被調(diào)用的構(gòu)造器可以是主構(gòu)造器,也可以是源文件中早于調(diào)用構(gòu)造器定義的其他輔助構(gòu)造器。其結(jié)果是對scala構(gòu)造器的調(diào)用終將導(dǎo)致對主構(gòu)造器的調(diào)用,因此主構(gòu)造器是類的唯一入口點。在scala中,只有主構(gòu)造器可以調(diào)用超類的構(gòu)造器。 你可以在類參數(shù)列表之前加上private關(guān)鍵字,使類的主構(gòu)造器私有,私有的主構(gòu)造器只能被類本身以及伴生對象訪問??梢允褂胷equire方法來為構(gòu)造器的參數(shù)加上先決條件,如果不滿足要求的話,require會拋出異常,阻止對象的創(chuàng)建。如果類的主體為空,那么可以省略花括號。訪問級別控制:公有是scala的默認訪問級別,因此如果你想使成員公有,就不要指定任何訪問修飾符。公有的成員可以在任何地方被訪問。私有類似于java,即在之前加上private。不同的是,在scala中外部類不可以訪問內(nèi)部類的私有成員。保護類似于java,即在之前加上protected。不同的是,在scala中同一個包中的其他類不能訪問被保護的成員。scala里的訪問修飾符可以通過使用限定詞強調(diào)。格式為private[X]或protected[X]的修飾符表示“直到X”的私有或保護,這里X指代某個所屬的包、類或單例對象。 scala還有一種比private更嚴格的訪問修飾符,即private[this]。被private[this]標記的定義僅能在包含了定義的同一個對象中被訪問,這種限制被稱為對象私有。這可以保證成員不被同一個類中的其他對象訪問。 對于私有或者保護訪問來說,scala的訪問規(guī)則給予了伴生對象和類一些特權(quán),伴生對象可以訪問所有它的伴生類的私有成員、保護成員,反過來也成立。成員(類型、字段和方法): scala中也可以定義類型成員,類型成員以關(guān)鍵字type聲明。通過使用類型成員,你可以為類型定義別名。 scala里字段和方法屬于相同的命名空間,scala禁止在同一個類里用同樣的名稱定義字段和方法,盡管java允許這樣做。getter和setter: 在scala中,類的每個非私有的var成員變量都隱含定義了getter和setter方法,但是它們的命名并沒有沿襲java的約定,var變量x的getter方法命名為“x”,它的setter方法命名為“x_=”。你也可以在需要的時候,自行定義相應(yīng)的getter和setter方法,此時你還可以不定義關(guān)聯(lián)的字段,自行定義setter的好處之一就是你可以進行賦值的合法性檢查。 如果你將scala字段標注為@BeanProperty時,scala編譯器會自動額外添加符合JavaBeans規(guī)范的形如getXxx/setXxx的getter和setter方法。這樣的話,就方便了java與scala的互操作。樣本類: 帶有case修飾符的類稱為樣本類(caseclass),這種修飾符可以讓scala編譯器自動為你的類添加一些句法上的便捷設(shè)定,以便用于模式匹配,scala編譯器自動添加的句法如下:幫你實現(xiàn)一個該類的伴生對象,并在伴生對象中提供apply方法,讓你不用new關(guān)鍵字就能構(gòu)造出相應(yīng)的對象;在伴生對象中提供unapply方法讓模式匹配可以工作;樣本類參數(shù)列表中的所有參數(shù)隱式地獲得了val前綴,因此它們被當作字段維護;添加toString、hashCode、equals、copy的“自然”實現(xiàn)。封閉類: 帶有sealed修飾符的類稱為封閉類(sealedclass),封閉類除了類定義所在的文件之外不能再添加任何新的子類。這對于模式匹配來說是非常有用的,因為這意味著你僅需要關(guān)心你已經(jīng)知道的子類即可。這還意味你可以獲得更好的編譯器幫助。單例對象(singletonobject):scala沒有靜態(tài)方法,不過它有類似的特性,叫做單例對象,以object關(guān)鍵字定義(注:main函數(shù)也應(yīng)該在object中定義,任何擁有合適簽名的main方法的單例對象都可以用來作為程序的入口點)。定義單例對象并不代表定義了類,因此你不可以使用它來new對象。當單例對象與某個類共享同一個名稱時,它就被稱為這個類的伴生對象(companionobject)。類和它的伴生對象必須定義在同一個源文件里。類被稱為這個單例對象的伴生類。類和它的伴生對象可以互相訪問其私有成員。不與伴生類共享名稱的單例對象被稱為獨立對象(standaloneobject)。apply與update:在scala中,通常使用類似函數(shù)調(diào)用的語法。當使用小括號傳遞變量給對象時,scala都將其轉(zhuǎn)換為apply方法的調(diào)用,當然前提是這個類型實際定義過apply方法。比如s是一個字符串,那么s(i)就相當于c++中的s[i]以及java中的s.charAt(i),實際上s(i)是s.apply(i)的簡寫形式。類似地,BigInt(“123”)就是BigInt.apply(“123”)的簡寫形式,這個語句使用伴生對象BigInt的apply方法產(chǎn)生一個新的BigInt對象,不需要使用new。與此相似的是,當對帶有括號并包含一到若干參數(shù)的變量賦值時,編譯器將使用對象的update方法對括號里的參數(shù)(索引值)和等號右邊的對象執(zhí)行調(diào)用,如arr(0)=“hello”將轉(zhuǎn)換為arr.update(0,“hello”)。類和單例對象之間的差別是,單例對象不帶參數(shù),而類可以。因為單例對象不是用new關(guān)鍵字實例化的,所以沒機會傳遞給它實例化參數(shù)。單例對象在第一次被訪問的時候才會被初始化。當你實例化一個對象時,如果使用了new則是用類實例化對象,無new則是用伴生對象生成新對象。同時要注意的是:我們可以在類或(單例)對象中嵌套定義其他的類和(單例)對象。對象相等性:與java不同的是,在scala中,“==”和“!=”可以直接用來比較對象的相等性,“==”和“!=”方法會去調(diào)用equals方法,因此一般情況下你需要覆蓋equals方法。如果要判斷引用是否相等,可以使用eq和ne。在使用具有哈希結(jié)構(gòu)的容器類庫時,我們需要同時覆蓋hashCode和equals方法,但是實現(xiàn)一個正確的hashCode和equals方法是比較困難的一件事情,你需要考慮的問題和細節(jié)很多,可以參見java總結(jié)中的相應(yīng)部分。另外,正如樣本類部分所講的那樣,一旦一個類被聲明為樣本類,那么scala編譯器就會自動添加正確的符合要求的hashCode和equals方法。===抽象類和抽象成員 與java相似,scala中abstract聲明的類是抽象類,抽象類不可以被實例化。 在scala中,抽象類和特質(zhì)中的方法、字段和類型都可以是抽象的。示例如下: traitMyAbstract{ typeT //抽象類型 deftransform(x:T):T //抽象方法 valinitial:T //抽象val varcurrent:T //抽象var } 抽象方法:抽象方法不需要(也不允許)有abstract修飾符,一個方法只要是沒有實現(xiàn)(沒有等號或方法體),它就是抽象的。 抽象類型:scala中的類型成員也可以是抽象的。抽象類型并不是說某個類或特質(zhì)是抽象的(特質(zhì)本身就是抽象的),抽象類型永遠都是某個類或特質(zhì)的成員。 抽象字段:沒有初始化的val或var成員是抽象的,此時你需要指定其類型。抽象字段有時會扮演類似于超類的參數(shù)這樣的角色,這對于特質(zhì)來說尤其重要,因為特質(zhì)缺少能夠用來傳遞參數(shù)的構(gòu)造器。因此參數(shù)化特質(zhì)的方式就是通過在子類中實現(xiàn)抽象字段完成。如對于以下特質(zhì): traitMyAbstract{ valtest:Int println(test) defshow(){ println(test) }}你可以使用如下匿名類語法創(chuàng)建繼承自該特質(zhì)的匿名類的實例,如下:newMyAbstract{ valtest=1 }.show()你可以通過以上方式參數(shù)化特質(zhì),但是你會發(fā)現(xiàn)這和“new類名(參數(shù)列表)”參數(shù)化一個類實例還是有區(qū)別的,因為你看到了對于test變量的兩次println(第一次在特質(zhì)主體中,第二次是由于調(diào)用了方法show),輸出了兩個不同的值(第一次是0,第二次是1)。這主要是由于超類會在子類之前進行初始化,而超類抽象成員在子類中的具體實現(xiàn)的初始化是在子類中進行的。為了解決這個問題,你可以使用預(yù)初始化字段和懶值。預(yù)初始化字段: 預(yù)初始化字段,可以讓你在初始化超類之前初始化子類的字段。預(yù)初始化字段用于對象或有名稱的子類時,形式如下: classBextends{ vala=1 }withA預(yù)初始化字段用于匿名類時,形式如下: new{ vala=1 }withA 需要注意的是:由于預(yù)初始化的字段在超類構(gòu)造器調(diào)用之前被初始化,因此它們的初始化器不能引用正在被構(gòu)造的對象。懶值: 加上lazy修飾符的val變量稱為懶值,懶值右側(cè)的表達式將直到該懶值第一次被使用的時候才計算。如果懶值的初始化不會產(chǎn)生副作用,那么懶值定義的順序就不用多加考慮,因為初始化是按需的。===繼承與覆蓋(override)繼承: 繼承時,如果父類主構(gòu)造器帶有參數(shù),子類需要把要傳遞的參數(shù)放在父類名之后的括號里即可,如下: classSecond(a:Int,b:Int)extendsFirst(a){…}scala繼承層級: 如上圖所示:Any是所有其他類的超類。Null是所有引用類(繼承自AnyRef的類)的子類,Null類型的值為null。Nothing是所有其他類(包括Null)的子類,Nothing類型沒有任何值,它的一個用處是它標明了不正常的終止(例如拋出異常,啥也不返回)。AnyVal是scala中內(nèi)建值類(共9個)的父類。AnyRef是scala中所有引用類的父類,在java平臺上AnyRef實際就是java.lang.Object的別名,因此java里寫的類和scala里寫的類都繼承自AnyRef,你可以認為java.lang.Object是scala在java平臺上實現(xiàn)AnyRef的方式。scala類與java類的不同之處在于,scala類還繼承了一個名為ScalaObject的特別記號特質(zhì),目的是想讓scala程序執(zhí)行得更高效。覆蓋: 由于scala里字段和方法屬于相同的命名空間,這讓字段可以覆蓋無參數(shù)方法或空括號方法,但反過來好像不可以啊。另外,你也可以用空括號方法覆蓋無參數(shù)方法,反之亦可。在scala中,若子類覆蓋了父類的具體成員則必須帶override修飾符;若是實現(xiàn)了同名的抽象成員時則override是可選的;若并未覆蓋或?qū)崿F(xiàn)基類中的成員則禁用override修飾符。===特質(zhì)(trait) 特質(zhì)相當于接口,不能被實例化。特質(zhì)定義使用trait關(guān)鍵字,與類相似,你同樣可以在其中定義而不僅是聲明字段和方法等。你可以使用extends或with將多個特質(zhì)“混入”類中。注意當在定義特質(zhì)時,使用extends指定了特質(zhì)的超類,那么該特質(zhì)就只能混入擴展了指定的超類的類中。 特質(zhì)與類的區(qū)別在于:①特質(zhì)不能帶有“類參數(shù)”,也即傳遞給主構(gòu)造器的參數(shù);②不論在類的哪個地方,super調(diào)用都是靜態(tài)綁定的,但在特質(zhì)中,它們是動態(tài)綁定的,因為在特質(zhì)定義時,尚且不知道它的超類是誰,因為它還沒有“混入”,由于在特質(zhì)中使用super調(diào)用超類方法是動態(tài)綁定的,因此你需要對特質(zhì)中相應(yīng)的方法加上abstract聲明(雖然加上了abstract聲明,但方法仍可以被具體定義,這種用法只有在特質(zhì)中有效),以告訴編譯器特質(zhì)中的該方法只有在特質(zhì)被混入某個具有期待方法的具體定義的類中才有效。你需要非常注意特質(zhì)被混入的次序:特質(zhì)在構(gòu)造時順序是從左到右,構(gòu)造器的順序是類的線性化(線性化是描述某個類型的所有超類型的一種技術(shù)規(guī)格)的反向。由于多態(tài)性,子類的方法最先起作用,因此越靠近右側(cè)的特質(zhì)越先起作用,如果最右側(cè)特質(zhì)調(diào)用了super,它調(diào)用左側(cè)的特質(zhì)的方法,依此類推。Ordered特質(zhì): Ordered特質(zhì)擴展自java的Comparable接口。Ordered特質(zhì)用于排序,為了使用它,你需要做的是:首先將其混入類中,然后實現(xiàn)一個compare方法。需要注意的是:Ordered并沒有為你定義equals方法,因為通過compare實現(xiàn)equals需要檢查傳入對象的類型,但是因為類型擦除,導(dǎo)致它無法做到。因此,即使繼承了Ordered,也還是需要自己定義equals。Ordering特質(zhì): Ordering特質(zhì)擴展自java的Comparator接口。Ordering特質(zhì)也用于排序,為了使用它,你需要做的是:定義一個該特質(zhì)的子類的單獨的實例,需要實現(xiàn)其中的compare方法,并將其作為參數(shù)傳遞給排序函數(shù)。此乃策略模式也。Application特質(zhì):特質(zhì)Application聲明了帶有合適簽名的main方法。但是它存在一些問題,所以只有當程序相對簡單并且是單線程的情況下才可以繼承Application特質(zhì)。Application特質(zhì)相對于APP特質(zhì)來說,有些陳舊,你應(yīng)該使用更新的APP特質(zhì)。APP特質(zhì): APP特質(zhì)同Application特質(zhì)一樣,都提供了帶有合適簽名的main方法,在使用時只需將它混入你的類中,然后就可以在類的主構(gòu)造器中寫代碼了,無需再定義main方法。如果你需要命令行參數(shù),可以通過args屬性得到。===顯式類型轉(zhuǎn)換 正如之前所述的,scala中類型轉(zhuǎn)換使用方法實現(xiàn),以下是顯式類型測試和顯式類型轉(zhuǎn)換的示例:a.isInstanceOf[String] //顯式類型測試a.asInstanceOf[String] //顯式類型轉(zhuǎn)換===隱式轉(zhuǎn)換、隱式參數(shù)隱式轉(zhuǎn)換: 隱式轉(zhuǎn)換只是普通的方法,唯一特殊的地方是它以修飾符implicit開始,implicit告訴scala編譯器可以在一些情況下自動調(diào)用(比如說如果當前類型對象不支持當前操作,那么scala編譯器就會自動添加調(diào)用相應(yīng)隱式轉(zhuǎn)換函數(shù)的代碼,將其轉(zhuǎn)換為支持當前操作的類型的對象,前提是已經(jīng)存在相應(yīng)的隱式轉(zhuǎn)換函數(shù)且滿足作用域規(guī)則),而無需你去調(diào)用(當然如果你愿意,你也可以自行調(diào)用)。隱式轉(zhuǎn)換函數(shù)定義如下:implicitdeffunctionName(…)={…} 隱式轉(zhuǎn)換滿足以下規(guī)則:作用域規(guī)則:scala編譯器僅會考慮處于作用域之內(nèi)的隱式轉(zhuǎn)換。隱式轉(zhuǎn)換要么是以單一標識符的形式(即不能是aaa.bbb的形式,應(yīng)該是bbb的形式)出現(xiàn)在作用域中,要么是存在于源類型或者目標類型的伴生對象中。單一調(diào)用規(guī)則:編譯器在同一個地方只會添加一次隱式操作,不會在添加了一個隱式操作之后再在其基礎(chǔ)上添加第二個隱式操作。顯式操作先行規(guī)則:若編寫的代碼類型檢查無誤,則不會嘗試任何隱式操作。隱式參數(shù): 柯里化函數(shù)的完整的最后一節(jié)參數(shù)可以被隱式提供,即隱式參數(shù)。此時最后一節(jié)參數(shù)必須被標記為implicit(整節(jié)參數(shù)只需一個implicit,并不是每個參數(shù)都需要),同時用來提供隱式參數(shù)的相應(yīng)實際變量也應(yīng)該標記為implicit的。對于隱式參數(shù),我們需要注意的是:隱式參數(shù)也可以被顯式提供;提供隱式參數(shù)的實際變量必須以單一標識符的形式出現(xiàn)在作用域中;編譯器選擇隱式參數(shù)的方式是通過匹配參數(shù)類型與作用域內(nèi)的值類型,因此隱式參數(shù)應(yīng)該是很稀少或者很特殊的類型(最好是使用自定義的角色確定的名稱來命名隱式參數(shù)類型),以便不會被碰巧匹配;如果隱式參數(shù)是函數(shù),編譯器不僅會嘗試用隱式值補足這個參數(shù),還會把這個參數(shù)當作可用的隱式操作而使用于方法體中。視界: 視界使用“<%”符號,可以用來縮短帶有隱式參數(shù)的函數(shù)簽名。比如,“T<%Ordered[T]”是在說“任何的T都好,只要T能被當作Ordered[T]即可”,因此只要存在從T到Ordered[T]的隱式轉(zhuǎn)換即可。 注意視界與上界的不同:上界“T<:Ordered[T”是說T是Ordered[T]類型的。隱式操作調(diào)試:隱式操作是scala的非常強大的特性,但有時很難用對也很難調(diào)試。有時如果編譯器不能發(fā)現(xiàn)你認為應(yīng)該可以用的隱式轉(zhuǎn)換,你可以把該轉(zhuǎn)換顯式地寫出來,這有助于發(fā)現(xiàn)問題。另外,你可以在編譯scala程序時,使用“-Xprint:typer”選項來讓編譯器把添加了所有的隱式轉(zhuǎn)換之后的代碼展示出來。===類型參數(shù)化在scala中,類型參數(shù)化(類似于泛型)使用方括號實現(xiàn),如:Foo[A],同時,我們稱Foo為高階類型。如果一個高階類型有2個類型參數(shù),則在聲明變量類型時可以使用中綴形式來表達,此時也稱該高階類型為中綴類型,示例如下: classFoo[A,B] valx:IntFooString=null //IntFooString等同于Foo[Int,String]與java相似,scala的類型參數(shù)化也使用類型擦除實現(xiàn)(類型擦除是很差勁的泛型機制,不過可能是由于java的原因,scala也這樣做了),類型擦除的唯一例外就是數(shù)組,因為在scala中和java中,它們都被特殊處理,數(shù)組的元素類型與數(shù)組值保存在一起。在scala中,數(shù)組是“不變”的(這點與java不同),泛型默認是“不變”的。協(xié)變、逆變與不變: 拿Queue為例,如果S是T的子類型,那么Queue[S]是Queue[T]的子類型,就稱Queue是協(xié)變的;相反,如果Queue[T]是Queue[S]的子類型,那么Queue是逆變的;既不是協(xié)變又不是逆變的是不變的,不變的又叫嚴謹?shù)?。在scala中,泛型默認是不變的。當定義類型時,你可以在類型參數(shù)前加上“+”使類型協(xié)變,如Queue[+A]。類似地,你可以在類型參數(shù)前加上“-”使類型逆變。在java中使用類型時可以通過使用extends和super來達到協(xié)變逆變的目的,它們都是“使用點變型”,java不支持“聲明點變型”。而scala中同時提供了聲明點變型(“+”和“-”,它們只能在類型定義時使用)和使用點變型(“<:”和“>:”,類似于java中的extends和super,在使用類型時聲明)。不管是“聲明點變型”還是“使用點變型”,都遵循PECS法則,詳見java泛型。需要注意的是:變型并不會被繼承,父類被聲明為變型,子類若想保持仍需要再次聲明。繼承中的協(xié)變逆變: c++、java、scala都支持返回值協(xié)變,也就是說在繼承層次中子類覆蓋超類的方法時,可以指定返回值為更具體的類型。c#不支持返回值協(xié)變。允許參數(shù)逆變的面向?qū)ο笳Z言并不多——c++、java、scala和c#都會把它當成一個函數(shù)重載。 更多信息參見java泛型。===集合scala的集合(collection)庫分為可變(mutable)類型與不可變(immutable)類型。以Set為例,特質(zhì)scala.collection.immutable.Set和scala.collection.mutable.Set都擴展自scala.collection.Set。scala集合的頂層抽象類和特質(zhì):scala.collection.immutable:scala.collection.mutable:不可變集合與可變集合之間的對應(yīng)關(guān)系:不可變(collection.immutable._)可變(collection.mutable._)ArrayArrayBufferListListBufferStringStringBuilder-LinkedList,DoubleLinkedListListMutableListQueueQueueArrayArraySeqStackStackHashMapHashSetHashMapHashSet-ArrayStackIterable與Iterator: Iterable是可變和不可變序列、集、映射的超特質(zhì)。集合對象可以通過調(diào)用iterator方法來產(chǎn)生迭代器Iterator。Iterable與Iterator之間的差異在于:前者指代的是可以被枚舉的類型,而后者是用來執(zhí)行枚舉操作的機制。盡管Iterable可以被枚舉若干次,但Iterator僅能使用一次。數(shù)組: 在scala中,數(shù)組保存相同類型的元素,其中包含的元素值是可變的。數(shù)組也是對象,訪問數(shù)組使用小括號。在JVM中,scala的數(shù)組以java數(shù)組方式實現(xiàn)。scala中數(shù)組是非協(xié)變的。定長數(shù)組使用Array,創(chuàng)建之后長度不可改變。變長數(shù)組使用ArrayBuffer。與java一樣,scala中多維數(shù)組也是通過數(shù)組的數(shù)組來實現(xiàn)的。構(gòu)造多維數(shù)組可以使用ofDim方法或者直接使用for循環(huán)來new。示例如下: valmatrix=Array.ofDim[Double](3,4) //ofDim方法創(chuàng)建多維數(shù)組 matrix(1)(2)=12.36 valmutliarr=newArray[Array[Int]](10) //for循環(huán)方式創(chuàng)建多維數(shù)組 for(i<-0untilmutliarr.length) mutliarr(i)=newArray[Int](5)列表: 列表保存相同類型的元素。scala里的列表類型是協(xié)變的,這意味著如果S是T的子類,那么List[S]也是List[T]的子類。 不可變列表使用List,一旦創(chuàng)建之后就不可改變??勺兞斜硎褂肔istBuffer。 List是抽象類,它有兩個子類型:Nil和::。Nil是空列表對象,類型是List[Nothing]。::是樣本類,可以創(chuàng)建非空列表,::的伴生對象可以以中綴標注的形式用于模式匹配。所以在scala中存在兩個::,一個是樣本類,另一個是List的方法,因此在構(gòu)造一個列表時,我們就有了多種方法,如下: vallist1=List("A") //這里L(fēng)ist是伴生對象,相當于List.apply() vallist2=::("A",Nil) //這里::是伴生對象,相當于::.apply() vallist3="A"::Nil //這里::是方法,相當于Nil.::()List類沒有提供append操作(向列表尾部追加),因為隨著列表變長,效率將逐漸低下。List提供了“::”做前綴插入,因為這將消耗固定時間。如果你想通過添加元素來構(gòu)造列表,你的選擇是先把它們前綴插入,完成之后再調(diào)用reverse;或者使用ListBuffer,一種提供append操作的可變列表,完成之后調(diào)用toList。棧和隊列: scala集合庫提供了可變和不可變的棧類Stack,也提供了可變和不可變的隊列類Queue。元組與對偶:元組Tuple也是不可變的,但元組可以包含不同類型的元素,并且因此而不能繼承自Iterable。元組實例化之后,可以使用點號、下劃線和從1開始的索引訪問其中的元素。因為元組可以保存不同類型的元素,所以不能使用apply方法訪問其元素(apply返回同樣的類型)。元組的索引從1開始,是因為對于擁有靜態(tài)類型元組的其他語言,如Haskell和ML,從1開始是傳統(tǒng)的設(shè)定。scala的任何對象都可以調(diào)用“->”方法,并返回包含鍵值對的二元組(也叫對偶,是元組的最簡單形態(tài)),比如“hello”->100則創(chuàng)建出(“hello”,100)。 元組相應(yīng)操作示例如下: valt=(1400,“Jim”,“haha”,3.14) //定義一個元組 valsecond=t._2 //引用元組第二個組元 val(first,second,third,fourth)=t //分別獲取元組的第1、2、3、4個組元 val(first,secong,_)=t //只獲取前兩個組元集和映射: 集中保存著不重復(fù)的元素。映射可以把鍵和值關(guān)聯(lián)起來保存。拉鏈操作: valsymbols=Array(“<”,“-”,“>”) valcounts=Array(2,10,2) valpairs=symbols.zip(counts)以上代碼生成對偶類型的數(shù)組,如下: Array((<,2),(-,10),(>,2))可變集合vs不可變集合: 可變集合性能更好,不可變集合更易于理清頭緒。對于某些問題來說,可變集合能夠很好的處理;而另一些,不可變集合更為合適。如果在使用可變集合時,你發(fā)現(xiàn)需要擔憂何時復(fù)制可變集合的副本,或者思考很多關(guān)于誰“主宰”或“擁有”可變集合的時候,那么請考慮是否可用不可變集合代替。===異常 scala的異常工作機制與java的類似,但也有區(qū)別。區(qū)別如下:scala沒有“受檢”異?!悴恍枰暶骱瘮?shù)或方法可能會拋出某種異常。throw表達式是有值的,其值是Nothing類型。try-catch-finally表達式也是有值的,但是情況有些特殊。當沒有拋出異常時,try子句為表達式值;如果拋出異常并被捕獲,則對應(yīng)于相應(yīng)的catch子句;如果沒有被捕獲,表達式就沒有返回值。finally子句計算得到的值,總是被拋棄(除非使用return語句),所以你應(yīng)該在finally子句中干一些它應(yīng)該干的事,比如說:關(guān)閉文件、套接字、數(shù)據(jù)庫連接等,而最好別干什么其他事。===斷言、檢查 scala里,斷言使用assert函數(shù),檢查使用ensuring函數(shù),如果條件不成立,它們將會拋出AssertionError。它們都在Predef中定義。你可以使用JVM的-ea和-da命令行標志來開放和禁止斷言以及檢查。===包和引用打包: scala的代碼采用了java平臺完整的包機制。你可以使用兩種方式把代碼放進包里:使用放在文件頂部的package子句來把整個文件放入包中;使用package子句把要放入到包中的代碼用花括號括起來,這種方式像C#的命名空間。使用這種方式,你可以定義出嵌套的包,注意:scala的包可以嵌套,java則不可以。任何你自己寫的頂層包都被隱含地包含在_root_包中,因此你可以在多層嵌套的包代碼中通過_root_來訪問頂層包中的代碼。引用: 與java類似,scala使用import來引用,與java不同的是,scala的import子句:可以出現(xiàn)在任何地方,而不僅僅在文件開始處;可以引用對象和包;可以重命名或隱藏一些被引用的成員。這可以通過在被引用成員的對象之后加上括號里的引用選擇器子句來做到,示例如下(令p為包名):importp.{x} //從p中引入x,等價于importp.ximportp.{x=>y} //從p中引入x,并重命名為yimportp.{x=>_,_} //從p中引入除了x之外的所有東東。注意單獨的“_”稱作全包括,必須位于選擇器的最后。importp.{_}等價于importp._隱式引用: scala隱含地為每個源文件都加入如下引用: importjava.lang._ importscala._ importPredef._包scala中的Predef對象包含了許多有用的方法。例如:通常我們所使用的println、readLine、assert等。===scalaI/O 由于scala可以和java互操作,因此目前scala中的I/O類庫并不多,你可能需要使用java中的I/O類庫。下面介紹scala中有的東東: scala.Console對象可以用于終端輸入輸出,其中終端輸入函數(shù)有:readLine、readInt、readChar等等,終端輸出函數(shù)有:print、println、printf等等。其實,Predef對象中提供的預(yù)定義的readLine、println等等方法都是Console對象中對應(yīng)方法的別名。 scala.io.Source可以以文本的方式迭代地讀取源文件或者其他數(shù)據(jù)源。用完之后記得close啊。對象序列化: 為了讓對象可序列化,你可以這樣定義類: @SerialVersionUID(42L)classPersonextendsSerializable{…}其中,@SerialVersionUID注解指定序列化ID,如果你能接受缺省的ID,也可省去該注解;Serializable在scala包中,因此你無需引入。你可以像java中一樣對對象進行序列化。scala集合類都是可以序列化的,因此你可以把它們作為你的可序列化類的成員。===Actor和并發(fā) 與java的基于共享數(shù)據(jù)和鎖的線程模型不同,scala的actor包則提供了另外一種不共享任何數(shù)據(jù)、依賴消息傳遞的模型。設(shè)計并發(fā)軟件時,actor是首選的工具,因為它們能夠幫助你避開死鎖和爭用狀況,這兩種情形都是在使用共享和鎖模型時很容易遇到的。創(chuàng)建actor:actor是一個類似于線程的實體,它有一個用來接收消息的郵箱。實現(xiàn)actor的方法是繼承scala.actors.Actor特質(zhì)并完成其act方法。你可以通過actor的start方法來啟動它。actor在運行時都是相互獨立的。你也可以使用scala.actors.Actor對象的actor方法來創(chuàng)建actor,不過此時你就無需再調(diào)用start方法,因為它在創(chuàng)建之后馬上啟動。發(fā)送接收消息:Actor通過相互發(fā)送消息的方式進行通信,你可以使用“!”方法來發(fā)送消息,使用receive方法來接收消息,receive方法中包含消息處理的模式匹配(偏函數(shù))。發(fā)送消息并不會導(dǎo)致actor阻塞,發(fā)送的消息在接收actor的郵箱中等待處理,直到actor調(diào)用了receive方法,如果actor調(diào)用了receive但沒有模式匹配成功的消息,那么該actor將會阻塞,直到收到了匹配的消息。創(chuàng)建actor并發(fā)送接收消息的示例如下:objectScalaTestextendsActor{ defact(){ while(true){ receive{ casemsg=>println(msg) } } } defmain(args:Array[String]){ start() this!"hello." }}將原生線程當作actor:Actor子系統(tǒng)會管理一個或多個原生線程供自己使用。只要你用的是你顯式定義的actor,就不需要關(guān)心它們和線程的對應(yīng)關(guān)系是怎樣的。該子系統(tǒng)也支持反過來的情形:即每個原生線程也可以被當作actor來使用。此時,你應(yīng)該使用Actor.self方法來將當前線程作為actor來查看,也就是說可以這樣使用了:Actor.self!"message"。通過重用線程獲取更好的性能:Actor是構(gòu)建在普通java線程之上的,如果你想讓程序盡可能高效,那么慎用線程的創(chuàng)建和切換就很重要了。為幫助你節(jié)約線程,scala提供了react方法,和receive一樣,react帶有一個偏函數(shù),不同的是,react在找到并處理消息后并不返回(它的返回類型是Nothing),它在處理完消息之后就結(jié)束了。由于react不需要返回,故其不需要保留當前線程的調(diào)用棧。因此actor庫可以在下一個被喚醒的線程中重用當前的線程。極端情況下,如果程序中所有的actor都使用react,則它們可

溫馨提示

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

評論

0/150

提交評論