Scala基礎學習入門
阿新 • • 發佈:2018-12-23
從技術上來講,scala程式並不是一個直譯器,你在命令列中輸入的內容被快速地編譯成位元組碼,然後這段位元組碼交給Java虛擬機器執行。
變數宣告:
scala中變數或函式的型別總是寫在變數或函式名稱後面:
scala中可以使用下面的方式來建立對映:
val | val不能再繼續賦值, | 鼓勵使用該命令方式 |
var | 生命週期中可以被多次賦值 | 大多數程式並不需要那麼多var變數 |
val greeting: String = "Hello"
僅當同一行程式碼中存在多條語句時才需要用分號隔開。
scala中的陣列
如果陣列的長度固定,使用Array,如果長度可能有變化則使用ArrayBuffer,通過ArrayBuffer.toArray方法返回Array;
如果需要遍歷陣列,陣列和陣列列表有一些語法上的不同,for迴圈的語法:
until是RichInt類的方法,返回所有小於(但不包括)上限的數字,可以使用 until(end, step)來限定跳躍的步數,進行定製化的遍歷操作;還可以使用reverse方法來反轉整個Range。 對於scala來說,以某種方式對它進行轉換是非常簡單的,這些轉換動作不會修改原始陣列,而是產生一個全新的數值,可以使用 for (elem <- a) yield 2 * elem 來返回一個型別與原始集合相同的新集合(並不會修改原始陣列),當然也可以按照下面的方式,僅針對某幾個過濾好的元素來操作:for (i <- 0 until a.length) println(i+”:”+a(i))
陣列中也提供了一些比較常用的演算法,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: 連線陣列中的字串,可以定義連線符,起始和結束符號;
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];
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中都可以用物件來實現:
- 作為存放工具函式或常量的地方;
- 高效地共享單個不可變例項;
- 需要用單個例項來協調某個服務時;
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,可替換的演算法實現)的直接語言層面實現。
當然,特質也會有構造器,特質的構造器以如下方式執行:
- 呼叫超類的構造器;
- 特質構造器在超類構造器之後,類構造器之前執行;
- 特質由左到右被構造;
- 每個特質當中,父特質先被構造;
- 如果多個特質共有一個父特質,而那個父特質已經被構造,則不會再次構造;
- 所有特質構造完畢,子類被構造。
class SavingAccount extends Account with FileLogger with ShortLogger
構造器的執行順序:1.Account(超類);2.Logger(第一個特質的父類);3.FileLogger(父類);4.ShortLogger(第二個特質,由於其超類已經被構造過,跳過);5.SavingAccount。
特質不能有構造器引數,每個特質都有一個無引數的構造器,缺少構造器引數是特質與類之間唯一的技術差別,除此之外特質可以具備類的所有特性。