您的示例與方差無關。此外,頭部和尾部也與變化無關。
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
即使List
不協變的,因爲Scala會自動推斷的Boy
和Girl
常見的超這樣的工作,也就是Kid
,並在右側的表達式的類型將是List[Kid]
,到底是什麼你需要在左邊。
以下,然而,不工作,因爲java.util.List
不是協變(這是不變的,因爲它是Java類型):
scala> import java.util.{List => JList, Arrays}
import java.util.{List=>JList, Arrays}
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> val list1 = Arrays.asList(Boy("kevin"), Boy("bob"))
list1: java.util.List[Boy] = [Boy(kevin), Boy(bob)]
scala> val list2: JList[Kid] = list1
<console>:12: error: type mismatch;
found : java.util.List[Boy]
required: java.util.List[Kid]
Note: Boy <: Kid, but Java-defined trait List is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Kid`. (SLS 3.2.10)
val list2: JList[Kid] = list1
^
Arrays.asList
方法的簽名是這樣的:
def asList[T](args: T*): java.util.List[T]
由於java.util.List[T]
不變,因此不可能將JList[Boy]
(list1
)指定爲JList[Kid]
(list2
)。有一個原因:如果可以,那麼因爲JList
是可變的,您還可以添加任何延伸Kid
(不僅是Boy
)的任何內容到同一個列表中,以突破類型安全性。
在另一方面,scala.List
將工作在完全相同的情況:
scala> val list1 = List(Boy("kevin"), Boy("bob"))
list1: List[Boy] = List(Boy(kevin), Boy(bob))
scala> val list2: List[Kid] = list1
list2: List[Kid] = List(Boy(kevin), Boy(bob))
這是因爲scala.List
在其類型參數協變。請注意,協變List
類型的工作方式就好像List[Boy]
是List[Kid]
的子類型,非常類似於您可以將所有內容都分配給類型爲Any
的變量的情況,因爲每個其他類型都是Any
的子類型。這是非常有用的類比。
逆變函數的工作方式非常類似,但在其他方向。考慮這個特質:
trait Predicate[-T] {
def apply(obj: T): Boolean
}
object Predicate {
// convenience method to convert functions to predicates
def apply[T](f: (T) => Boolean) = new Predicate[T] {
def apply(obj: T) = f(obj)
}
}
注意-
前T
參數:它是一個逆變註釋,就是Predicate[T]
被定義在它的唯一類型參數進行逆變。
回想一下,對於協變列表List[Boy]
是List[Kid]
的子類型。那麼,對於逆變斷言它在相反的方向:Predicate[Kid]
是Predicate[Boy]
一個亞型,所以你可以Predicate[Kid]
類型的值賦給Predicate[Boy]
類型的變量:
scala> val pred1: Predicate[Kid] = Predicate { kid => kid.hashCode % 2 == 0 }
pred1: Predicate[Kid] = [email protected]
scala> val pred2: Predicate[Boy] = pred1
pred2: Predicate[Boy] = [email protected]
如果Predicate[T]
不逆變,我們將不能將pred1
分配給pred2
,儘管它是完全合法和安全的:顯然,在超類型上定義的謂詞可以很容易地處理子類型。
總之,方差會影響參數化類型之間的類型兼容性。 List
是協變,這樣就可以List[Boy]
類型的值分配給List[Kid]
類型的變量(實際上,對於任何T
延伸S
,可以List[T]
類型的值分配給List[S]
類型的變量)。
在另一方面,因爲,Predicate
是逆變,可以分配給Predicate[Kid]
Predicate[Boy]
(即,用於擴展S
任何T
,可以Predicate[S]
類型的值分配給Predicate[T]
類型的變量)。
如果某個類型在其類型參數中不變,則上述任何一項都不能完成(如JList
所示)。
注意參數類型和參數之間的對應關係:
T <: S ===> List [T] <: List [S] (covariance)
T <: S ===> Predicate[S] <: Predicate[T] (contravariance)
這就是爲什麼第一個效果就是所謂的理由*合作*方差(T <: S
在左邊,在右邊
..T.. <: ..S..
)第二個是* contra *方差(左側爲T <: S
,右側爲..S.. <: ..T..
)。
是否使自己的參數化類型協變或逆變或不變取決於您的類職責。如果只有返回泛型類型的值,那麼使用協方差是有意義的。例如,List[T]
僅包含返回T
,從不接受T
作爲參數的方法,因此爲了增加表示力而使其協變是安全的。這種參數化類型可以稱爲生產者。
如果你的類只接受泛型類型作爲參數的值,而不是返回他們(酷似Predicate
以上具有單一方法def apply(obj: T): Boolean
),那麼你可以放心地讓它逆變。這樣的參數化類型可以叫做消費者
如果你的類都接受和返回泛型類型的值,即它既是生產者又是消費者,那麼你別無選擇,只能在這個泛型中保留類不變類型參數。
這個成語通常被稱爲「佩奇」(「製片人extends
,消費者super
」),因爲方差的註解是在Java編寫extends
和super
。
示例代碼根本不依賴於'List'協方差。你能否擴大你的問題/例子,使其更清晰? –
基本上我對方差感到困惑,並提出一個問題來挑起關於這個話題的答案。弗拉基米爾用他的回答幫了我很多。 –