快學(xué)Scala第18章----高級(jí)類型

本章要點(diǎn)

  • 單例類型可用于方法串接和帶對(duì)象參數(shù)的方法。
  • 類型投影對(duì)所有外部類型的對(duì)象都包含了其內(nèi)部類的實(shí)例毯焕。
  • 類型別名給類型指定一個(gè)短小的名稱衍腥。
  • 結(jié)構(gòu)類型等效于“鴨子類型”。
  • 存在類型為泛型類型的通配參數(shù)提供了統(tǒng)一形式纳猫。
  • 使用自身類型來表明某特質(zhì)對(duì)混入它的類或?qū)ο蟮念愋鸵蟆?/li>
  • “蛋糕模式”用自身類型來實(shí)例依賴注入婆咸。
  • 抽象類型必須在子類中被具體化。
  • 高等類型帶有本身為參數(shù)化類型的類型參數(shù)芜辕。

單例類型

給定任何引用v尚骄,你可以得到類型v.type,它有兩個(gè)可能的值: v 和 null 物遇。例如我們想讓方法返回this,從而實(shí)現(xiàn)方法的串接調(diào)用:

class Document {
  def setTitle(title: String) = {...; this}
  def setAuthor(author: String) = {...; this}
  ...
}

// 級(jí)聯(lián)使用
val article = new Document
article.setTitle("Whatever Floats Your Boat").setAuthor("Cay Horstmann")

不過憾儒,要是還有子類询兴,問題就來了:

class Book extends Document {
  def addChapter(chapter: String) = {...; this}
  ...
}

val book = new Book()
book.setTitle("Scala for the Impatient").addChapter("chapter1")  // error

由于setTitle返回的是this, Scala將返回類型推斷為Document起趾。但Document并沒有addChapter方法诗舰。
解決方法是聲明setTitle的返回類型為this.type :

def setTitle(title: String): this.type = {...; this}

這樣book.setTitle("...")返回類型就是book.type。
如果你想定義一個(gè)接受object實(shí)例作為參數(shù)的方法训裆,你也可以使用單例類型眶根。例如

object Title

class Document {
  private var useNextArgAs: Any = null
  def set(obj: Title.type): this.type = {useNextArgAs = obj; this}
  def to(arg: String) = if (useNextArgAs == Title) title = arg; else ...
  ...
}

class Book extends Document {}
book.set(Title).to("Scala for the Impatient")

注意Title.type參數(shù)蜀铲,你不能用:

def set(obj: Title) ...  // error, Title代指的是單例對(duì)象,而不是類型

類型投影

在第5章的嵌套類中已經(jīng)有過例子属百。http://blog.csdn.net/dwb1015/article/details/51706746

路徑

形如: com.horstmann.impatient.chatter.Member 這樣的表達(dá)式稱之為路徑记劝。在最后的類型前,路徑的所有組成部分都必須是“穩(wěn)定的”族扰,也就是說它必須指定到單個(gè)厌丑、有窮的范圍。組成的部分必須是以下當(dāng)中的一種:

  • 對(duì)象
  • val
  • this渔呵、super怒竿、super[S]、C.this扩氢、C.super或C.super[S]
    路徑的組成部分不能是類耕驰。也不能是var。 例如:
val aly = new Network.Member  // error Network是類

var chatter = new Network
val fred = new chatter.Member   // error  chatter不穩(wěn)定

類型別名

對(duì)于復(fù)雜類型录豺,你可以用type關(guān)鍵字創(chuàng)建一個(gè)簡單的別名朦肘,這和C/C++的typedef相同。

class Book {
  import scala.collection.mutable._
  type Index = HashMap[String, (Int, Int)]
}

類型別名必須被嵌套在類或?qū)ο笾泄臁K荒艹霈F(xiàn)在Scala文件的頂層厚骗。


結(jié)構(gòu)類型

所謂的“結(jié)構(gòu)類型”指的是一組關(guān)于抽象方法、字段和類型的規(guī)格說明兢哭,這些抽象方法领舰、字段和類型是滿足該規(guī)格的類型必須具備的。

def appendLines(target: { def append(str: String): Any }, lines: Iterable[String]) {
  for (l <- lines) { target.append(l); target.append("\n") }
}

