1. 程式人生 > >Scala基礎學習入門

Scala基礎學習入門

從技術上來講,scala程式並不是一個直譯器,你在命令列中輸入的內容被快速地編譯成位元組碼,然後這段位元組碼交給Java虛擬機器執行。 變數宣告:
val val不能再繼續賦值, 鼓勵使用該命令方式
var 生命週期中可以被多次賦值 大多數程式並不需要那麼多var變數
scala中變數或函式的型別總是寫在變數或函式名稱後面:
val greeting: String = "Hello"
  僅當同一行程式碼中存在多條語句時才需要用分號隔開。 scala中的陣列 如果陣列的長度固定,使用Array,如果長度可能有變化則使用ArrayBuffer,通過ArrayBuffer.toArray方法返回Array; 如果需要遍歷陣列,陣列和陣列列表有一些語法上的不同,for迴圈的語法:
for (i <- 0 until a.length)
     println(i+”:”+a(i))
  until是RichInt類的方法,返回所有小於(但不包括)上限的數字,可以使用 until(end, step)來限定跳躍的步數,進行定製化的遍歷操作;還可以使用reverse方法來反轉整個Range。 對於scala來說,以某種方式對它進行轉換是非常簡單的,這些轉換動作不會修改原始陣列,而是產生一個全新的數值,可以使用 for (elem <- a) yield 2 * elem 來返回一個型別與原始集合相同的新集合(並不會修改原始陣列),當然也可以按照下面的方式,僅針對某幾個過濾好的元素來操作:
for (elem <- a if elem % 2 == 0) yield 2 * elem
  陣列中也提供了一些比較常用的演算法,
  • sum: 可以呼叫sum操作來獲取Array其值的總和;
  • min:Array的最小值;
  • max:Array中的最大值;
  • sorted:將Array或ArrayBuffer排序並返回經過排序的Array/ArrayBuffer,不會修改原始版本;
  • sortWith:提供函式用於排序;
  • scala.util.Sorting.quickSort(陣列)
  • mkString: 連線陣列中的字串,可以定義連線符,起始和結束符號;
scala陣列是用Java陣列實現的,可以在Java與Scala之間來回傳遞,但是可以使用更加方便的方式,引入scala.collection.JavaConversions中的隱式轉換方法,這樣就可以在程式碼中直接使用Java的集合。 對映和元組
scala中可以使用下面的方式來建立對映:
val scores = Map("Alice"->10, "Bob"->3, "Cindy"->4)
  ->操作符可以用於建立對偶,對偶 “Alice”->10, 產出的值為(“Alice”, 10)。 在scala中,函式與對映之間的相似性尤為明顯,將使用()表示法來查詢某個鍵對應的值,scores(“Alice”);getOrElse(key, default)來獲取某個值,當key不存在時使用default值代替。 如果想要更新對映中的值,可以對()的值進行賦值操作:scores(“Alice”)=20,或者通過+=操作來新增多個關係,-=來移除某個鍵和對應的值。 對於對映的迭代,可以用迴圈來進行遍歷:
for ((k,v) <- 對映) 處理k和v
  如果需要反轉一個對映,交換鍵和值的位置,可以使用for yield操作來簡單實現:
for ((k,v) <- 對映) yield (v,k)
  如果需要實現與Java Map的互操作,可以通過增加import scala.collection.JavaConversions.mapAsScalaMap/mapAsJavaMap來實現。 對映是鍵值對偶的集合,對偶是元組tuple的最簡單形態,它只有兩個元素,而元組是可以包含多個元素的,Tuple[Int, Double, java.lang.String],訪問元組的方式比較簡單,只需要根據 variable._1 ._2這種方式來實現。 使用元組的原因之一就是把多個值綁在一起,使它們能夠被一起處理,這通常用zip方法來完成:
val symbols = Array("<", "-", ">")
val counts = Array(2, 10, 2)
val pairs = symbols.zip(counts)
  //生成的pairs就會存在一個 Tuple的集合 (“>”, 2), (“-“, 10), (“>”, 2) 類-class scala中,類並不宣告為public,方法預設是public的。scala原始檔中可以包含多個類,所有這些型別都具有公有可見性。在呼叫無參方法時,可以寫上圓括號,也可以不寫。關於這點的最佳實踐是,對於setter型別的方法使用括號,而getter型別的方法不用使用括號。 在編寫Java類時,我們往往不喜歡使用公有欄位,scala中對每個欄位都提供getter/setter方法,假如欄位名為age,則getter/setter方法名稱分別叫做age和age_=。scala中對每個欄位生成getter/setter方法聽上去比較恐怖,但我們可以控制該過程:
  • 如果欄位是私有的,則getter/setter方法也是私有的;
  • 如果欄位是val,則只有getter方法被生成(該欄位對應java的final屬性);
  • 如果不需要任何getter/setter,可以將欄位宣告為private[this];
