在定義型態參數時,可使用 + 或 - 標註型態參數是否支援 共變性 或 逆變性,這可以使得以該類別宣告的變數與實例都具有所指定的可變性。
然而如果類別定義已固定,你無法修改類別定義,也許是該類別來自現有的Java程式庫(這也是主要運用既存型態的場合);或者是絕大多數的情況下,並不需要可變性,但偶而會遇到類似 共變性 或 逆變性 中所提及的例子,該如何是好?
例如在 共變性 中的例子:
class Node[T](val value: T, val next: Node[T])
class Fruit
class Apple extends Fruit {
override def toString = "Apple"
}
class Banana extends Fruit {
override def toString = "Banana"
}
在不改變Node類別定義的情況下,如何宣告一個變數,可以參考至Node[Apple]實例,也可以參考至Node[Banana]實例?你可以宣告既存型態(Existential type),既存型態的語法如下:
型態 forSome { 宣告區 }
「型態」中的類型資訊有些(forSome)來自「宣告區」,而宣告區中可以是抽象type或抽象val,型態或變數確實存在,但型態或變數未知。
以實際的例子來說:
val apple = new Node(new Apple, null)
val fruit1: Node[T] forSome { type T <: Fruit } = apple
以上例來說,T是某個型態的別名,真正的型態未知,但知道會是Fruit的子類型,你使用型態別名T來參數化Node的型態參數。對於以下型式的既存型態宣告:
型態[T1, T2, ...] forSome { type T1, type T2, ...}
可以使用以上的佔位字元簡寫型式:
型態[_, _, ...]
如果有上下界等限定,例如對於以下型式的既存型態宣告:
型態[T1, T2, ...] forSome { type T1 <: X, type T2 <: Y, ...}
可以使用以上的佔位字元簡寫型式:
型態[_ <: X, _ <: Y , ...]
所以上面的例子中,既存型態宣告部份可以簡寫為以下的方式:
val banana = new Node(new Banana, null)
val fruit2: Node[_ <: Fruit] = banana
所以,你可以如下定義一個show()函式,達到 共變性 中show()函式的作用:
def show(n: Node[_ <: Fruit]) {
var node: Node[_ <: Fruit] = n
do {
println(node.value)
node = node.next
} while(node != null)
}
val apple1 = new Node(new Apple, null)
val apple2 = new Node(new Apple, apple1)
val apple3 = new Node(new Apple, apple2)
val banana1 = new Node(new Banana, null)
val banana2 = new Node(new Banana, banana1)
show(apple3)
show(banana2)
再來看看 逆變性 中的例子,如果是這個定義:
trait Comparator[T] {
def compare(t1: T, t2: T): Int
}
在不改變Comparator的定義下,如果要達到逆變性宣告的作用,則可以使用以下的語法:
class Fruit(val price: Int, val weight: Int)
class Apple(override val price: Int,
override val weight: Int) extends Fruit(price, weight)
class Banana(override val price: Int,
override val weight: Int) extends Fruit(price, weight)
// 完整型式 val c1: Comparator[T] forSome { type T >: Apple } = comparator
val c1: Comparator[_ >: Apple] = comparator
// 完整型式 val c2: Comparator[T] forSome { type T >: Banana } = comparator
val c2: Comparator[_ >: Banana] = comparator
所以,在不改變原來Comparator的定義下,若要達到 逆變性 中的作用,可以改為以下的方式:
class Basket[T](things: T*) {
def sort(comparator: Comparator[_ >: T]) {
// 進行排序...
}
}
val comparator = new Comparator[Fruit] {
def compare(f1: Fruit, f2: Fruit) = f1.price - f2.price
}
val b1 = new Basket(new Apple(20, 100), new Apple(25, 150))
val b2 = new Basket(new Banana(30, 200), new Banana(25, 250))
b1.sort(comparator)
b2.sort(comparator)