Scala練習六Scala物件
Scala中的物件 |
摘要:
在本篇中,你將會學到何時使用Scala的object語法結構。在你需要某個類的單個例項時,或者想為其他值或函式找一個可以掛靠的地方時,你就會用到它。本篇的要點包括:
1. 用物件作為單例或存放工具方法
2. 類可以擁有—個同名的伴生物件
3. 物件可以擴充套件類或特質
4. 物件的apply方法通常用來構造伴生類的新例項
5. 如果不想顯式定義main方法,可以用擴充套件App特質的物件
6. 你可以通過擴充套件Enumeration物件來實現列舉
單例物件 |
Scala沒有靜態方法或靜態欄位,你可以用object這個語法結構來達到同樣目的。物件定義了某個類的單個例項,包含了你想要的特性。例如:
object Accounts {
private var lastNumber=0
def newUniqueNumber() = { lastNumber+=1, lastNumber}
}
當你在應用程式中需要一個新的唯一賬號時,呼叫Accounts.newUniqueNumber()即可。物件的構造器在該物件第一次被使用時呼叫。在本例中,Accounts的構造器在Accounts.newUniqueNumber()的首次呼叫時執行。如果一個物件從未被使用,那麼其構造器也不會被執行。
物件本質上可以擁有類的所有特性,它甚至可以擴充套件其他類或特質。只有一個例外:你不能提供構造器引數。對於任何你在Java或C++中會使用單例物件的地方
■ 作為存放工具函式或常量的地方
■ 高效地共享單個不可變例項
■ 需要用單個例項來協調某個服務時,可參考單例模式
注意:很多人都看低單例模式。Scala提供的是工具,可以做出好的設計,也可以做出糟糕的設計,你需要做出自己的判斷。
伴生物件 |
在Java或C++中,你通常會用到既有例項方法又有靜態方法的類。在Scala中,你可以通過類和與類同名的"伴生"物件來達到同樣的目的。例如:
class Account {
val id=Account.newUniqueNumber()
private var balance =0
def deposit (amount: Double) { balance+=amount }
}
object Account{ // 伴生物件
private var lastNumber=0
private def newUniqueNumber() = { lastNumber+=1;lstNumber }
}
類和它的伴生物件可以相互訪問私有特性。它們必須存在於同一個原始檔中。這說明了類的伴生物件可以被訪問,但並不在作用域當中。舉例來說,Account類必須通過Account.newUniqueNumber()而不是直接用newUniqueNumber()來呼叫伴生物件的方法。
擴充套件類或特質的物件 |
一個object可以擴充套件類以及一個或多個特質,其結果是一個擴充套件了指定類以及特質的類的物件,同時擁有在物件定義中給出的所有特性。一個有用的使用場景是給出可被共享的預設物件。舉例來說,考慮在程式中引入一個可撤銷動作的類:
abstract class UndoableAction (val description: String) {
def undo() : Unit
def redo() : Unit
}
預設情況下可以是"什麼都不做"。當然了,對於這個行為我們只需要一個例項即可:
object DoNothingAction extends UndoableAction("Do nothing") {
override def undo () {}
override def redo () {}
}
DoNothingAction物件可以被所有需要這個預設行為的地方共用。
val actions=Map( "open" -> DoNothingAction,"save" -> DoNothingAction,…) // 開啟和儲存功能尚未實
apply方法 |
apply含義
我們通常會定義和使用物件的apply方法。當遇到如下形式的表示式時,apply方法就會被呼叫:
Object(引數1,…,引數N)
通常,這樣—個apply方法返回的是伴生類的物件。舉例來說,Array物件定義了apply方法,讓我們可以用下面這樣的表示式來建立陣列:
Array("Mary", "had", "a", "little", "lamb")
為什麼不用構造器呢?對於巢狀表示式而言,省去new關鍵字會方便很多,例如:
Array (Array (1, 7),Array (2, 9))
注意:Array(100)和new Array(100)很容易搞混。前一個表示式呼叫的是apply(100),輸出一個單元素整數100的Array[Int]。而第二個表示式呼叫的是構造器this(100),結果是Array[Nothing],包含了100個null元素。
apply示例
這裡有一個定義apply方法的示例:
class Account private (val id: Int, initialBalance: Double) {
private var balance=initiaIBalance
………
}
object Account { //伴生物件
def apply (initialBalance: Double) =
new Account (newUniqueNumber(), initialBalance)
}
這樣一來你就可以用如下程式碼來構造賬號了:
val acct = Account (1000.0)
應用程式物件 |
main方法
每個Scala程式都必須從一個物件的main方法開始,這個方法的型別為Array[String]=> Unit:
object Hello{
def main (args: Array[String]) {
println("Hello, World! ")
}
}
擴充套件App特質
除了每次都提供自己的main方法外,你也可以擴充套件App特質,然後將程式程式碼放人構造器方法體內:
object Hello extends App{
println("Hello, World! ")
}
命令列引數
如果你需要命令列引數,則可以通過args屬性得到:
object Hello extends App{
if (args.length > 0)
println("Hello, "+args (0))
else
println("Hello, World! ")
}
時間特質
如果你在呼叫該應用程式時設定了scala.time選項的話,程式退出時會顯示逝去的時間
scalac Hello.scala
scala -Dscala.time Hello Fred
Hello, Fred
[total 4ms]
所有這些涉及一些小小的魔法。App特質擴充套件自另一個特質Delayedlnit,編譯器對該特質有特殊處理。所有帶有該特質的類,其初始化方法都會被挪到delayedlnit方法中。App特質的main方法捕獲到命令列引數,呼叫delayedlnit方法,並且還可以根據要求打印出逝去的時間。在較早版本的Scala有一個Application特質來達到同樣的目的。那個特質是在靜態初始化方法中執行程式動作,並不被即時編譯器優化,因此應儘量使用新的App特質。
列舉 |
列舉定義初始化
和Java或C++不同,Scala並沒有列舉型別。不過,標準類庫提供了一個Enumeration助手類,可以用於產出列舉。定義一個擴充套件Enumeration類的物件並以Value方法呼叫初始化列舉中的所有可選值。例如:
object TrafficLightColor extends Enumeration (
val Red, Yellow,Green=Value
}
在這裡我們定義了三個欄位:Red、Yellow和Green,然後用Value呼叫將它們初始化。這是如下程式碼的簡寫:
val Red = Value
val Yellow = Value
val Green = Value
每次呼叫Value方法都返回內部類的新例項,該內部類也叫做Value。或者,你也可以向Value方法傳人ID、名稱,或兩個引數都傳:
val Red = Value (0, "Stop")
val Yellow = Value(10) // 名稱為"Yellow"
val Green = Value("Go") // ID為11
如果不指定,則ID在將前一個列舉值基礎上加一,從零開始,預設名稱為欄位名。
列舉引用
定義完成後,你就可以用TrafficLightColor.Red、TrafficLightColor.Yellow等來引用列舉值了。如果這些變得冗長煩瑣,則可以用如下語句直接引入列舉值:
import TrafficLightColor._
需要注意的是:列舉的型別是TrafficLightColor.Value而不是TrafficLightColor,後者是握有這些值的物件。有人推薦增加一個類型別名:
object TrafficLightColor extends Enumeration {
TrafficLightColor = Value
val Red, Yellow, Green=Value
}
現在列舉的型別變成了TraffcLightColor.TrafficLightColor,但僅當你使用import語句時這樣做才顯得有意義。例如:
import TrafficLightColor._
def doWhat (color : TrafficLightColor) = {
if (color==Red) "stop"
else if (color==Yellow) "hurry up"
else "go"
}
訪問列舉
列舉值的ID可通過id方法返回,名稱通過toString方法返回。對TrafficLightColor.values的呼叫輸出所有列舉值的集:
for(c <- TrafficLightColor.values)
println (c.id+":"+c)
最後,你可以通過列舉的ID或名稱來進行查詢定位,以下兩段程式碼都輸出TrafficLightColor.Red物件:
TrafficLightColor (0) // 將呼叫Enumeration.apply
TrafficLightColor.withName( "Red")