Scala總結

Scala總結java

===概述mysql

 

scala是一門以Java虛擬機(JVM)爲目標運行環境並將面向對象和函數式編程的最佳特性結合在一塊兒的靜態類型編程語言。react

scala是純粹的面向對象的語言。java雖然是面向對象的語言,可是它不是純粹的,由於java的基本數據類型不是類,而且在java中還有靜態成員變量和靜態方法。相反,scala是純粹面向對象的,每一個值都是對象,每一個操做都是方法調用。linux

scala也是一個成熟的函數式語言。函數式編程有兩個指導思想:①函數是頭等值,也就是說函數也是值,而且和其餘類型(如整數、字符串等)處於同一地位,函數能夠被看成參數傳遞,也能夠被看成返回值返回,還能夠在函數中定義函數等等;②程序的操做應該把輸入值映射爲輸出值而不是就地修改,也就是說函數調用不該產生反作用,雖然函數式編程語言鼓勵使用「無反作用」的方法,可是scala並不強制你必須這麼作。scala容許你使用指令式的編程風格,可是隨着你對scala的深刻了解,你可能會更傾向於一種更爲函數式的編程風格。向函數式編程轉變,你就應該儘可能去使用val、不可變對象、無反作用方法,而不是var、可變對象、有反作用方法。要明白的是,從指令式編程向函數式編程的轉變會很困難,所以你要作好充分的準備,並不斷的努力。c++

scala運行於JVM之上,而且它能夠訪問任何的java類庫而且與java框架進行互操做,scala也大量重用了java類型和類庫。程序員

 

第一個scala程序:sql

object ScalaTest {docker

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

println("hello scala.")數據庫

}

}

 

===scala解釋器

 

安裝好scala並配置好PATH環境變量以後,就能夠在終端中輸入「scala」命令打開scala解釋器。在其中,你能夠像使用shell同樣,使用TAB補全、Ctrl+r搜索、上下方向鍵切換歷史命令等等。退出scala解釋器,可使用命令:「:q」或者「:quit」。

因爲解釋器是輸入一句執行一句,所以也常稱爲REPL。REPL一次只能看到一行代碼,所以若是你要在其中粘貼代碼段的話,可能會出現問題,這時你可使用粘貼模式,鍵入以下語句:

:paste

而後把代碼粘貼進去,再按下Ctrl+d,這樣REPL就會把代碼段看成一個總體來分析。

 

===scala做爲腳本運行

 

scala代碼也能夠做爲腳本運行,只要你設置好代碼文件的shell前導詞(preamble),並將代碼文件設置爲可執行。以下:

#!/usr/bin/env scala

println("這是scala腳本")

設置代碼文件爲可執行,便可執行之啦。

scala腳本的命令行參數保存在名爲args的數組中,你可使用args獲取命令行輸入的程序參數。

 

===scala編譯運行

 

scala編譯器scalac會將scala代碼編譯爲jvm能夠運行的字節碼,而後就能夠在jvm上執行了。假設有一個Hello.scala 文件,咱們就可使用 scalac Hello.scala 編譯,而後使用 scala Hello 運行。固然也可使用java工具來運行,但須要在classpath裏指定scala-library.jar。對於classpath,在Unix家族的系統上,類路徑的各個項目由冒號「:」分隔,在MS Windows系統上,它們由分號「;」分隔。例如,在Linux上你能夠輸入這樣的命令來運行(注意classpath最後加一個「.」):

java -classpath /usr/local/scala-2.10.4/lib/scala-library.jar:. Hello

 

===scala IDE開發環境

 

你可使用 eclipse 或者 intellij idea 做爲scala的IDE開發環境,但都須要安裝scala插件才行。下面分別介紹這兩種方式:

 

eclipse開發環境配置:

 

scala ide for eclipse(下載地址:http://scala-ide.org)中集成了scala插件,你能夠直接使用它進行開發,不過它包含的可能不是咱們想要的scala版本,所以,仍是在該網站上下載對應的scala插件,插在eclipse上,這樣更好啊。

咱們先安裝eclipse juno,而後下載eclipse juno以及scala 2.10.4對應的scala sdk插件升級包:update-site.zip。將插件解壓縮,將 features 和 plugins 目錄下的全部東東都複製到eclipse中的對應目錄中,重啓eclipse便可。而後就能夠新建scala project了。

 

intellij idea開發環境配置:

 

 

咱們先安裝好intellij idea,而後安裝scala插件,自動安裝插件有時會很是慢,尤爲是在china。咱們仍是手動配置插件吧。請注意插件的版本,必須與當前idea版本兼容。手動配置插件方法以下:

(1) 進入 setting > plugins > browse repositorits 搜索你要下載的插件名稱,右側能夠找到下載地址。

(2) 解壓插件壓縮包,把插件的所有文件都複製到IntelliJ IDEA安裝程序的plugins文件夾中,注意插件最好以一個單獨的文件夾放在plugins目錄下。

(3) 通常重啓intellij idea就會自動加載插件,進入 setting > plugins 看看有木有。若是不自動加載的話,進入setting > plugins > install plugin from disk,找到剛纔複製的插件位置,再而後就行了。

接下來就能夠新建scala project,新建時我選擇的是「Scala」(不是sbt,由於我這選擇sbt以後,等半天sbt都不會配置好,鬱悶啊)。

 

相關姿式:

 

什麼是SBT? SBT = (not so) Simple Build Tool,是scala的構建工具,與java的maven地位相同。其設計宗旨是讓簡單的項目能夠簡單的配置,而複雜的項目能夠複雜的配置。

 

===scala特色

 

在scala中,語句以後的「;」是可選的,這根據你的喜愛。當有多個語句在同一行時,必須加上分號,但不建議把多個語句放在一行。

在scala中,建議使用2個空格做爲代碼縮進,不過我咋喜歡一個tab呢 ⊙﹏⊙!

在scala中,符號「_」至關於java中的通配符「*」。

scala相似於c++、java,索引也l是從0開始,但元組是個例外,它從1開始。

 

===數據類型

 

scala有7種數值類型:Byte、Char、Short、Int、Long、Float和Double,以及2種非數值類型:Boolean和Unit(只有一個值「()」,至關於java和c++中的void,即空值)。這些類型都是抽象的final類(不能使用new新建,也不能被繼承),在scala包中定義,是對java基本數據類型的包裝,所以與java基本數據類型有相同的長度。同時,scala還提供了RichInt、RichChar等等,它們分別提供Int、Char等所不具有的便捷方法。

另外,scala沿用了java.lang包中的String。在scala中,常量也稱做字面量,字符串字面量由雙引號包含的字符組成,同時scala提供了另外一種定義字符串常量的語法——原始字符串,它以三個雙引號做爲開始和結束,字符串內部能夠包含不管何種任意字符。

在scala中,咱們使用方法,而不是強制類型轉換,來作數值類型之間的轉換,如99.44.toInt、97.toChar。另外也能夠參見顯式類型轉換和隱式轉換。

 

===變量

 

scala有兩種變量:val和var。val如同java中的final變量,var如同java中的非final變量。因爲scala是徹底面向對象的,所以val和var只是聲明瞭對象的引用是不可變的仍是可變的,並不能說明引用指向的對象的可變性。聲明變量的同時須要初始化之,不然該變量就是抽象的。若是不指定變量的類型,編譯器會從初始化它的表達式中推斷出其類型。固然你也能夠在必要的時候指定其類型,但注意,在scala中變量或函數的類型老是寫在變量或函數的名稱的後邊。示例以下:

val answer = 「yes」

val answer, message: String = 「yes」

 

===標識符

 

scala標識符有四種形式:字母數字標識符、操做符標識符、混合標識符、字面量標識符。

 

字母數字標識符:跟其餘語言相似,由字母、數字和下劃線組成,但需注意「$」字符被保留做爲scala編譯器產生的標識符之用,你不要隨意使用它啊。

操做符標識符:由一個或多個操做符字符組成。scala編譯器將在內部「粉碎」操做符標識符以轉換成合法的內嵌「$」的java標識符。若你想從java代碼中訪問這個標識符,就應該使用這種內部表示方式。

混合標識符:由字母數字以及後面跟着的下劃線和一個操做符標識符組成。如unary_+定義了一個前綴操做符「+」。

字面量標識符:是用反引號`…`包含的任意字符串,scala將把被包含的字符串做爲標識符,即便被包含字符串是scala的關鍵字。例如:你可使用Thread.`yield`()來訪問java中的方法,即便yield是scala的關鍵字。

 

===操做符

 

scala的操做符和你在java和C++中的預期效果是同樣的,但注意scala並不提供++、--操做符。不過,scala中的操做符實際上都是方法,任何方法均可以看成操做符使用,如 a + b 至關於 a.+(b)。

須要注意的是:對於不可變對象(注:對象的不可變並非說它的引用變量是val的),並不真正支持相似於「+=」這樣以「=」結尾的操做符(即方法),不過scala仍是提供了一些語法糖,用以解釋以「=」結尾的操做符用於不可變對象的狀況。假設a是不可變對象的引用,那麼在scala中a += b將被解釋爲a = a + b,這時就至關於新建一個不可變對象從新賦值給引用a,前提是引用變量a要聲明爲var的,由於val變量定義以後是不可變的。

更多信息參見函數(方法)部分。

 

===塊表達式與賦值

 

在scala中,{}塊包含一系列表達式,其結果也是一個表達式,塊中最後一個表達式的值就是其值。

在scala中,賦值語句自己的值是Unit類型的。所以以下語句的值爲「()」:

{r = r * n; n -= 1}

正是因爲上述緣由,scala中不能多重賦值,而java和c++卻能夠多重賦值。所以,在scala中,以下語句中的x值爲「()」:

x = y = 1

 

===控制結構

 

scala和其餘編程語言有一個根本性差別:在scala中,幾乎全部構造出來的語法結構都有值。這個特性使得程序結構更加精簡。scala內建的控制結構不多,僅有if、while、for、try、match和函數調用等而已。如此之少的理由是,scala從語法層面上支持函數字面量。

 

if表達式:

scala的if/else語法結構與java等同樣,可是在scala中if/else表達式有值,這個值就是跟在if/esle後邊的表達式的值。以下:

val s = if(x > 0) 1 else -1

同時注意:scala的每一個表達式都有一個類型,好比上述if/esle表達式的類型是Int。若是是混合類型表達式,則表達式的類型是兩個分支類型的公共超類型。String和Int的超類型就是Any。若是一個if語句沒有else部分,則當if條件不知足時,表達式結果爲Unit。如:

if(x > 0) 1

就至關於:

if(x > 0) 1 else ()

 

while循環:

scala擁有與java和c++中同樣的while和do-while循環,while、do-while結果類型是Unit。

 

for表達式:

scala中沒有相似於for(; ; )的for循環,你可使用以下形式的for循環語句:

for(i <- 表達式)

該for表達式語法對於數組和全部集合類均有效。具體介紹以下:

枚舉:for(i <- 1 to 10),其中「i <- 表達式」語法稱之爲發生器,該語句是讓變量i(注意此處循環變量i是val的(但無需你指定),該變量的類型是集合的元素類型)遍歷表達式中的全部值。1 to 10產生的Range包含上邊界,若是不想包含上邊界,可使用until。

過濾:也叫守衛,在for表達式的發生器中使用過濾器能夠經過添加if子句實現,如:for(i <- 1 to 10 if i!=5),若是要添加多個過濾器,即多個if子句的話,要用分號隔開,如:for(i <- 1 to 10 if i!=5; if i!=6)。

嵌套枚舉:若是使用多個「<-」子句,你就獲得了嵌套的「循環」,如:for(i <- 1 to 5; j <- 1 to i)。

流間變量綁定:你能夠在for發生器以及過濾器等中使用變量保存計算結果,以便在循環體中使用,從而避免屢次計算以獲得該結果。流間變量綁定和普通變量定義類似,它被看成val,可是無需聲明val關鍵字。

製造新集合:for(…) yield 變量/循環體,最終將產生一個集合對象,集合對象的類型與它第一個發生器的類型是兼容的。

實際上:for表達式具備等價於組合應用map、flatMap、filter和foreach這幾種高階函數的表達能力。實際上,全部的可以yield(產生)結果的for表達式都會被編譯器轉譯爲高階方法map、flatMap及filter的組合調用;全部的不帶yield的for循環都會被轉譯爲僅對高階函數filter和foreach的調用。正是因爲這幾個高階函數支持了for表達式,因此若是一個數據類型要支持for表達式,它就要定義這幾個高階函數。有些時候,你可使用for表達式代替map、flatMap、filter和foreach的顯式組合應用,或許這樣會更清晰明瞭呢。

 

scala中沒有break和continue語句。若是須要相似的功能時,咱們能夠:

1) 使用Boolean類型的控制變量

