您好,登錄后才能下訂單哦!
本篇內容介紹了“scala的型變是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
型變是復雜類型的子類型關系與其組件類型的子類型關系的相關性。Scala支持 泛型類 的類型參數(shù)的型變注釋,允許它們是協(xié)變的,逆變的,或在沒有使用注釋的情況下是不變的。在類型系統(tǒng)中使用型變允許我們在復雜類型之間建立直觀的連接,而缺乏型變則會限制類抽象的重用性。
class Foo[+A] // A covariant classclass Bar[-A] // A contravariant classclass Baz[A] // An invariant class
使用注釋 +A
,可以使一個泛型類的類型參數(shù) A
成為協(xié)變。對于某些類 class List[+A]
,使 A
成為協(xié)變意味著對于兩種類型 A
和 B
,如果 A
是 B
的子類型,那么 List[A]
就是 List[B]
的子類型。這允許我們使用泛型來創(chuàng)建非常有用和直觀的子類型關系。
考慮以下簡單的類結構:
abstract class Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
類型 Cat
和 Dog
都是 Animal
的子類型。Scala 標準庫有一個通用的不可變的類 sealed abstract class List[+A]
,其中類型參數(shù) A
是協(xié)變的。這意味著 List[Cat]
是 List[Animal]
,List[Dog]
也是 List[Animal]
。直觀地說,貓的列表和狗的列表都是動物的列表是合理的,你應該能夠用它們中的任何一個替換 List[Animal]
。
在下例中,方法 printAnimalNames
將接受動物列表作為參數(shù),并且逐行打印出它們的名稱。如果 List[A]
不是協(xié)變的,最后兩個方法調用將不能編譯,這將嚴重限制 printAnimalNames
方法的適用性。
object CovarianceTest extends App {
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach { animal =>
println(animal.name)
}
}
val cats: List[Cat] = List(Cat("Whiskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
printAnimalNames(dogs)
}
通過使用注釋 -A
,可以使一個泛型類的類型參數(shù) A
成為逆變。與協(xié)變類似,這會在類及其類型參數(shù)之間創(chuàng)建一個子類型關系,但其作用與協(xié)變完全相反。也就是說,對于某個類 class Writer[-A]
,使 A
逆變意味著對于兩種類型 A
和 B
,如果 A
是 B
的子類型,那么 Writer[B]
是 Writer[A]
的子類型。
考慮在下例中使用上面定義的類 Cat
,Dog
和 Animal
:
abstract class Printer[-A] { def print(value: A): Unit}
這里 Printer[A]
是一個簡單的類,用來打印出某種類型的 A
。讓我們定義一些特定的子類:
class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat] {
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}
如果 Printer[Cat]
知道如何在控制臺打印出任意 Cat
,并且 Printer[Animal]
知道如何在控制臺打印出任意 Animal
,那么 Printer[Animal]
也應該知道如何打印出 Cat
就是合理的。反向關系不適用,因為 Printer[Cat]
并不知道如何在控制臺打印出任意 Animal
。因此,如果我們愿意,我們應該能夠用 Printer[Animal]
替換 Printer[Cat]
,而使 Printer[A]
逆變允許我們做到這一點。
object ContravarianceTest extends App {
val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}
val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter
printMyCat(catPrinter)
printMyCat(animalPrinter)
}
這個程序的輸出如下:
The cat's name is: BootsThe animal's name is: Boots
默認情況下,Scala中的泛型類是不變的。這意味著它們既不是協(xié)變的也不是逆變的。在下例中,類 Container
是不變的。Container[Cat]
不是 Container[Animal]
,反之亦然。
class Container[A](value: A) { private var _value: A = value def getValue: A = _value def setValue(value: A): Unit = { _value = value }}
可能看起來一個 Container[Cat]
自然也應該是一個 Container[Animal]
,但允許一個可變的泛型類成為協(xié)變并不安全。在這個例子中,Container
是不變的非常重要。假設 Container
實際上是協(xié)變的,下面的情況可能會發(fā)生:
val catContainer: Container[Cat] = new Container(Cat("Felix"))val animalContainer: Container[Animal] = catContaineranimalContainer.setValue(Dog("Spot"))val cat: Cat = catContainer.getValue
幸運的是,編譯器在此之前就會阻止我們。
另一個可以幫助理解型變的例子是 Scala 標準庫中的 trait Function1[-T, +R]
。Function1
表示具有一個參數(shù)的函數(shù),其中第一個類型參數(shù) T
表示參數(shù)類型,第二個類型參數(shù) R
表示返回類型。Function1
在其參數(shù)類型上是逆變的,并且在其返回類型上是協(xié)變的。對于這個例子,我們將使用文字符號 A => B
來表示 Function1[A, B]
。
假設前面使用過的類似 Cat
,Dog
,Animal
的繼承關系,加上以下內容:
abstract class SmallAnimal extends Animal
case class Mouse(name: String) extends SmallAnimal
假設我們正在處理接受動物類型的函數(shù),并返回他們的食物類型。如果我們想要一個 Cat => SmallAnimal
(因為貓吃小動物),但是給它一個 Animal => Mouse
,我們的程序仍然可以工作。直觀地看,一個 Animal => Mouse
的函數(shù)仍然會接受一個 Cat
作為參數(shù),因為 Cat
即是一個 Animal
,并且這個函數(shù)返回一個 Mouse
,也是一個 SmallAnimal
。既然我們可以安全地,隱式地用后者代替前者,我們可以說 Animal => Mouse
是 Cat => SmallAnimal
的子類型。
某些與 Scala 類似的語言以不同的方式支持型變。例如,Scala 中的型變注釋與 C# 中的非常相似,在定義類抽象時添加型變注釋(聲明點型變)。但是在Java中,當類抽象被使用時(使用點型變),才會給出型變注釋。
“scala的型變是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。