本章要點(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)
自身類型
在第十章 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
}