2) 使用嵌套函數,你能夠從函數當中return

3) ...

 

match表達式與模式匹配:

scala中沒有switch,但有更強大的match。它們的主要區別在於:

① 任何類型的常量/變量,均可以做爲比較用的樣本;

② 在每一個case語句最後,不須要break,break是隱含的;

③ 更重要的是match表達式也有值;

④ 若是沒有匹配的模式,則MatchError異常會被拋出。

match表達式的形式爲:選擇器 match { 備選項 }。一個模式匹配包含了一系列備選項,每一個都開始於關鍵字case。每一個備選項都包含了一個模式以及一到多個表達式,它們將在模式匹配過程當中被計算。箭頭符號「=>」隔開了模式和表達式。按照代碼前後順序,一旦一個模式被匹配,則執行「=>」後邊的表達式((這些)表達式的值就做爲match表達式的值),後續case語句再也不執行。示例以下:

a match {

case 1 => "match 1"

case _ => "match _"

}

match模式的種類以下:

① 通配模式:能夠匹配任意對象,通常做爲默認狀況,放在備選項最後,如:

case _ => 

② 變量模式:相似於通配符,能夠匹配任意對象,不一樣的是匹配的對象會被綁定在變量上,以後就可使用這個變量操做對象。所謂變量就是在模式中臨時生成的變量,不是外部變量,外部變量在模式匹配時被看成常量使用,見常量模式。注意:同一個模式變量只能在模式中出現一次。

③ 常量模式:僅匹配自身,任何字面量均可以做爲常量,外部變量在模式匹配時也被看成常量使用,如:

case "false" => "false"

case true => "truth"

case Nil => "empty list"