你可以對(duì)任何具備append方法的類的實(shí)例調(diào)用appendLines方法迟螺。這比定義一個(gè)有appendLines方法的特質(zhì)更為靈活冲秽,因?yàn)槟悴荒芸偸悄軌驅(qū)⒃撎刭|(zhì)添加到使用的類上。
但是類型結(jié)構(gòu)背后使用的是反射矩父,而反射調(diào)用的開銷比較大锉桑。因此,你應(yīng)該只在需要抓住那些無法共享一個(gè)特質(zhì)的類的共通行為的時(shí)候才使用結(jié)構(gòu)類型窍株。


復(fù)合類型

復(fù)合類型的定義形式如下:

T1 with T2 with T3 ...

在這里民轴,要想成為該復(fù)合類型的實(shí)例,某個(gè)值必須滿足每一個(gè)類型的要求才行球订。例如:

val image = new ArrayBuffer[java.awt.Shape with java.io.Serializable]

val rect = new Ractangle(5, 10, 20, 30)
image += rect   // OK , Rectangle是Serializable的
image += new Area(rect)  // error, Area是Shape但不是Serializable的

你可以把結(jié)構(gòu)類型的聲明添加到簡單類型或復(fù)合類型后裸。例如:

Shape with Serializable { def contains(p: Point): Boolean}

該類型必須既是一個(gè)Shape的子類型也是Serializable的子類型,并且還必須有一個(gè)帶Point參數(shù)的contains方法冒滩。

從技術(shù)上講微驶,

// 結(jié)構(gòu)類型
{ def append(str: String): Any }
// 是如下代碼的簡寫
AnyRef { def append(str: String): Any }

// 復(fù)合類型
Shape with Serializable 
// 是如下代碼的簡寫
Shape with Serializable  {}

中置類型

中置類型是一個(gè)帶有兩個(gè)類型參數(shù)的類型,以“中置”語法表示,類型名稱寫在兩個(gè)類型參數(shù)之間因苹。例如:

String Map Int
// 而不是
Map[String, Int]

存在類型

存在類型被加入Scala是為了與Java的類型通配符兼容苟耻。存在類型的定義是在類型表達(dá)式之后跟上forSome{...},花括號(hào)中包含了type和val聲明扶檐。例如:

Array[T] forSome {type T <: JComponent}
// 這與類型通配符效果相同
Array[_ <: JComponent]

Scala的類型通配符只不過是存在類型的語法糖凶杖。例如:

Array[_]
// 等同于
Array[T] forSome { type T }

Map[_, _]
// 等同于
Map[T, U] forSome { type T; type U }

你也可以在forSome代碼塊中使用val聲明,因?yàn)関al可以有自己的嵌套類型蘸秘。例如:

n.Member forSome { val n: Network}

Scala類型系統(tǒng)

type.png

自身類型

在第十章 http://blog.csdn.net/dwb1015/article/details/51761510 已經(jīng)有過介紹官卡。
**注意: **自身類型不會(huì)自動(dòng)繼承, 你需要重復(fù)自身類型的聲明:

trait ManagedException extends LoggedException {
  this: Exception => 
     ...
}

依賴注入

在Scala中醋虏,你可以通過特質(zhì)和自身類型達(dá)到一個(gè)簡單的依賴注入的效果寻咒。

trait Logger {
  def log(msg: String) 
}

class ConsoleLogger(str: String) extends Logger {
  def log(msg: String) = {
    println("Console: " + msg)
  }
}

class FileLogger(str: String) extends Logger {
  def log(msg: String) = {
    println("File: " + msg)
  }
}

// 用戶認(rèn)證特質(zhì)有一個(gè)對(duì)日志功能的依賴
trait Auth {
  this: Logger =>
    def login(id: String, password: String): Boolean
}

// 應(yīng)用邏輯有賴于上面兩個(gè)特質(zhì)
trait App {
  this: Logger with Auth =>
    ...
}

// 組裝應(yīng)用
object MyApp extends App with FileLogger("test.log") with MockAuth("users.txt")

