Scala 解惑篇
關於new
在Scala中有這樣幾種情況,不需要new關鍵字就可以得到對象的實例。
object
object Sample{n}nval instance = Samplen
使用object去標註的類,就是一個單實例的。在scala語言層面上是不需要new的。
case class
case class Sample(x:Int,y:Int){n}nval s = Sample(1,2)n
伴生對象
object Sample{ndef apply() = new Samplen}nclass Sample{n}nval s = Samplen
伴生對象這種方式獲取實例的情況十分普遍。幾乎所有的Scala類都提供了相應的伴生對象的實現。
val L1 = List(1, 2, 3)nval i = BigDecimal("123.789")nval d = Documentn
那麼要解釋伴生對象這個情況,就得提及一個方法 apply(注入) 。apply在數學和計算機科學中,是一個函數,它將函數應用到參數。 用程序員熟悉的話說,就是Java中的工廠模式。 使用工廠方法 apply轉換一組參數,創建一個關聯類的一個新實例。(case class 實際上是scala編譯器會自動的為它生成伴生對象並實現apply方法)
object Sample {n def apply(x: Int) = new Sample(x)n}nnclass Sample(x: Int) {n}nnval x = Sample(2) //equivalent to Sample.apply(4)n
關於_
剛剛接觸scala的朋友經常會被"_"符號搞得暈頭轉向。實際上"_"出現在不同的地方含義是有一定區別的。
在package中
import scala.math._ nimport java.util.{ArrayList => _, _}n
類似Java中包定義中的*,是一個通配符。
在參數類型中
def sum(x:Int*): Int ={nx.reduce(_+_)n}nval s = sum(1 to 3:_*)n//output: 6n
_*作為一個整體,告訴編譯器你希望將某個參數當作參數序列處理!例如val s = sum(1 to 3:_*)就是將1 to 3當作參數序列處理。PS:*和Java中的可變長參數含義相同。
在泛型中
case class A[K[_], T](a: K[T])nndef foo(l: List[Option[_]]) = {}n
代表一個運行期存在的類型。
在模式匹配
Some(5) match { case Some(_) => println("Yes") } //output: yesnSome("Hello") match { case Some(_) => println("Yes") } //output: yesn
代表無論是什麼值都可以匹配。
在成員變數
class Sample{nvar s:String = _n}n
初始化默認值。相當於Java中的 String str = null。
在函數
val nums = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)nnnums map (_ + 2) //equivalent to nums map (x=>x+2)nnums reduce (_ + _) //equivalent to nums reduce ((n,n1)=>n+n1)n
作為佔位符使用
在元組
val T1 = (1,"one","一")nT1._1 //output: 1nT1._2 //output: onenT1._3 //output: 一n
關於Option
Java 開發者一般都知道 NullPointerException, 通常這是由於某個方法返回了 null ,但這並不是開發者所希望發生的,代碼也不好去處理這種異常,經常會導致程序崩潰。Scala 試圖擺脫 NullPointerException這個問題,並提供自己的類型用來表示一個值是可選的(有值或無值), 這就是 Option[A] 特質。
Option[A] 是一個類型為 A 的可選值的容器: 如果值存在, Option[A] 就是一個 Some[A] ,如果不存在, Option[A] 就是對象 None 。
在類型層面上指出一個值是否存在,使用你的代碼的開發者(也包括你自己)就會被編譯器強制去處理這種可能性, 而不能依賴值存在的偶然性。
def getOption(i:Int):Option[Int]={nif(i>0) Some(1) else Nonen}ngetOption(0) match {ncase Some(x) =>print _ncase None => n}n
Scala在很多的集合類中,都提供了這種特性。
val M1 = Map("one" -> 1, "two" -> 1)nM1.get("one") // output: Some(1)nM1.get("three") //output: Nonen
關於.語法
Scala即支持 "." 語法去調用方法,也支持空格然後註明調用的方法這種形式。
object Sample {n def sayHello(name:String): String = {n s"Hello $name"n }n}nSample.sayHello("Stanley")nSample sayHello "Stanley"n
上面代碼範例中對於,sayHello的調用是等價的。最後一行代碼節省了 "." 和 "()" 符號,這種語法設計在符合了Scala一貫精簡的作風外,也更接近於自然語言。像之前集合章節中的代碼範例中,我們為集合增加一個元素就用 += 就可以了。看起來就是一個自然的數學公式,其本質上不過是List的類定義裡面有一個名字叫 "+=" 的成員方法。
val L1 = mutable.MutableList(1,2,3)nL1 += 4nL1.+=(4) //Equivalent to L1 += 4n
關於擴展性
Scala語言的名稱來源於"Scalable"(可擴展的)。之所以這樣命名,是因為它老斯基設計成可以隨著開發者的需求而擴展。為了更好的解釋這種擴展性。我們舉一個例子
試想一下,當我們想給一個普通的List[Int] 增加一個求方差的能力。參考以下代碼範例,首先聲明了一個可以求方差的class,然後提供了一個隱式轉換的方法。最後,List的實例L1和L2就擁有了求方差的能力。
class EnhanceList(val source: List[Int]) {ndef variance(): Double = {nval avg = this.source.reduce(_ + _) / source.sizenval vari = this.source.map(x => (x - avg) * (x - avg)).reduce(_ + _) / source.sizen varin }n}nnimplicit def list2EnhanceList(l: List[Int]) = new EnhanceList(l)nnval L1 = List(50, 100, 100, 60, 50)nval L2 = List(73, 70, 75, 72, 70)n//Increase variance for L1,L2nL1 variance;nL2 variancen
我們注意最後的兩行代碼,是不是像List突然增加了求方差的能力呢?當然不是,而是由於Scala編譯器強大的類型系統,把L1和L2利用隱式轉換成了EnhanceList類。可是,看起來卻那麼自然的賦予了List求方差的能力。
關於yield
yield關鍵字是可以用來收集結果的表達式。它可以記住每次迭代的值,並可以根據條件決定是否保留。
for {n i <- (1 until 3)n j <- (3 until 5)n if((j%i)==0)n} yield (i, j)n//output: Vector((1,3), (1,4), (2,4))n
關於None ,Nil ,Null ,Nothing
Scala中有幾個表達空的類型,容易讓初學者混淆。
前文曾經提到過,None是Option的子類型。為了避免令人討厭的NullPointException異常,Scala設計了一個Option類型。Option類型的返回值只有兩個結果Some(x) 和 None。
def doOption(i:Int):Option[Int]={nif(i > 10) Some(i) else Nonen}nndoOption(9) match {ncase Some(x) => xncase None => n}n
Null是所有AnyRef的子類,在scala的類型系統中,AnyRef是Any的子類,同時Any子類的還有AnyVal。對應java值類型的所有類型都是AnyVal的子類。所以Null可以賦值給所有的引用類型(AnyRef),不能賦值給值類型,這個java的語義是相同的。 null是Null的唯一對象。
Nothing是所有類型的子類,也是Null的子類。Nothing沒有對象,但是可以用來定義類型。e.g.,如果一個方法拋出異常,則異常的返回值類型就是Nothing
def get(index:Int):Int = {nif(index < 0) throw new Exception()nelse indexn}nget(-1)n
如果x < 0,拋出異常,返回值的類型為Nothing,Nothing也是Int的子類,所以,if表達式的返回類型為Int,get方法的返回值類型也為Int。
Nil是一個空的List,定義為List[Nothing],根據List的定義List[+A],所有Nil是所有List[T]的子類。
val L1 = 1 :: 2 :: 3 :: Niln//output: List(1, 2, 3)n
上面的L1定義如果沒有Nil,Scala類型系統無法猜出L1的類型。那麼 成員方法 :: (把元素添加到List方法)就會出現找不到方法的編譯錯誤。
推薦閱讀: