[英]Scala type lowerbound bug?
case class Level[B](b: B){
def printCovariant[A<:B](a: A): Unit = println(a)
def printInvariant(b: B): Unit = println(b)
def printContravariant[C>:B](c: C): Unit = println(c)
}
class First
class Second extends First
class Third extends Second
//First >: Second >: Third
object Test extends App {
val second = Level(new Second) //set B as Second
//second.printCovariant(new First) //error and reasonable
second.printCovariant(new Second)
second.printCovariant(new Third)
//second.printInvariant(new First) //error and reasonable
second.printInvariant(new Second)
second.printInvariant(new Third) //why no error?
second.printContravariant(new First)
second.printContravariant(new Second)
second.printContravariant(new Third) //why no error?
}
似乎scala的下限類型檢查有bug ...不變大小寫和反變例。
我不知道上面的代碼是否有錯誤。
始終牢記,如果將Third
擴展為Second
那么每當需要Second
,都可以提供Third
。 這稱為亞型多態性。
考慮到這一點, second.printInvariant(new Third)
編譯是很自然的。 您提供了Third
,它是Second
的子類型,因此它簽出。 這就像為蘋果提供一種獲取水果的方法一樣。
這意味着你的方法
def printCovariant[A<:B](a: A): Unit = println(a)
可以寫成:
def printCovariant(a: B): Unit = println(a)
不會丟失任何信息。 由於子類型多態性,第二個接受B及其所有子類,這與第一個相同。
第二種錯誤情況也是如此-這是子類型多態的另一種情況。 您可以通過新的Third,因為Third實際上是Second(請注意,我在子類和超類之間使用的是is-a關系,它取自面向對象的表示法)。
如果您想知道為什么我們甚至還需要上限(子類型多態性不夠嗎?),請觀察以下示例:
def foo1[A <: AnyRef](xs: A) = xs
def foo2(xs: AnyRef) = xs
val res1 = foo1("something") // res1 is a String
val res2 = foo2("something") // res2 is an Anyref
現在,我們確實觀察到了差異。 即使子類型多態允許我們在兩種情況下都傳遞String,但只有方法foo1
可以引用其參數的類型(在我們的情況下為String)。 方法foo2
會很高興地使用String,但是並不會真正知道它是String。 因此,當您想要保留類型時,上限可以派上用場(在您的情況下,您只需打印出值即可,因此您實際上並不關心類型-所有類型都有一個toString方法)。
編輯:
(更多細節,您可能已經知道了,但是為了完整起見,我將其說明)
上限比我在這里描述的更多,但是在參數化方法時,這是最常見的情況。 在對類進行參數化時,可以使用上限來描述協方差,而使用下限來描述逆方差。 例如,
class SomeClass[U] {
def someMethod(foo: Foo[_ <: U]) = ???
}
表示方法someMethod
參數foo
在類型上是協變的。 怎么樣? 好吧,通常情況下(即沒有調整方差),子類型多態性不允許我們傳遞帶有其類型參數的子類型的Foo
參數。 如果T <: U
,並不意味着Foo[T] <: Foo[U]
。 我們說Foo
在類型上是不變的。 但是我們只是對方法進行了調整,以接受使用U
或其任何子類型參數化的Foo
。 現在,這實際上是協方差。 因此,只要關注someMethod
如果某個類型T
是U
的子類型,則Foo[T]
是Foo[U]
的子類型。 太好了,我們實現了協方差。 但是請注意,我說的是“只要關注someMethod
”。 Foo
在此方法中其類型是協變的,但在其他方法中,它可能是不變的或相反的。
這種差異聲明被稱為使用地點差異,因為我們在類型使用時聲明類型的差異(在這里,它用作someMethod
的方法參數類型)。 這是Java中唯一的一種方差聲明。 使用使用地點差異時,您要小心“ 獲取量”原理 (用Google搜索)。 基本上,這個原則說我們只能從協變類中獲得東西(我們不能放進去),反之亦然(對於我們可以放進但不能得到)。 在我們的例子中,我們可以這樣演示它:
class Foo[T] { def put(t: T): Unit = println("I put some T") }
def someMethod(foo: Foo[_ <: String]) = foo.put("asd") // won't compile
def someMethod2(foo: Foo[_ >: String]) = foo.put("asd")
更籠統地說,我們只能將協變類型用作返回類型,將反變量類型用作參數類型。
現在,使用站點聲明很不錯,但是在Scala中,利用聲明站點差異(Java所沒有的東西)更為常見。 這意味着我們將在定義Foo
描述Foo
的泛型類型的變化。 我們只說class Foo[+T]
。 現在,在編寫與Foo
一起使用的方法時,我們不需要使用界限; 我們宣布Foo
在每種使用案例和每種情況下,其類型都是永久協變的。
有關Scala中差異的更多詳細信息,請隨時查看我關於此主題的博客文章 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.