像這樣使用特質(zhì)的組合有些別扭。畢竟颈嚼,一個(gè)應(yīng)用程序并非是認(rèn)證器和文件日志器的合體毛秘。更自然的表示方式可能是通過實(shí)例變量來表示組件。
蛋糕模式給出了更好的設(shè)計(jì)阻课。在這個(gè)模式當(dāng)中叫挟,你對(duì)每個(gè)服務(wù)都提供一個(gè)組件特質(zhì),該特質(zhì)包含:

  • 任何所依賴的組件限煞,以自身類型表述抹恳。
  • 描述服務(wù)接口的特質(zhì)。
  • 一個(gè)抽象的val署驻,該val將被初始化成服務(wù)的一個(gè)實(shí)例奋献。
  • 可以有選擇性的包含服務(wù)接口的實(shí)現(xiàn)。
trait LoggerComponent {
  trait Logger { ... }
  val logger: Logger
  class FileLogger(file: String) extends Logger { ... }
  ...
}

trait AuthComponent {
  this: LoggerComponent =>    // 讓我們可以訪問日志器
  
  trait Auth { ... }
  val auth: Auth
  class MockAuth(file: String) extends Auth { ... }
  ...
}

// 使用組件
object AppComponents extends  LoggerComponent with AuthComponent {
  val logger = new FileLogger("test.log")
  val auth = new MockAuth("users.txt")
}

抽象類型

類或特質(zhì)可以定義一個(gè)在子類中被具體化的抽象類型旺上。例如:

trait Reader {
  type Contents
  def read(fileName: String): Contents
}

class StringReader extends Reader {
  type Contents = String
  def read(fileName: String) = Source.fromFile(filename, "UTF-8").mkString
}

class ImageReader extends Reader {
  type Contents = BufferedImage
  def read(fileName: String) = ImageIO.read(new File(fileName))
}

同樣的效果可以通過類型參數(shù)來實(shí)現(xiàn):

trait Reader[C] {
  def read(fileName: String): C
}

class StringReader extends Reader[String] {
  def read(fileName: String) = Source.fromFile(filename, "UTF-8").mkString
}

class ImageReader extends Reader[BufferedImage] {
  def read(fileName: String) = ImageIO.read(new File(fileName))
}

那種方式更好呢瓶蚂?Scala經(jīng)驗(yàn)法則:

  • 如果類型是在類被實(shí)例化時(shí)給出,則使用類型參數(shù)宣吱。
  • 如果類型是在子類中給出窃这,則使用抽象類型。

抽象類型可以有類型界定征候,就和參數(shù)類型一樣杭攻。例如:

trait Listener {
  type Event <: java.util.EventObject
  ...
}

// 子類必須提供一個(gè)兼容的類型
trait ActionListener extends Listener {
  type Event = java.awt.event.ActionEvent
}

家族多態(tài)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市疤坝,隨后出現(xiàn)的幾起案子兆解,更是在濱河造成了極大的恐慌,老刑警劉巖卒煞,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痪宰,死亡現(xiàn)場(chǎng)離奇詭異叼架,居然都是意外死亡畔裕,警方通過查閱死者的電腦和手機(jī)衣撬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扮饶,“玉大人具练,你說我怎么就攤上這事√鹞蓿” “怎么了扛点?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岂丘。 經(jīng)常有香客問我陵究,道長,這世上最難降的妖魔是什么奥帘? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任铜邮,我火速辦了婚禮,結(jié)果婚禮上寨蹋,老公的妹妹穿的比我還像新娘松蒜。我一直安慰自己,他們只是感情好已旧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布秸苗。 她就那樣靜靜地躺著,像睡著了一般运褪。 火紅的嫁衣襯著肌膚如雪惊楼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天吐句,我揣著相機(jī)與錄音胁后,去河邊找鬼。 笑死嗦枢,一個(gè)胖子當(dāng)著我的面吹牛攀芯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播文虏,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼侣诺,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了氧秘?” 一聲冷哼從身側(cè)響起年鸳,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丸相,沒想到半個(gè)月后搔确,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年膳算,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了座硕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涕蜂,死狀恐怖华匾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情机隙,我是刑警寧澤蜘拉,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站有鹿,受9級(jí)特大地震影響旭旭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葱跋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一您机、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧年局,春花似錦际看、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至僵朗,卻和暖如春赖欣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背验庙。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工顶吮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粪薛。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓悴了,卻偏偏與公主長得像,于是被迫代替她去往敵國和親违寿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子湃交,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容