對於一個符號名,是變量仍是常量呢?scala使用了一個簡單的文字規則對此加以區分:用小寫字母開始的簡單名被看成是模式變量,全部其餘的引用被認爲是常量。若是常量是小寫命名的外部變量,那麼它就得特殊處理一下了:若是它是對象的字段,則能夠加上「this.」或「obj.」前綴;或者更通用的是使用字面量標識符解決問題,也即用反引號「`」包圍之。

④ 抽取器模式:抽取器機制基於能夠從對象中抽取值的unapply或unapplySeq方法,其中,unapply用於抽取固定數量的東東,unapplySeq用於抽取可變數量的東東,它們都被稱爲抽取方法,抽取器正是經過隱式調用抽取方法抽取出對應東東的。抽取器中也能夠包含可選的apply方法,它也被稱做注入方法,注入方法使你的對象能夠看成構造器來用,而抽取方法使你的對象能夠看成模式來用,對象自己被稱做抽取器,與是否具備apply方法無關。樣本類會自動生成伴生對象並添加必定的句法以做爲抽取器,實際上,你也能夠本身定義一個任意其餘名字的單例對象做爲抽取器使用,以這樣的方式定義的抽取器對象與樣本類類型是無關聯的。你能夠對數組、列表、元組進行模式匹配,這正是基於抽取器模式的。

⑤ 類型模式:你能夠把類型模式看成類型測試和類型轉換的簡易替代,示例以下:

case s: String => s.length

⑥ 變量綁定:除了獨立的變量模式以外,你還能夠把任何其餘模式綁定到變量。只要簡單地寫上變量名、一個@符號,以及這個模式。

模式守衛:模式守衛接在模式以後,開始於if,至關於一個判斷語句。守衛能夠是任意的引用模式中變量的布爾表達式。若是存在模式守衛,只有在守衛返回true的時候匹配纔算成功。

Option類型:scala爲可選值定義了一個名爲Option的標準類型,一個Option實例的值要麼是Some類型的實例,要麼是None對象。分離可選值最一般的辦法是經過模式匹配,以下:

case Some(s) => s

case None => 「?」

模式無處不在:在scala中,模式能夠出如今不少地方,而不僅僅在match表達式裏。好比:

① 模式使用在變量定義中,以下:

val myTuple = (123, 「abc」)

val (number, string) = myTuple

② 模式匹配花括號中的樣本序列(即備選項)能夠用在可以出現函數字面量的任何地方,實質上,樣本序列就是更廣泛的函數字面量,函數字面量只有一個入口點和參數列表,樣本序列能夠有多個入口點,每一個都有本身的參數列表,每一個樣本都是函數的一個入口點,參數被模式所特化。以下:

val withDefault: Option[Int] => String = {

case Some(x) => "is int"

case None => "?"

}

③ for表達式裏也可使用模式。示例以下:

for((number, string) <- myTuple) println(number + string)

模式匹配中的中綴標註:帶有兩個參數的方法能夠做爲中綴操做符使用,使用中綴操做符時其實是其中一個操做數在調用操做符對應的方法,而另外一個操做數做爲方法的參數。但對於模式來講規則有些不一樣:若是被看成模式,那麼相似於p op q這樣的中綴標註等價於op(p,q),也就是說中綴標註符op被用作抽取器模式。

 

===函數

 

函數定義:

定義函數時,除了遞歸函數以外,你能夠省略返回值類型聲明,scala會根據=號後邊的表達式的類型推斷返回值類型,同時=號後邊表達式的值就是函數的返回值,你無需使用return語句(scala推薦你使用表達式值代替return返回值,固然根據你的須要,也能夠顯式使用return返回值)。示例以下:

def abs(x: Double) = if(x >= 0) x else -x

def fac(n: Int) = {

var r = 1

for(i <- 1 to n) r = r * i

r

}

對於遞歸函數必須指定返回值類型,以下:

def fac(n: Int) : Int = if(n <= 0 ) 1 else  n * fac(n-1)

但你要知道的是:聲明函數返回類型,老是有好處的,它可使你的函數接口清晰。所以建議不要省略函數返回類型聲明。

函數體定義時有「=」時,若是函數僅計算單個結果表達式,則能夠省略花括號。若是表達式很短,甚至能夠把它放在def的同一行裏。

去掉了函數體定義時的「=」的函數通常稱之爲「過程」,過程函數的結果類型必定是Unit。所以,有時定義函數時忘記加等號,結果經常是出乎你的意料的。

沒有返回值的函數的默認返回值是Unit。

 

函數調用:

scala中,方法調用的空括號能夠省略。慣例是若是方法帶有反作用就加上括號,若是沒有反作用就去掉括號。若是在函數定義時,省略了空括號,那麼在調用時,就不能加空括號。另外,函數做爲操做符使用時的調用形式參見相應部分。

 

函數參數:

通常狀況下,scala編譯器是沒法推斷函數的參數類型的,所以你須要在參數列表中聲明參數的類型。對於函數字面量來講,根據其使用環境的不一樣,scala有時能夠推斷出其參數類型。

scala裏函數參數的一個重要特徵是它們都是val(這是無需聲明的,在參數列表裏你不能顯式地聲明參數變量爲val),不是var,因此你不能在函數裏面給參數變量從新賦值,這將遭到編譯器的強烈反對。

 

重複參數:

在scala中,你能夠指明函數的最後一個參數是重複的,從而容許客戶向函數傳入可變長度參數列表。要想標註一個重複參數,可在參數的類型以後放一個星號「*」。例如:

def echo(args: String*) = for(arg <- args) println(arg)

這樣的話,echo就能夠被零至多個String參數調用。在函數內部,重複參數的類型是聲明參數類型的數組。所以,echo函數裏被聲明爲類型「String*」的args的類型其實是Array[String]。然而,若是你有一個合適類型的數組,並嘗試把它看成重複參數傳入,會出現編譯錯誤。要實現這個作法,你須要在數組名後添加一個冒號和一個_*符號,以告訴編譯器把數組中的每一個元素看成參數,而不是將整個數組看成單一的參數傳遞給echo函數,以下:

echo(arr: _*)

 

默認參數與命名參數:

函數的默認參數與java以及c++中類似,都是從左向右結合。另外,你也能夠在調用時指定參數名。示例以下:

def fun(str: String, left: String = 「[」, right: String = 「]」) = left + str + right

fun(「hello」)

fun(「hello」, 「<<<」)

fun(「hello」, left = 「<<<」)

 

函數與操做符:

從技術層面上來講,scala沒有操做符重載,由於它根本沒有傳統意義上的操做符。諸如「+」、「-」、「*」、「/」這樣的操做符,其實調用的是方法。方法被看成操做符使用時,根據使用方式的不一樣,能夠分爲:中綴標註(操做符)、前綴標註、後綴標註。

中綴標註:中綴操做符左右分別有一個操做數。方法若只有一個參數(其實是兩個參數,由於有一個隱式的this),調用的時候就能夠省略點及括號。實際上,若是方法有多個顯式參數,也能夠這樣作,只不過你須要把參數用小括號所有括起來。若是方法被看成中綴操做符來使用(也即省略了點及括號),那麼左操做數是方法的調用者,除非方法名以冒號「:」結尾(此時,方法被右操做數調用)。另外,scala的中綴標註不只能夠在操做符中存在,也能夠在模式匹配、類型聲明中存在,參見相應部分。

前綴標註:前綴操做符只有右邊一個操做數。可是對應的方法名應該在操做符字符上加上前綴「unary_」。標識符中能做爲前綴操做符用的只有+、-、!和~。

後綴標註:後綴操做符只有左邊一個操做數。任何不帶顯式參數的方法均可以做爲後綴操做符。

 

 

在scala中,函數的定義方式除了做爲對象成員函數的方法以外,還有內嵌在函數中的函數,函數字面量和函數值。

 

嵌套定義的函數:

嵌套定義的函數也叫本地函數,本地函數僅在包含它的代碼塊中可見。

 

函數字面量:

在scala中,你不只能夠定義和調用函數,還能夠把它們寫成匿名的字面量,也即函數字面量,並把它們做爲值傳遞。函數字面量被編譯進類,並在運行期間實例化爲函數值(任何函數值都是某個擴展了scala包的若干FunctionN特質之一的類的實例,如Function0是沒有參數的函數,Function1是有一個參數的函數等等。每個FunctionN特質有一個apply方法用來調用函數)。所以函數字面量和值的區別在於函數字面量存在於源代碼中,而函數值做爲對象存在於運行期。這個區別很像類(源代碼)和對象(運行期)之間的關係。

如下是對給定數執行加一操做的函數字面量:

(x: Int) => x + 1

其中,=>指出這個函數把左邊的東西轉變爲右邊的東西。在=>右邊,你也可使用{}來包含代碼塊。

函數值是對象,所以你能夠將其存入變量中,這些變量也是函數,你可使用一般的括號函數調用寫法調用它們。如:

val fun = (x: Int) => x + 1

val a = fun(5)

有時,scala編譯器能夠推斷出函數字面量的參數類型,所以你能夠省略參數類型,而後你也能夠省略參數外邊的括號。如:

(x) => x + 1

x => x + 1

若是想讓函數字面量更簡潔,能夠把通配符「_」看成單個參數的佔位符。若是碰見編譯器沒法識別參數類型時,在「_」以後加上參數類型聲明便可。如:

List(1,2,3,4,5).filter(_ > 3)

val fun = (_: Int) + (_: Int)

 

部分應用函數:

你還可使用單個「_」替換整個參數列表。例如能夠寫成:

List(1,2,3,4,5).foreach(println(_))

或者更好的方法是你還能夠寫成:

List(1,2,3,4,5).foreach(println _)

以這種方式使用下劃線時,你就正在寫一個部分應用函數。部分應用函數是一種表達式,你不須要提供函數須要的全部參數,代之以僅提供部分,或不提供所需參數。以下先定義一個函數,而後建立一個部分應用函數,並保存於變量,而後該變量就能夠做爲函數使用:

def sum(a: Int, b: Int, c: Int) = a + b + c

val a = sum _

println(a(1,2,3))

實際發生的事情是這樣的:名爲a的變量指向一個函數值對象,這個函數值是由scala編譯器依照部分應用函數表達式sum _,自動產生的類的一個實例。編譯器產生的類有一個apply方法帶有3個參數(之因此帶3個參數是由於sum _表達式缺乏的參數數量爲3),而後scala編譯器把表達式a(1,2,3)翻譯成對函數值的apply方法的調用。你可使用這種方式把成員函數和本地函數轉換爲函數值,進而在函數中使用它們。不過,你還能夠經過提供某些但不是所有須要的參數表達一個部分應用函數。以下,此變量在使用的時候,能夠僅提供一個參數:

val b = sum(1, _: Int, 3)

若是你正在寫一個省略全部參數的部分應用函數表達式,如println _或sum _,並且在代碼的那個地方正須要一個函數,你就能夠省略掉下劃線(不是須要函數的地方,你這樣寫,編譯器可能會把它看成一個函數調用,由於在scala中,調用無反作用的函數時,默認不加括號)。以下代碼就是:

List(1,2,3,4,5).foreach(println)

 

閉包:

閉包是能夠包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。好比說,在函數字面量中使用定義在其外的局部變量,這就造成了一個閉包。以下代碼foreach中就建立了一個閉包:

var sum = 0

List(1,2,3,4,5).foreach(x => sum += x)

在scala中,閉包捕獲了變量自己,而不是變量的值。變量的變化在閉包中是可見的,反過來,若閉包改變對應變量的值,在外部也是可見的。

 

尾遞歸:

遞歸調用這個動做在最後的遞歸函數叫作尾遞歸。scala編譯器能夠對尾遞歸作出重要優化,當其檢測到尾遞歸就用新值更新函數參數,而後把它替換成一個回到函數開頭的跳轉。

你可使用開關「-g:notailcalls」關掉編譯器的尾遞歸優化。

別高興太早,scala裏尾遞歸優化的侷限性很大,由於jvm指令集使實現更加先進的尾遞歸形式變得困難。尾遞歸優化限定了函數必須在最後一個操做調用自己,而不是轉到某個「函數值」或什麼其餘的中間函數的狀況。

在scala中,你不要刻意迴避使用遞歸,相反,你應該儘可能避免使用while和var配合實現的循環。

 

高階函數:

帶有其餘函數做爲參數的函數稱爲高階函數。

 

柯里化:

柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。以下就是一個柯里化以後的函數:

def curriedSum(x: Int)(y: Int) = x + y

這裏發生的事情是當你調用curriedSum時,實際上接連調用了兩個傳統函數。第一個調用的函數帶單個名爲x的參數,並返回第二個函數的函數值;這個被返回的函數帶一個參數y,並返回最終計算結果。你可使用部分應用函數表達式方式,來獲取第一個調用返回的函數,也即第二個函數,以下:

val onePlus = curriedSum(3)_

 

高階函數和柯里化配合使用能夠提供靈活的抽象控制,更進一步,當函數只有一個參數時,在調用時,你可使用花括號代替小括號,scala支持這種機制,其目的是讓客戶程序員寫出包圍在花括號內的函數字面量,從而讓函數調用感受更像抽象控制,不過須要注意的是:花括號也就是塊表達式,所以你能夠在其中填寫多個表達式,可是最後一個表達式的值做爲該塊表達式的值並最終成爲了函數參數。若是函數有兩個以上的參數,那麼你可使用柯里化的方式來實現函數。

 

傳名參數:

對於以下代碼,myAssert帶有一個函數參數,該參數變量的類型爲不帶函數參數的函數類型:

myAssert(predicate: () => Boolean) = {

if(!predicate())

throw new AssertionError

}

在使用時,咱們須要使用以下的語法:

myAssert(() => 5 > 3)

這樣很麻煩,咱們可使用以下稱之爲「傳名參數」的語法簡化之:

myAssert(predicate: => Boolean) = {

if(!predicate)

throw new AssertionError

}

以上代碼在定義參數類型時是以「=>」開頭而不是「() =>」,並在調用函數(經過函數類型的變量)時,不帶「()」。如今你就能夠這樣使用了:

myAssert(5 > 3)

其中,「predicate: => Boolean」說明predicate是函數類型,在使用時傳入的是函數字面量。注意與「predicate: Boolean」的不一樣,後者predicate是Boolean類型的(表達式)。

 

偏函數:

偏函數和部分應用函數是無關的。偏函數是隻對函數定義域的一個子集進行定義的函數。 scala中用scala.PartialFunction[-T, +S]來表示。偏函數主要用於這樣一種場景:對某些值如今還沒法給出具體的操做(即需求還不明朗),也有可能存在幾種處理方式(視乎具體的需求),咱們能夠先對需求明確的部分進行定義,之後能夠再對定義域進行修改。PartialFunction中可使用的方法以下:

isDefinedAt:判判定義域是否包含指定的輸入。

orElse:補充對其餘域的定義。

compose:組合其餘函數造成一個新的函數,假設有兩個函數f和g,那麼表達式f _ compose g _則會造成一個f(g(x))形式的新函數。你可使用該方法對定義域進行必定的偏移。

andThen:將兩個相關的偏函數串接起來,調用順序是先調用第一個函數,而後調用第二個,假設有兩個函數f和g,那麼表達式f _ andThen g _則會造成一個g(f(x))形式的新函數,恰好與compose相反。

 

===類(class)和對象(object)

 

類(class)和構造器:

類的定義形式以下:

class MyClass(a: Int, b: Int) {

println(a.toString)

}

在scala中,類也能夠帶有類參數,類參數能夠直接在類的主體中使用,不必定義字段而後把構造器的參數賦值到字段裏,但須要注意的是:類參數僅僅是個參數而已,不是字段,若是你須要在別的地方使用,就必須定義字段。不過還有一種稱爲參數化字段的定義形式,能夠簡化字段的定義,以下:

class MyClass(val a: Int, val b: Int) {

println(a.toString)

}

以上代碼中多了val聲明,做用是在定義類參數的同時定義類字段,不過它們使用相同的名字罷了。類參數一樣可使用var做前綴,還可使用private、protected、override修飾等等。scala編譯器會收集類參數並創造出帶一樣的參數的類的主構造器,並將類內部任何既不是字段也不是方法定義的代碼編譯至主構造器中。除了主構造器,scala也能夠有輔助構造器,輔助構造器的定義形式爲def this(…)。每一個輔助構造器都以「this(…)」的形式開頭以調用本類中的其餘構造器,被調用的構造器能夠是主構造器,也能夠是源文件中早於調用構造器定義的其餘輔助構造器。其結果是對scala構造器的調用終將致使對主構造器的調用,所以主構造器是類的惟一入口點。在scala中,只有主構造器能夠調用超類的構造器。

你能夠在類參數列表以前加上private關鍵字,使類的主構造器私有,私有的主構造器只能被類自己以及伴生對象訪問。

可使用require方法來爲構造器的參數加上先決條件,若是不知足要求的話,require會拋出異常,阻止對象的建立。

若是類的主體爲空,那麼能夠省略花括號。

 

訪問級別控制:

公有是scala的默認訪問級別,所以若是你想使成員公有,就不要指定任何訪問修飾符。公有的成員能夠在任何地方被訪問。

私有相似於java,即在以前加上private。不一樣的是,在scala中外部類不能夠訪問內部類的私有成員。

保護相似於java,即在以前加上protected。不一樣的是,在scala中同一個包中的其餘類不能訪問被保護的成員。

scala裏的訪問修飾符能夠經過使用限定詞強調。格式爲private[X]或protected[X]的修飾符表示「直到X」的私有或保護,這裏X指代某個所屬的包、類或單例對象。

scala還有一種比private更嚴格的訪問修飾符,即private[this]。被private[this]標記的定義僅能在包含了定義的同一個對象中被訪問,這種限制被稱爲對象私有。這能夠保證成員不被同一個類中的其餘對象訪問。

對於私有或者保護訪問來講,scala的訪問規則給予了伴生對象和類一些特權,伴生對象能夠訪問全部它的伴生類的私有成員、保護成員,反過來也成立。

 

成員(類型、字段和方法):

scala中也能夠定義類型成員,類型成員以關鍵字type聲明。經過使用類型成員,你能夠爲類型定義別名。

scala裏字段和方法屬於相同的命名空間,scala禁止在同一個類裏用一樣的名稱定義字段和方法,儘管java容許這樣作。

 

getter和setter:

在scala中,類的每一個非私有的var成員變量都隱含定義了getter和setter方法,可是它們的命名並無沿襲java的約定,var變量x的getter方法命名爲「x」,它的setter方法命名爲「x_=」。你也能夠在須要的時候,自行定義相應的getter和setter方法,此時你還能夠不定義關聯的字段,自行定義setter的好處之一就是你能夠進行賦值的合法性檢查。

若是你將scala字段標註爲@BeanProperty時,scala編譯器會自動額外添加符合JavaBeans規範的形如getXxx/setXxx的getter和setter方法。這樣的話,就方便了java與scala的互操做。

 

樣本類:

帶有case修飾符的類稱爲樣本類(case class),這種修飾符可讓scala編譯器自動爲你的類添加一些句法上的便捷設定,以便用於模式匹配,scala編譯器自動添加的句法以下:

① 幫你實現一個該類的伴生對象,並在伴生對象中提供apply方法,讓你不用new關鍵字就能構造出相應的對象;

② 在伴生對象中提供unapply方法讓模式匹配能夠工做;

③ 樣本類參數列表中的全部參數隱式地得到了val前綴,所以它們被看成字段維護;

④ 添加toString、hashCode、equals、copy的「天然」實現。

 

封閉類:

帶有sealed修飾符的類稱爲封閉類(sealed class),封閉類除了類定義所在的文件以外不能再添加任何新的子類。這對於模式匹配來講是很是有用的,由於這意味着你僅須要關心你已經知道的子類便可。這還意味你能夠得到更好的編譯器幫助。

 

單例對象(singleton object):

scala沒有靜態方法,不過它有相似的特性,叫作單例對象,以object關鍵字定義(注:main函數也應該在object中定義,任何擁有合適簽名的main方法的單例對象均可以用來做爲程序的入口點)。定義單例對象並不表明定義了類,所以你不可使用它來new對象。當單例對象與某個類共享同一個名稱時,它就被稱爲這個類的伴生對象(companion object)。類和它的伴生對象必須定義在同一個源文件裏。類被稱爲這個單例對象的伴生類。類和它的伴生對象能夠互相訪問其私有成員。不與伴生類共享名稱的單例對象被稱爲獨立對象(standalone object)。

apply與update:在scala中,一般使用相似函數調用的語法。當使用小括號傳遞變量給對象時,scala都將其轉換爲apply方法的調用,固然前提是這個類型實際定義過apply方法。好比s是一個字符串,那麼s(i)就至關於c++中的s[i]以及java中的s.charAt(i),實際上 s(i) 是 s.apply(i) 的簡寫形式。相似地,BigInt(「123」) 就是 BigInt.apply(「123」) 的簡寫形式,這個語句使用伴生對象BigInt的apply方法產生一個新的BigInt對象,不須要使用new。與此類似的是,當對帶有括號幷包含一到若干參數的變量賦值時,編譯器將使用對象的update方法對括號裏的參數(索引值)和等號右邊的對象執行調用,如arr(0) = 「hello」將轉換爲arr.update(0, 「hello」)。

類和單例對象之間的差異是,單例對象不帶參數,而類能夠。由於單例對象不是用new關鍵字實例化的,因此沒機會傳遞給它實例化參數。單例對象在第一次被訪問的時候纔會被初始化。當你實例化一個對象時,若是使用了new則是用類實例化對象,無new則是用伴生對象生成新對象。同時要注意的是:咱們能夠在類或(單例)對象中嵌套定義其餘的類和(單例)對象。

 

對象相等性:

與java不一樣的是,在scala中,「==」和「!=」能夠直接用來比較對象的相等性,「==」和「!=」方法會去調用equals方法,所以通常狀況下你須要覆蓋equals方法。若是要判斷引用是否相等,可使用eq和ne。

在使用具備哈希結構的容器類庫時,咱們須要同時覆蓋hashCode和equals方法,可是實現一個正確的hashCode和equals方法是比較困難的一件事情,你須要考慮的問題和細節不少,能夠參見java總結中的相應部分。另外,正如樣本類部分所講的那樣,一旦一個類被聲明爲樣本類,那麼scala編譯器就會自動添加正確的符合要求的hashCode和equals方法。

 

===抽象類和抽象成員

 

與java類似,scala中abstract聲明的類是抽象類,抽象類不能夠被實例化。

 

在scala中,抽象類和特質中的方法、字段和類型均可以是抽象的。示例以下:

trait MyAbstract {

type T // 抽象類型

def transform(x: T): T // 抽象方法

val initial: T // 抽象val

var current: T // 抽象var

}

抽象方法:抽象方法不須要(也不容許)有abstract修飾符,一個方法只要是沒有實現(沒有等號或方法體),它就是抽象的。

抽象類型:scala中的類型成員也能夠是抽象的。抽象類型並非說某個類或特質是抽象的(特質自己就是抽象的),抽象類型永遠都是某個類或特質的成員。

抽象字段:沒有初始化的val或var成員是抽象的,此時你須要指定其類型。抽象字段有時會扮演相似於超類的參數這樣的角色,這對於特質來講尤爲重要,由於特質缺乏可以用來傳遞參數的構造器。所以參數化特質的方式就是經過在子類中實現抽象字段完成。如對於如下特質:

trait MyAbstract {

val test: Int

println(test)

def show() {

println(test)

}

}

你可使用以下匿名類語法建立繼承自該特質的匿名類的實例,以下:

new MyAbstract {

val test = 1

}.show()

你能夠經過以上方式參數化特質,可是你會發現這和「new 類名(參數列表)」參數化一個類實例仍是有區別的,由於你看到了對於test變量的兩次println(第一次在特質主體中,第二次是因爲調用了方法show),輸出了兩個不一樣的值(第一次是0,第二次是1)。這主要是因爲超類會在子類以前進行初始化,而超類抽象成員在子類中的具體實現的初始化是在子類中進行的。爲了解決這個問題,你可使用預初始化字段和懶值。

 

預初始化字段:

預初始化字段,可讓你在初始化超類以前初始化子類的字段。預初始化字段用於對象或有名稱的子類時,形式以下:

class B extends {

val a = 1

} with A

預初始化字段用於匿名類時,形式以下:

new {

val a = 1

} with A

須要注意的是:因爲預初始化的字段在超類構造器調用以前被初始化,所以它們的初始化器不能引用正在被構造的對象。

 

懶值:

加上lazy修飾符的val變量稱爲懶值,懶值右側的表達式將直到該懶值第一次被使用的時候才計算。若是懶值的初始化不會產生反作用,那麼懶值定義的順序就不用多加考慮,由於初始化是按需的。

 

===繼承與覆蓋(override)

 

繼承:

繼承時,若是父類主構造器帶有參數,子類須要把要傳遞的參數放在父類名以後的括號裏便可,以下:

class Second(a: Int, b: Int) extends First(a) {…}

 

scala繼承層級:

 

如上圖所示:Any是全部其餘類的超類。Null是全部引用類(繼承自AnyRef的類)的子類,Null類型的值爲null。Nothing是全部其餘類(包括Null)的子類,Nothing類型沒有任何值,它的一個用處是它標明瞭不正常的終止(例如拋出異常,啥也不返回)。AnyVal是scala中內建值類(共9個)的父類。AnyRef是scala中全部引用類的父類,在java平臺上AnyRef實際就是java.lang.Object的別名,所以java裏寫的類和scala裏寫的類都繼承自AnyRef,你能夠認爲java.lang.Object是scala在java平臺上實現AnyRef的方式。scala類與java類的不一樣之處在於,scala類還繼承了一個名爲ScalaObject的特別記號特質,目的是想讓scala程序執行得更高效。

 

覆蓋:

因爲scala裏字段和方法屬於相同的命名空間,這讓字段能夠覆蓋無參數方法或空括號方法,但反過來好像不能夠啊。另外,你也能夠用空括號方法覆蓋無參數方法,反之亦可。在scala中,若子類覆蓋了父類的具體成員則必須帶override修飾符;如果實現了同名的抽象成員時則override是可選的;若並未覆蓋或實現基類中的成員則禁用override修飾符。

 

===特質(trait)

 

特質至關於接口,不能被實例化。特質定義使用trait關鍵字,與類類似,你一樣能夠在其中定義而不只是聲明字段和方法等。你可使用extends或with將多個特質「混入」類中。注意當在定義特質時,使用extends指定了特質的超類,那麼該特質就只能混入擴展了指定的超類的類中。

特質與類的區別在於:①特質不能帶有「類參數」,也即傳遞給主構造器的參數;②不論在類的哪一個地方,super調用都是靜態綁定的,但在特質中,它們是動態綁定的,由於在特質定義時,尚且不知道它的超類是誰,由於它尚未「混入」,因爲在特質中使用super調用超類方法是動態綁定的,所以你須要對特質中相應的方法加上abstract聲明(雖然加上了abstract聲明,但方法仍能夠被具體定義,這種用法只有在特質中有效),以告訴編譯器特質中的該方法只有在特質被混入某個具備期待方法的具體定義的類中才有效。你須要很是注意特質被混入的次序:特質在構造時順序是從左到右,構造器的順序是類的線性化(線性化是描述某個類型的全部超類型的一種技術規格)的反向。因爲多態性,子類的方法最早起做用,所以越靠近右側的特質越先起做用,若是最右側特質調用了super,它調用左側的特質的方法,依此類推。

 

Ordered特質:

Ordered特質擴展自java的Comparable接口。Ordered特質用於排序,爲了使用它,你須要作的是:首先將其混入類中,而後實現一個compare方法。須要注意的是:Ordered並無爲你定義equals方法,由於經過compare實現equals須要檢查傳入對象的類型,可是由於類型擦除,致使它沒法作到。所以,即便繼承了Ordered,也仍是須要本身定義equals。

 

Ordering特質:

Ordering特質擴展自java的Comparator接口。Ordering特質也用於排序,爲了使用它,你須要作的是:定義一個該特質的子類的單獨的實例,須要實現其中的compare方法,並將其做爲參數傳遞給排序函數。此乃策略模式也。

 

Application特質:

特質Application聲明瞭帶有合適簽名的main方法。可是它存在一些問題,因此只有當程序相對簡單而且是單線程的狀況下才能夠繼承Application特質。Application特質相對於APP特質來講,有些陳舊,你應該使用更新的APP特質。

 

APP特質:

APP特質同Application特質同樣,都提供了帶有合適簽名的main方法,在使用時只需將它混入你的類中,而後就能夠在類的主構造器中寫代碼了,無需再定義main方法。若是你須要命令行參數,能夠經過args屬性獲得。

 

===顯式類型轉換

 

正如以前所述的,scala中類型轉換使用方法實現,如下是顯式類型測試和顯式類型轉換的示例:

a.isInstanceOf[String] // 顯式類型測試

a.asInstanceOf[String] // 顯式類型轉換

 

===隱式轉換、隱式參數

 

隱式轉換:

隱式轉換隻是普通的方法,惟一特殊的地方是它以修飾符implicit開始,implicit告訴scala編譯器能夠在一些狀況下自動調用(好比說若是當前類型對象不支持當前操做,那麼scala編譯器就會自動添加調用相應隱式轉換函數的代碼,將其轉換爲支持當前操做的類型的對象,前提是已經存在相應的隱式轉換函數且知足做用域規則),而無需你去調用(固然若是你願意,你也能夠自行調用)。隱式轉換函數定義以下:

implicit def functionName(…) = {…}

隱式轉換知足如下規則:

做用域規則:scala編譯器僅會考慮處於做用域以內的隱式轉換。隱式轉換要麼是以單一標識符的形式(即不能是aaa.bbb的形式,應該是bbb的形式)出如今做用域中,要麼是存在於源類型或者目標類型的伴生對象中。

單一調用規則:編譯器在同一個地方只會添加一次隱式操做,不會在添加了一個隱式操做以後再在其基礎上添加第二個隱式操做。

顯式操做先行規則:若編寫的代碼類型檢查無誤,則不會嘗試任何隱式操做。

 

隱式參數:

柯里化函數的完整的最後一節參數能夠被隱式提供,即隱式參數。此時最後一節參數必須被標記爲implicit(整節參數只需一個implicit,並非每一個參數都須要),同時用來提供隱式參數的相應實際變量也應該標記爲implicit的。對於隱式參數,咱們須要注意的是:

① 隱式參數也能夠被顯式提供;

② 提供隱式參數的實際變量必須以單一標識符的形式出如今做用域中;

③ 編譯器選擇隱式參數的方式是經過匹配參數類型與做用域內的值類型,所以隱式參數應該是很稀少或者很特殊的類型(最好是使用自定義的角色肯定的名稱來命名隱式參數類型),以便不會被碰巧匹配;

④ 若是隱式參數是函數,編譯器不只會嘗試用隱式值補足這個參數,還會把這個參數看成可用的隱式操做而使用於方法體中。

 

視界:

視界使用「<%」符號,能夠用來縮短帶有隱式參數的函數簽名。好比,「T <% Ordered[T]」是在說「任何的T都好,只要T能被看成Ordered[T]便可」,所以只要存在從T到Ordered[T]的隱式轉換便可。

注意視界與上界的不一樣:上界「T <: Ordered[T」是說T是Ordered[T]類型的。

 

隱式操做調試:

隱式操做是scala的很是強大的特性,但有時很難用對也很難調試。

有時若是編譯器不能發現你認爲應該能夠用的隱式轉換,你能夠把該轉換顯式地寫出來,這有助於發現問題。

另外,你能夠在編譯scala程序時,使用「-Xprint:typer」選項來讓編譯器把添加了全部的隱式轉換以後的代碼展現出來。

 

===類型參數化

 

在scala中,類型參數化(相似於泛型)使用方括號實現,如:Foo[A],同時,咱們稱Foo爲高階類型。若是一個高階類型有2個類型參數,則在聲明變量類型時可使用中綴形式來表達,此時也稱該高階類型爲中綴類型,示例以下:

class Foo[A,B]

val x: Int Foo String = null // Int Foo String 等同於 Foo[Int,String]

 

與java類似,scala的類型參數化也使用類型擦除實現(類型擦除是不好勁的泛型機制,不過多是因爲java的緣由,scala也這樣作了),類型擦除的惟一例外就是數組,由於在scala中和java中,它們都被特殊處理,數組的元素類型與數組值保存在一塊兒。在scala中,數組是「不變」的(這點與java不一樣),泛型默認是「不變」的。

 

協變、逆變與不變:

拿Queue爲例,若是S是T的子類型,那麼Queue[S]是Queue[T]的子類型,就稱Queue是協變的;相反,若是Queue[T]是Queue[S]的子類型,那麼Queue是逆變的;既不是協變又不是逆變的是不變的,不變的又叫嚴謹的。

在scala中,泛型默認是不變的。當定義類型時,你能夠在類型參數前加上「+」使類型協變,如Queue[+A]。相似地,你能夠在類型參數前加上「-」使類型逆變。在java中使用類型時能夠經過使用extends和super來達到協變逆變的目的,它們都是「使用點變型」,java不支持「聲明點變型」。而scala中同時提供了聲明點變型(「+」和「-」,它們只能在類型定義時使用)和使用點變型(「<:」和「>:」,相似於java中的extends和super,在使用類型時聲明)。無論是「聲明點變型」仍是「使用點變型」,都遵循PECS法則,詳見java泛型。須要注意的是:變型並不會被繼承,父類被聲明爲變型,子類若想保持仍須要再次聲明。

 

繼承中的協變逆變:

c++、java、scala都支持返回值協變,也就是說在繼承層次中子類覆蓋超類的方法時,能夠指定返回值爲更具體的類型。c#不支持返回值協變。

容許參數逆變的面嚮對象語言並很少——c++、java、scala和c#都會把它當成一個函數重載。

 

更多信息參見java泛型。

 

===集合

 

scala的集合(collection)庫分爲可變(mutable)類型與不可變(immutable)類型。以Set爲例,特質scala.collection.immutable.Set和scala.collection.mutable.Set都擴展自scala.collection.Set。

 

scala集合的頂層抽象類和特質:

 

 

scala.collection.immutable:

 

 

scala.collection.mutable:

 

 

不可變集合與可變集合之間的對應關係:

不可變(collection.immutable._)

可變(collection.mutable._)

Array

ArrayBuffer

List

ListBuffer

String

StringBuilder

-

LinkedList, DoubleLinkedList

List

MutableList

Queue

Queue

Array

ArraySeq

Stack

Stack

HashMap HashSet

HashMap HashSet

-

ArrayStack

 

Iterable與Iterator:

Iterable是可變和不可變序列、集、映射的超特質。集合對象能夠經過調用iterator方法來產生迭代器Iterator。Iterable與Iterator之間的差別在於:前者指代的是能夠被枚舉的類型,然後者是用來執行枚舉操做的機制。儘管Iterable能夠被枚舉若干次,但Iterator僅能使用一次。

 

數組:

在scala中,數組保存相同類型的元素,其中包含的元素值是可變的。數組也是對象,訪問數組使用小括號。在JVM中,scala的數組以java數組方式實現。scala中數組是非協變的。

定長數組使用Array,建立以後長度不可改變。變長數組使用ArrayBuffer。

與java同樣,scala中多維數組也是經過數組的數組來實現的。構造多維數組可使用ofDim方法或者直接使用for循環來new。示例以下:

val matrix = Array.ofDim[Double](3,4) // ofDim方法建立多維數組

matrix(1)(2) = 12.36

val mutliarr = new Array[Array[Int]](10) // for循環方式建立多維數組

for(i <- 0 until mutliarr.length)

mutliarr(i) = new Array[Int](5)

 

列表:

列表保存相同類型的元素。scala裏的列表類型是協變的,這意味着若是S是T的子類,那麼List[S]也是List[T]的子類。

不可變列表使用List,一旦建立以後就不可改變。可變列表使用ListBuffer。

List是抽象類,它有兩個子類型:Nil和::。Nil是空列表對象,類型是List[Nothing]。::是樣本類,能夠建立非空列表,::的伴生對象能夠以中綴標註的形式用於模式匹配。因此在scala中存在兩個::,一個是樣本類,另外一個是List的方法,所以在構造一個列表時,咱們就有了多種方法,以下:

val list1 = List("A") // 這裏List是伴生對象,至關於 List.apply()

val list2 = ::("A",Nil) // 這裏::是伴生對象, 至關於 ::.apply()

val list3 = "A" :: Nil // 這裏::是方法, 至關於 Nil.::()

List類沒有提供append操做(向列表尾部追加),由於隨着列表變長,效率將逐漸低下。List提供了「::」作前綴插入,由於這將消耗固定時間。若是你想經過添加元素來構造列表,你的選擇是先把它們前綴插入,完成以後再調用reverse;或者使用ListBuffer,一種提供append操做的可變列表,完成以後調用toList。

 

棧和隊列:

scala集合庫提供了可變和不可變的棧類Stack,也提供了可變和不可變的隊列類Queue。

 

元組與對偶:

元組Tuple也是不可變的,但元組能夠包含不一樣類型的元素,而且所以而不能繼承自Iterable。

元組實例化以後,可使用點號、下劃線和從1開始的索引訪問其中的元素。由於元組能夠保存不一樣類型的元素,因此不能使用apply方法訪問其元素(apply返回一樣的類型)。元組的索引從1開始,是由於對於擁有靜態類型元組的其餘語言,如Haskell和ML,從1開始是傳統的設定。

scala的任何對象均可以調用「->」方法,並返回包含鍵值對的二元組(也叫對偶,是元組的最簡單形態),好比 「hello」 -> 100 則建立出 (「hello」, 100)。

元組相應操做示例以下:

val t = (1400, 「Jim」, 「haha」, 3.14) // 定義一個元組

val second = t._2 // 引用元組第二個組元

val (first, second, third, fourth) = t // 分別獲取元組的第一、二、三、4個組元

val (first, secong, _) = t // 只獲取前兩個組元

 

集和映射:

集中保存着不重複的元素。映射能夠把鍵和值關聯起來保存。

 

拉鍊操做:

val symbols = Array(「<」, 「-」, 「>」)

val counts = Array(2, 10, 2)

val pairs = symbols.zip(counts)

以上代碼生成對偶類型的數組,以下:

Array((<,2), (-,10), (>,2))

 

可變集合vs不可變集合:

可變集合性能更好,不可變集合更易於理清頭緒。對於某些問題來講,可變集合可以很好的處理;而另外一些,不可變集合更爲合適。若是在使用可變集合時,你發現須要擔心什麼時候複製可變集合的副本,或者思考不少關於誰「主宰」或「擁有」可變集合的時候,那麼請考慮是否可用不可變集合代替。

 

===異常

 

scala的異常工做機制與java的相似,但也有區別。區別以下:

① scala沒有「受檢」異常——你不須要聲明函數或方法可能會拋出某種異常。

② throw表達式是有值的,其值是Nothing類型。

③ try-catch-finally表達式也是有值的,可是狀況有些特殊。當沒有拋出異常時,try子句爲表達式值;若是拋出異常並被捕獲,則對應於相應的catch子句;若是沒有被捕獲,表達式就沒有返回值。finally子句計算獲得的值,老是被拋棄(除非使用return語句),因此你應該在finally子句中幹一些它應該乾的事,好比說:關閉文件、套接字、數據庫鏈接等,而最好別幹什麼其餘事。

 

===斷言、檢查

 

scala裏,斷言使用assert函數,檢查使用ensuring函數,若是條件不成立,它們將會拋出AssertionError。它們都在Predef中定義。你可使用JVM的-ea和-da命令行標誌來開放和禁止斷言以及檢查。

 

===包和引用

 

打包:

scala的代碼採用了java平臺完整的包機制。你可使用兩種方式把代碼放進包裏:

① 使用放在文件頂部的package子句來把整個文件放入包中;

② 使用package子句把要放入到包中的代碼用花括號括起來,這種方式像C#的命名空間。使用這種方式,你能夠定義出嵌套的包,注意:scala的包能夠嵌套,java則不能夠。任何你本身寫的頂層包都被隱含地包含在_root_包中,所以你能夠在多層嵌套的包代碼中經過_root_來訪問頂層包中的代碼。

 

引用:

與java相似,scala使用import來引用,與java不一樣的是,scala的import子句:

① 能夠出如今任何地方,而不只僅在文件開始處;

② 能夠引用對象和包;

③ 能夠重命名或隱藏一些被引用的成員。這能夠經過在被引用成員的對象以後加上括號裏的引用選擇器子句來作到,示例以下(令p爲包名):

import p.{x} // 從p中引入x,等價於 import p.x

import p.{x => y} // 從p中引入x,並重命名爲y

import p.{x => _, _} // 從p中引入除了x以外的全部東東。注意單獨的「_」稱做全包括,必須位於選擇器的最後。import p.{_} 等價於 import p._

 

隱式引用:

scala隱含地爲每一個源文件都加入以下引用:

import java.lang._

import scala._

import Predef._

包scala中的Predef對象包含了許多有用的方法。例如:一般咱們所使用的println、readLine、assert等。

 

===scala I/O

 

因爲scala能夠和java互操做,所以目前scala中的I/O類庫並很少,你可能須要使用java中的I/O類庫。下面介紹scala中有的東東:

 

scala.Console對象能夠用於終端輸入輸出,其中終端輸入函數有:readLine、readInt、readChar等等,終端輸出函數有:print、println、printf等等。其實,Predef對象中提供的預約義的readLine、println等等方法都是Console對象中對應方法的別名。

 

scala.io.Source能夠以文本的方式迭代地讀取源文件或者其餘數據源。用完以後記得close啊。

 

對象序列化:

爲了讓對象可序列化,你能夠這樣定義類:

@SerialVersionUID(42L) class Person extends Serializable {…}

其中,@SerialVersionUID註解指定序列化ID,若是你能接受缺省的ID,也可省去該註解;Serializable在scala包中,所以你無需引入。你能夠像java中同樣對對象進行序列化。scala集合類都是能夠序列化的,所以你能夠把它們做爲你的可序列化類的成員。

 

===Actor和併發

 

與java的基於共享數據和鎖的線程模型不一樣,scala的actor包則提供了另一種不共享任何數據、依賴消息傳遞的模型。設計併發軟件時,actor是首選的工具,由於它們可以幫助你避開死鎖和爭用情況,這兩種情形都是在使用共享和鎖模型時很容易遇到的。

 

建立actor:

actor是一個相似於線程的實體,它有一個用來接收消息的郵箱。實現actor的方法是繼承scala.actors.Actor特質並完成其act方法。你能夠經過actor的start方法來啓動它。actor在運行時都是相互獨立的。你也可使用scala.actors.Actor對象的actor方法來建立actor,不過此時你就無需再調用start方法,由於它在建立以後立刻啓動。

 

發送接收消息:

Actor經過相互發送消息的方式進行通訊,你可使用「!」方法來發送消息,使用receive方法來接收消息,receive方法中包含消息處理的模式匹配(偏函數)。發送消息並不會致使actor阻塞,發送的消息在接收actor的郵箱中等待處理,直到actor調用了receive方法,若是actor調用了receive但沒有模式匹配成功的消息,那麼該actor將會阻塞,直到收到了匹配的消息。建立actor併發送接收消息的示例以下:

object ScalaTest extends Actor {

def act() {

while (true) {

receive {

case msg => println(msg)

}

}

}

 

def main(args: Array[String]) {

start()

this ! "hello."

}

}

 

將原生線程看成actor:

Actor子系統會管理一個或多個原生線程供本身使用。只要你用的是你顯式定義的actor,就不須要關心它們和線程的對應關係是怎樣的。該子系統也支持反過來的情形:即每一個原生線程也能夠被看成actor來使用。此時,你應該使用Actor.self方法來將當前線程做爲actor來查看,也就是說能夠這樣使用了:Actor.self ! "message"。

 

經過重用線程獲取更好的性能:

Actor是構建在普通java線程之上的,若是你想讓程序儘量高效,那麼慎用線程的建立和切換就很重要了。爲幫助你節約線程,scala提供了React方法,和receive同樣,react帶有一個偏函數,不一樣的是,react在找到並處理消息後並不返回(它的返回類型是Nothing),它在處理完消息以後就結束了。因爲react不須要返回,故其不須要保留當前線程的調用棧。所以actor庫能夠在下一個被喚醒的線程中重用當前的線程。極端狀況下,若是程序中全部的actor都使用react,則它們能夠用單個線程實現。

因爲react不返回,接收消息的消息處理器如今必須同時處理消息並執行actor全部餘下的工做。一般的作法是用一個頂級的工做方法(好比act方法自身)供消息處理器在處理完消息自己以後調用。編寫使用react而非receive的actor頗具挑戰性,不過在性能上可以帶來至關的回報。另外,actor庫提供的Actor.loop函數能夠重複執行一個代碼塊,哪怕代碼調用的是react。

 

良好的actor風格:

良好的actor風格的併發編程可使你的程序更容易調試而且減小死鎖和爭用情況。下面是一些actor風格編程的指導意見:

actor不該阻塞:編寫良好的actor在處理消息時並不阻塞。由於阻塞可能會致使死鎖,即其餘多個actor都在等待該阻塞的actor的響應。代替阻塞當前actor,你能夠建立一個助手actor,該助手actor在睡眠一段時間以後發回一個消息,以告訴建立它的actor。記住一句話:會阻塞的actor不要處理消息,處理消息的actor請不要使其阻塞。

只經過消息與actor通訊:Actor模型解決共享數據和鎖的關鍵方法是提供了一個安全的空間——actor的act方法——在這裏你能夠順序地思考。換個說法就是,actor讓你能夠像一組獨立的經過異步消息傳遞來進行交互的單線程的程序那樣編寫多線程的程序。不過,這隻有在消息是你的actor的惟一通訊途徑的前提下才成立。一旦你繞過了actor之間的消息傳遞機制,你就回到了共享數據和鎖的模型中,全部那些你想用actor模型避開的困難又都回來了。但這並非說你應該徹底避免繞開消息傳遞的作法,雖然共享數據和鎖要作正確很難,但也不是徹底不可能。實際上scala的actor和Erlang的actor實現方式的區別之一就是,scala讓你能夠在同一個程序中混用actor與共享數據和鎖兩種模型。

優選不可變消息:actor模型提供了每一個actor的act方法的單線程環境,你無需擔憂它使用到的對象是不是線程安全的,由於它們都被侷限於一個線程中。但例外的是在多個線程中間傳送的消息對象,它被多個actor共享,所以你須要擔憂消息對象是否線程安全。確保消息對象是線程安全的最佳途徑是在消息中只使用不可變對象。若是你發現你有一個可變對象,而且想經過消息發送給另外一個actor,你應該製做併發送它的一個副本。

讓消息自包含:actor在發送請求消息以後不該阻塞,它繼續作着其餘事情,當收到響應消息以後,它如何解釋它(也就是說它如何能記起它在發送請求消息時它在作着什麼呢),這是一個問題。解決辦法就是在響應消息中增長冗餘信息,好比說能夠把請求消息中的一些東東做爲響應消息的一部分發送給請求者。

 

===GUI編程

 

scala圖形用戶界面編程可使用scala.swing庫,該庫提供了對java的Swing框架的GUI類的訪問,對其進行包裝,隱藏了大部分複雜度。示例以下:

object MyScalaGUI extends SimpleSwingApplication {

def top = new MainFrame {

title = "My Scala GUI"

location = new Point(600, 300)

preferredSize = new Dimension(400, 200)

val button = new Button {

text = "Click me"

}

val label = new Label {

text = "Who am I?"

}

contents = new BoxPanel(Orientation.Vertical) {

contents += button

contents += label

}

listenTo(button)

reactions += {

case ButtonClicked(b) => label.text = "Hello, I'm Tom."

}

}

}

要編寫圖形界面程序,你能夠繼承SimpleSwingApplication類,該類已經定義了包含一些設置java Swing框架代碼的main方法,main方法隨後會調用top方法,而top方法是抽象的,須要你來實現。top方法應當包含定義頂級GUI組件的代碼,這一般是某種Frame。

 

GUI類庫:

Frame:便可以包含任意數據的窗體。Frame有一些屬性(也就是getter和setter),其中比較重要的有title(將被寫到標題欄)、contents(將被顯示在窗體中)。Frame繼承自Container,每一個Container都有一個contents屬性,讓你得到和設置它包含的組件,不過,Frame的contents只能包含一個組件,由於框架的contents屬性只能經過「=」賦值,而有些Container (如Panel)的contents能夠包含多個組件,由於它們的contents屬性能夠經過「+=」賦值。

MainFrame:就像是個普通的Swing的Frame,只不過關閉它的同時也會關閉整個GUI應用程序。

Panel:面板是根據某種固定的佈局規則顯示全部它包含的組件的容器。面板能夠包含多個組件。

 

處理事件:

爲了處理事件,你須要爲用戶輸入事件關聯一個動做,scala和java基本上用相同的「發佈/訂閱」方式來處理事件。發佈者又稱做事件源,訂閱者又稱做事件監聽器。舉例來講,Button是一個事件源,它能夠發佈一個事件ButtonClicked,表示該按鈕被點擊。scala中事件是真正的對象,建立一個事件也就是建立一個樣本類的實例,樣本類的參數指向事件源。事件(樣本)類包含在scala.swing.event包中。

在scala中,訂閱一個事件源source的方法是調用listenTo(source),取消訂閱的方法是調用deafTo(source)。好比:你可讓一個組件A監聽它其中的一個組件B,以便A在B發出任何事件時獲得通知。爲了讓A對監聽到的事件作出響應,你須要向A中名爲reactions的屬性添加一個處理器,處理器也就是帶有模式匹配的函數字面量,能夠在單個處理器中用多個樣原本匹配多種事件。可以使用「+=」向reactions中添加處理器,使用「-=」從中移除處理器。從概念上講,reactions中安裝的處理器造成一個棧,當接收到一個事件時,最後被安裝的處理器首先被嘗試,但無論嘗試是否成功,後續的處理器都會被一一嘗試。

 

===結合scala和java

 

scala和java高度兼容,所以能夠進行互操做,大多數狀況下結合這兩種語言時並不須要太多顧慮,儘管如此,有時你仍是會遇到一些結合java和scala的問題。基本上,scala使用java代碼相對於java使用scala代碼更容易一些。

 

scala代碼如何被翻譯:

scala的實現方式是將代碼翻譯爲標準的java字節碼。scala的特性儘量地直接映射爲相對等的java特性。但scala中的某些特性(如特質)在java中沒有,而還有一些特性(如泛型)與java中的不盡相同,對於這些特性,scala代碼沒法直接映射爲java的語法結構,所以它必須結合java現有的特性來進行編碼。這些是通常性的原則,如今咱們來考慮一些特例:

值類型:相似Int這樣的值類型翻譯成java有兩種不一樣的方式,只要可能就直接翻譯爲java的int以得到更好的性能,但有時作不到,就翻譯爲包裝類的對象。

單例對象:scala對單例對象的翻譯採用了靜態和實例方法相結合的方式。對每個scala單例對象,編譯器都會爲這個對象建立一個名稱後加美圓符號的java類,這個類擁有scala單例對象的全部方法和字段,這個java類同時還有一個名爲MODULE$的靜態字段,保存該類在運行期建立的一個實例。也就是說,在java中要這樣使用scala中的單例對象:單例對象名$.MODULE$.方法名();

特質:編譯任何scala特質都會建立一個同名的java接口,這個接口能夠做爲java類型使用,你能夠經過這個類型的變量來調用scala對象的方法。若是特質中還有已經實現的方法,那麼還會生成對應的實現類「特質名$class」。

 

存在類型:

全部java類型在scala中都有對等的概念,這是必要的,以便scala能夠訪問任何合法的java類。scala中的存在類型實際上主要是用於從scala訪問java泛型中的通配類型以及沒有給出類型參數的原始類型。存在類型的通用形式以下:

type forSome { declarations }

type部分是任意的scala類型,而declarations部分是一個抽象val和type的列表。這個定義解讀爲:聲明的變量和類型是存在但未知的,正如類中的抽象成員那樣。這個類型進而被容許引用這些聲明的變量和類型,雖然編譯器不知道它們具體指向什麼。例如對於以下的java語句:

Iterator<?>

Iterator<? extends Component>

在scala中能夠用存在類型分別表示爲:

Iterator[T] forSome { type T }

Iterator[T] forSome { type T <: Component }

另外,存在類型也可使用下劃線佔位符語法以更簡短的方式來寫。若是在可使用類型的地方使用了下劃線,那麼scala會爲你作出一個存在類型,每一個下劃線在forSome語句中都變成一個類型參數。如前咱們在介紹scala的泛型時,指定類型參數上界下界時,使用的就是這種簡寫的語法。做爲示例,咱們能夠將上述兩個語句簡寫爲:

Iterator[_]

Iterator[_ <: Component]

 

隱式轉換爲java類型:

 

在scala和java代碼之間傳遞數據時,若是使用的是容器類庫(固然包括數組等),那麼可能須要進行一些轉換,而scala類庫自己就提供了這樣的隱式轉換庫,所以你能夠方便地在scala和java之間傳遞數據。你惟一須要作的是:import scala.collection.JavaConversions._。

相關文章
相關標籤/搜索