基礎知識(續(xù))

2022-05-13 10:36 更新

apply 方法

當類或對象有一個主要用途的時候,apply 方法為你提供了一個很好的語法糖。

scala> class Foo {}
defined class Foo

scala> object FooMaker {
     |   def apply() = new Foo
     | }
defined module FooMaker

scala> val newFoo = FooMaker()
newFoo: Foo = Foo@5b83f762

scala> class Bar {
     |   def apply() = 0
     | }
defined class Bar

scala> val bar = new Bar
bar: Bar = Bar@47711479

scala> bar()
res8: Int = 0

在這里,我們實例化對象看起來像是在調用一個方法。以后會有更多介紹!

單例對象

單例對象用于持有一個類的唯一實例。通常用于工廠模式。

object Timer {
  var count = 0

  def currentCount(): Long = {
    count += 1
    count
  }
}

可以這樣使用:

scala> Timer.currentCount()
res0: Long = 1

單例對象可以和類具有相同的名稱,此時該對象也被稱為“伴生對象”。我們通常將伴生對象作為工廠使用。

下面是一個簡單的例子,可以不需要使用new來創(chuàng)建一個實例了。

class Bar(foo: String)

object Bar {
  def apply(foo: String) = new Bar(foo)
}

函數即對象

在 Scala 中,我們經常談論對象的函數式編程。這是什么意思?到底什么是函數呢?

函數是一些特質的集合。具體來說,具有一個參數的函數是 Function1 特質的一個實例。這個特征定義了 apply()語法糖,讓你調用一個對象時就像你在調用一個函數。

scala> object addOne extends Function1[Int, Int] {
     |   def apply(m: Int): Int = m + 1
     | }
defined module addOne

scala> addOne(1)
res2: Int = 2

這個 Function 特質集合下標從 0 開始一直到 22。為什么是 22?這是一個主觀的魔幻數字(magic number)。我從來沒有使用過多于 22 個參數的函數,所以這個數字似乎是合理的。

apply 語法糖有助于統(tǒng)一對象和函數式編程的二重性。你可以傳遞類,并把它們當做函數使用,而函數本質上是類的實例。

這是否意味著,當你在類中定義一個方法時,得到的實際上是一個 Function*的實例?不是的,在類中定義的方法是方法而不是函數。在 repl 中獨立定義的方法是 Function* 的實例。

類也可以擴展 Function,這些類的實例可以使用()調用。

scala> class AddOne extends Function1[Int, Int] {
     |   def apply(m: Int): Int = m + 1
     | }
defined class AddOne

scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>

scala> plusOne(1)
res0: Int = 2

可以使用更直觀快捷的 extends (Int => Int) 代替 extends Function1[Int, Int]

class AddOne extends (Int => Int) {
  def apply(m: Int): Int = m + 1
}

你可以將代碼組織在包里。

package com.twitter.example

在文件頭部定義包,會將文件中所有的代碼聲明在那個包中。

值和函數不能在類或單例對象之外定義。單例對象是組織靜態(tài)函數(static function)的有效工具。

package com.twitter.example

object colorHolder {
  val BLUE = "Blue"
  val RED = "Red"
}

現在你可以直接訪問這些成員

println("the color is: " + com.twitter.example.colorHolder.BLUE)

注意在你定義這個對象時 Scala 解釋器的返回:

scala> object colorHolder {
     |   val Blue = "Blue"
     |   val Red = "Red"
     | }
defined module colorHolder

這暗示了 Scala 的設計者是把對象作為 Scala 的模塊系統(tǒng)的一部分進行設計的。

模式匹配

這是 Scala 中最有用的部分之一。

匹配值

val times = 1

times match {
  case 1 => "one"
  case 2 => "two"
  case _ => "some other number"
}

使用守衛(wèi)進行匹配

times match {
  case i if i == 1 => "one"
  case i if i == 2 => "two"
  case _ => "some other number"
}

注意我們是怎樣將值賦給變量i的。

在最后一行指令中的_是一個通配符;它保證了我們可以處理所有的情況。

否則當傳進一個不能被匹配的數字的時候,你將獲得一個運行時錯誤。我們以后會繼續(xù)討論這個話題的。

參考 Effective Scala 對[什么時候使用模式匹配](http://twitter.github.com/effectivescala/#Functional programming-Pattern matching)和[模式匹配格式化的建議](http://twitter.github.com/effectivescala/#Formatting-Pattern matching)。 A Tour of Scala 也描述了模式匹配

匹配類型

你可以使用 match 來分別處理不同類型的值。

def bigger(o: Any): Any = {
  o match {
    case i: Int if i < 0 => i - 1
    case i: Int => i + 1
    case d: Double if d < 0.0 => d - 0.1
    case d: Double => d + 0.1
    case text: String => text + "s"
  }
}

匹配類成員

還記得我們之前的計算器嗎。

讓我們通過類型對它們進行分類。

def calcType(calc: Calculator) = calc match {
  case _ if calc.brand == "hp" && calc.model == "20B" => "financial"
  case _ if calc.brand == "hp" && calc.model == "48G" => "scientific"
  case _ if calc.brand == "hp" && calc.model == "30B" => "business"
  case _ => "unknown"
}

樣本類 Case Classes

使用樣本類可以方便得存儲和匹配類的內容。你不用 new 關鍵字就可以創(chuàng)建它們。

scala> case class Calculator(brand: String, model: String)
defined class Calculator

scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)

樣本類基于構造函數的參數,自動地實現了相等性和易讀的 toString 方法。

scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)

scala> val hp20B = Calculator("hp", "20b")
hp20B: Calculator = Calculator(hp,20b)

scala> hp20b == hp20B
res6: Boolean = true

樣本類也可以像普通類那樣擁有方法。

使用樣本類進行模式匹配

樣本類就是被設計用在模式匹配中的。讓我們簡化之前的計算器分類器的例子。

val hp20b = Calculator("hp", "20B")
val hp30b = Calculator("hp", "30B")

def calcType(calc: Calculator) = calc match {
  case Calculator("hp", "20B") => "financial"
  case Calculator("hp", "48G") => "scientific"
  case Calculator("hp", "30B") => "business"
  case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}

最后一句也可以這樣寫

  case Calculator(_, _) => "Calculator of unknown type"

或者我們完全可以不將匹配對象指定為 Calculator 類型

  case _ => "Calculator of unknown type"

或者我們也可以將匹配的值重新命名。

  case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)

異常

Scala 中的異??梢栽?try-catch-finally 語法中通過模式匹配使用。

try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
} finally {
  remoteCalculatorService.close()
}

try 也是面向表達式的

val result: Int = try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => {
    log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
    0
  }
} finally {
  remoteCalculatorService.close()
}

這并不是一個完美編程風格的展示,而只是一個例子,用來說明 try-catch-finally 和 Scala 中其他大部分事物一樣是表達式。

當一個異常被捕獲處理了,finally 塊將被調用;它不是表達式的一部分。

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號