在scala/Java中,方法可以訪問該類的所有物件私有欄位,但在scala中允許我們定義更加嚴格的訪問限制,通過private[this]這個修飾符來實現,且該欄位不會生成getter/setter方法。 scala提供生成的getter/setter方法針對Java的Bean規範時,可能並不是預期的,很多Java規範依賴getFoo/setFoo這種型別的命名規範。當在scala欄位中標註@BeanProperty時,這樣的方法就會自動生成了。 與Java一樣,scala中也可以有任意多的構造器,不過scala中有著主構造器(primary constructor)的概念,除了主構造器之外,類還可以有任意多個輔助構造器。 輔助構造器的名稱為this,每一個輔助構造器都必須以一個對先前已定義的其他輔助構造器或主構造器的呼叫開始。一個類如果沒有顯示地定義主構造器則自動擁有一個無參的主構造器。 在scala中每個類都會有主構造器,主構造器並不以this方法定義,而是與類定義交織在一起。 主構造器的引數直接放置在類名之後,主構造器的引數被編譯成欄位,其值被初始化成構造時傳入的引數,主構造器會執行類定義中的所有語句:
class Person(var name:String, private var age:Int){
     print(“….")
}
  在構造該Person物件時,會同時打印出其中的語句。 如果類名之後沒有引數,則具備一個無參主構造器,這樣一個構造器僅僅是簡單地執行類體中的所有語句而已。主構造器中的引數也可以不帶val/var,這取決於後續其他方法是否使用該欄位,如果沒有使用僅當做是區域性變數,如果使用了該欄位就升級為型別中欄位。 物件 scala中沒有靜態方法或靜態欄位,可以使用object這個語法結構來達到同樣的目的,物件定義了某個類的單個例項,物件使用object來定義而非class,可以將下面的類定義想象成靜態工具類方法,用於產生JVM內部唯一的unique數字:
object Accounts {
  private var lastNumber = 0
 
