Scala學習(六)---Scala物件

sunddenly發表於2015-06-29

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中都可以用物件來實現:

■ 作為存放工具函式常量的地方

■ 高效地共享單個不可變例項

■ 需要用單個例項來協調某個服務時,可參考單例模式

注意:很多人都看低單例模式。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")

如果,您認為閱讀這篇部落格讓您有些收穫,不妨點選一下右下角的【推薦】。
如果,您希望更容易地發現我的新部落格,不妨點選一下左下角的【關注我】。
如果,您對我的部落格所講述的內容有興趣,請繼續關注我的後續部落格,我是【Sunddenly】。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

相關文章