在類別中可使用type關鍵字來宣告型態成員,其應用之一,就是為某些形態取別名,例如:
type Text = String
def doSome(text: Text) {
println(text)
}
val text: Text = "Justin"
doSome(text) // Justin
在上例中,Text成了String的別名,之後你可以使用Text這個名稱來代替String進行宣告。會使用type來為型態取別名,通常是為了用個更簡潔的名稱。例如,在未匯入scala.collection.mutable.Set時,你程式中所使用的Set名稱所參考的物件,其實是定義在scala.Predef中:
package scala
object Predef {
...
type Map[A, B] = collection.immutable.Map[A, B]
type Set[A] = collection.immutable.Set[A]
val Map = collection.immutable.Map
val Set = collection.immutable.Set
...
}
object Predef {
...
type Map[A, B] = collection.immutable.Map[A, B]
type Set[A] = collection.immutable.Set[A]
val Map = collection.immutable.Map
val Set = collection.immutable.Set
...
}
val宣告了Map與Set分別參考至collection.immutable.Set與collection.immutable.Map物件,而type為collection.immutable.Set[A]與collection.immutable.Map[A, B]建立了別名Set[A]與Map[A, B]。
type宣告可以是抽象的,也就是暫不指定別名,例如:
trait Something {
type T // 抽象的 type 成員
val value: T
def doSome(t: T): T
}
在實作Something的類別中,再指定上例中T實際為哪個型態的別名:
class SomethingImpl extends Something {
type T = String
val value = "orz"
def doSome(t: T) = t + value
}
如果為了程式碼清楚起見,也可以在實作時使用實際型態名稱:
class SomethingImpl extends Something {
type T = String
val value = "orz"
def doSome(s: String) = s + value
}
在宣告抽象的type成員時,你可以使用 <: 或 >: 限制實際指定的型態,例如:
class Some
class SomeBody extends Some
trait Something {
type T <: Some // T 只能是 Some 或其子類別
val value: T
}
class SomethingImpl extends Something {
type T = SomeBody
val value = new SomeBody
}
如上例,T只能是Some或其子類別,否則就會編譯錯誤,如果使用 >: 的話則相反,只能是Some或其父類別。
以下為可能使用抽象type成員的情境,原先也許你設計了以下的類別:
class Food
abstract class Animal {
def eat(food: Food)
}
每個動物都會吃東西,所以你定義了一個抽象類別Animal,規範了eat()方法,你也許這麼實作:
class Fish extends Food {
override def toString = "魚"
}
class Grass extends Food {
override def toString = "草"
}
class Cat extends Animal {
def eat(food: Food) {
food match {
case fish: Fish => println("吃" + fish)
case _ => throw new IllegalArgumentException("這隻貓只吃魚")
}
}
}
class Cow extends Animal {
def eat(food: Food) {
food match {
case grass: Grass => println("吃" + grass)
case _ => throw new IllegalArgumentException("這隻牛只吃草")
}
}
}
由於eat()接受的是Food型態的物件,為了避免動物亂吃東西,你得記得在程式中自行實作比對,看看傳入的食物是不是正確的食物,所以:
val cat = new Cat
cat.eat(new Fish) // 吃魚
cat.eat(new Grass) // 丟出例外
這樣的作法基本上沒錯,但程式設計人員最好得記得要處理例外的狀況,另一方面,如果程式設計人員沒有記得要在程式中執行比對,那就會發生貓吃草的奇怪行為:
class Cat extends Animal {
def eat(fish: Food) {
println("吃" + fish)
}
}
val cat = new Cat
cat.eat(new Fish) // 吃魚
cat.eat(new Grass) // 吃草
如果這不是你所希望看到的結果,你可以考慮改為以下的設計:
abstract class Animal {
type F <: Food
def eat(f: F)
}
F是抽象type成員,必須是Food或其子類別。實作Animal時:
class Cat extends Animal {
type F = Fish
def eat(fish: Fish) {
println("吃" + fish)
}
}
class Cow extends Animal {
type F = Grass
def eat(grass: Grass) {
println("吃" + grass)
}
}
就可以指定動物實際上到底要吃什麼食物,不用執行時期比對,現在動物可以吃東西了:
val cat = new Cat
cat.eat(new Fish) // 吃魚
val cow = new Cow
cow.eat(new Grass) // 吃草
如果你亂餵東西,那編譯時期就會回報錯誤:
val cat = new Cat
cat.eat(new Grass) // 編譯錯誤,type mismatch
val cow = new Cow
cow.eat(new Fish) // 編譯錯誤,type mismatch
這樣的作法,是將執行時期的型態比對,轉為編譯時期的型態檢查,另一方面,你等於放棄eat()的執行時期多型,選擇了編譯時期多型。例如以下這個執行時期多型你就不能作了:
val animal: Animal = new Cat
animal.eat(new Fish) // 編譯錯誤,type mismatch