  def newUniqueNumber() = {
    lastNumber += 1
    lastNumber
  }
}
  物件在本職上可以擁有類的所有特性——甚至可以擴充套件其他類或者特質,但不能提供構造器函式。任何在Java中使用單例物件的地方,在scala中都可以用物件來實現:
  • 作為存放工具函式或常量的地方;
  • 高效地共享單個不可變例項;
  • 需要用單個例項來協調某個服務時;
在Java中,經常需要用到既有例項方法又有靜態方法的類,在scala中,可以通過類和類同名的伴生物件來達到同樣的目的,就好比Java中既存在靜態方法又存在例項方法一樣。類和它的伴生物件可以互相訪問其私有特性,它們必須存在於同一個原始檔中。 一個object可以擴充套件類以及一個或多個特質(trait),其最終結果是一個擴充套件了指定類以及特質的類物件,同時擁有在物件定義中給出的所有特性,此時該例項在JVM中就為同一個例項存在。 我們通常會定義和使用物件的apply方法,當遇到下面型別的表示式,apply方法就會被呼叫,通常這樣的一個apply方法返回的是伴生類物件:
Object(引數1, 引數2,...)
  對於巢狀表示式而言,這種方式省去了new關鍵字,會方便很多。注意,Array(100)和new Array(100)很容易混淆,前者呼叫的是apply方法,後者呼叫的是構造器。 每個scala程式都必須從一個物件的main方法開始,這個方法的簽名類似:
def main(args: Array[String]) {
}
  除了每次都提供main方法之外,還可以擴充套件app特質,將程式程式碼放入構造器方法體內,如果需要命令列引數,可以通過args來得到:
object Hello extends App{
     println(“Hello!")
}
  如果在呼叫該應用程式時設定了-Dscala.time選項的話,程式在退出時會顯示逝去時間。 和Java不同,scala並沒有列舉型別,不過標準類庫中提供了一個Enumeration助手類,用於產出列舉。
object TrafficLightColor extends Enumeration {
  val Red, Yellow, Green = Value
}
  記住列舉的型別為TrafficLightColor.Value,可以使用import TrafficLightColor._來靜態匯入所有列舉值。 特質 scala和java一樣不允許類從多個超類繼承,因為對於多重繼承來說的代價非常之高。Java採取了非常強的限制策略,類只能擴充套件自一個超類,可以實現任意數量的介面,但介面只能包含抽象方法,不能包含欄位,所以在Java中我們經常看到同時提供介面和抽象基類的做法。 scala中提供特質而非介面,特質可以同時擁有抽象方法和具體方法,而類可以實現多個特質,這個設計乾淨利落地解決了Java介面的問題。 trait的定義很簡單,類似Java介面:
trait Logger {
  def log(msg: String)
}
  子類實現時使用extends而非implements:
class ConsoleLogger extends Logger{
  override def log(msg: String): Unit = {println(msg)}
}
  在重寫特質的抽象方法時不需要給出override關鍵字,如果需要的特質不止一個,需要用with關鍵字來新增額外的特質(所有Java介面都可以當做scala的特質使用)。 特質的方法並不一定需要是抽象的,注意如果實現具有帶實現的特質時,可以說得到了一個具體的實現方法,這在Java介面中是無法做到的,也可以說被混入了。注意讓特質擁有具體行為存在一個弊端,當特質改變時,所有混入了該特質的類都必須要重新編譯。 trait可以在物件定義時才被“混入”到物件中,這無疑進一步省略了一個java class定義(相比於內部類或匿名內部類),注意被混入的trait需要有著具體方法實現,這樣在SavingAccount類中就可以在執行時才決定使用的trait:
class SavingAccount extends Account with Logger{
  override def withdraw(amount: Double): Unit = {
    if(amount > 1000) log("Insufficient funds")
  }
}
val acct = new SavingAccount with ConsoleLogger
  此外,特質還可以疊加,可以為類或物件新增多個互相呼叫的特質,從最後一個開始,這對於需要分階段加工處理某個值的場景非常有用,從設計模式角度,這個非常類似於Java中的裝飾者模式(Decorator,繼承同樣的介面,疊加不同的實現)。 額外增加兩個trait實現:
trait ShortLogger extends Logger{
  val maxLength = 15
  override def log(msg: String): Unit ={
    super.log(if(msg.length <= maxLength) msg else msg.substring(0, maxLength - 3))
  }
}
trait TimestampLogger extends Logger{
  override def log(msg: String): Unit = {
    super.log(new java.util.Date() + " " + msg)
  }
}
  在定義時,使用兩個特質進行疊加到ConsoleLogger中,注意,我們在定義ShortLogger/TimestampLogger時,使用的都是super.log,這樣就會將修改過的msg傳入到ConsoleLogger中,執行的順序取決於定義的順序,限制性ShortLogger,再執行TimestampLogger,此種定義使用時ShortLogger執行的super.log會呼叫TimestampLogger,同樣TimestampLogger的super.log也會呼叫到ConsoleLogger。
val acct = new SavingAccount with ConsoleLogger with TimestampLogger with ShortLogger
acct.withdraw(1001)
  可以非常方便地在物件宣告處修改順序,這好比在語言層面上增加了AOP功能。 對特質而言,無法從原始碼判斷super.someMethod會執行到哪裡,確切的方法依賴於使用這些特質的物件或類給出的順序,要靈活得多。如果需要控制具體哪一個特質的方法被呼叫,可以在括號中給出名稱: super[ConsoleLogger].log(…),這裡給出的型別必須是超型別,無法使用繼承層次中更遠的特質或類。 特質中可以包含大量工具方法,而這些工具方法可以依賴一些抽象方法來實現,with一個特質,在實現中大量使用特質中的方法,到物件建立時才去指定特質的具體實現。從Java的實際模式角度,這可以看成是策略模式(Strategy,可替換的演算法實現)的直接語言層面實現。 當然,特質也會有構造器,特質的構造器以如下方式執行:
  1. 呼叫超類的構造器;
  2. 特質構造器在超類構造器之後,類構造器之前執行;
  3. 特質由左到右被構造;
  4. 每個特質當中,父特質先被構造;
  5. 如果多個特質共有一個父特質,而那個父特質已經被構造,則不會再次構造;
  6. 所有特質構造完畢,子類被構造。
下面的類被宣告:
class SavingAccount extends Account with FileLogger with ShortLogger
  構造器的執行順序:1.Account(超類);2.Logger(第一個特質的父類);3.FileLogger(父類);4.ShortLogger(第二個特質,由於其超類已經被構造過,跳過);5.SavingAccount。 特質不能有構造器引數,每個特質都有一個無引數的構造器,缺少構造器引數是特質與類之間唯一的技術差別,除此之外特質可以具備類的所有特性。