泛型(二)

解剖泛型

泛型提供了,使用一組類型定義一組新類型的機制。

在示例中,可以為Keeper定義泛型類型:

class Keeper<Animal> {}

這個定義立即定義了所有相應(yīng)的Keeper類型:


QQ20180702-125720@2x.png

你可以通過創(chuàng)建這些類型的值來驗證這些類型是否真實,并在初始化器中指定整個類型:

var aCatKeeper = Keeper<Cat>()

首先,Keeper是泛型類型的名稱。

但是你可能會說,泛型類型實際上根本不是類型。它更像是制作真正類型或具體類型的方法。這其中的一個標志是,如果你嘗試在孤立狀態(tài)下實例化它,你會得到錯誤:

var aKeeper = Keeper()  // compile-time error!

編譯器在這里報錯,因為它不知道你想要什么樣的Keeper。尖括號中的動物是類型參數(shù),它指定你所保存的動物的類型。

一旦你提供了所需的類型參數(shù)(如Keeper<Cat>),泛型Keeper就會變成一個新的具體類型。Keeper<Cat>與Keeper<Dog>不同,盡管它們是相同的泛型。這些生成的具體類型稱為泛型類型的特殊化。

為了總結(jié)機制,為了定義一個泛型類型,如Keeper<Animal>,你只需選擇泛型類型的名稱和類型參數(shù)的名稱。類型參數(shù)應(yīng)該澄清類型參數(shù)和泛型類型之間的關(guān)系。有時你會遇到像T(類型的縮寫)這樣的名稱,但是當類型參數(shù)具有明確的角色(如Animal)時,應(yīng)該避免使用這些名稱。

請注意,Keeper類型目前不存儲任何東西,甚至不使用任何類型的Animal。從本質(zhì)上講,泛型是一種系統(tǒng)定義類型集的方法。

使用類型參數(shù)

但是,通常你需要對類型參數(shù)進行處理。

假設(shè)你想要更好地表達個體。首先,類型定義要包含有標識性的屬性(如名稱)。這讓每個值都代表單個動物或飼養(yǎng)員的身份:

class Cat {
  var name: String
  init(name: String) {
    self.name = name
  }
}
class Dog {
  var name: String
  init(name: String) {
    self.name = name
  }
}
class Keeper<Animal> {
  var name: String
  init(name: String) {
    self.name = name
  }
}

你還需要跟蹤哪個飼養(yǎng)員照料哪些動物。假設(shè)每個飼養(yǎng)員早上負責(zé)一只,下午負責(zé)另一只。你可以為泛型添加上午的動物和下午的動物屬性來表達這一點。但是還需要什么屬性呢?

很明顯,如果一個飼養(yǎng)員只管理狗,那么它的屬性只能是狗。如果是貓,那就是貓。一般來說,如果它是動物的飼養(yǎng)者,那么早上和下午的動物屬性應(yīng)該是Animal這樣的類型。

要表達這一點,你只需使用類型參數(shù),也就是之前用于區(qū)分你是哪種飼養(yǎng)員的屬性:

class Keeper<Animal> {
  var name: String
  var morningCare: Animal
  var afternoonCare: Animal
  init(name: String, morningCare: Animal, afternoonCare: Animal) {
    self.name = name
    self.morningCare = morningCare
    self.afternoonCare = afternoonCare
  } 
}

通過在上面的泛型定義中使用Animal,你可以表達出早晨和下午的動物一定是管理員最了解的動物。

正如函數(shù)參數(shù)成為函數(shù)體中要使用的常量一樣,你可以在整個類型定義中使用類型參數(shù),比如Animal。在Keeper<Animal>的定義中,你可以在任何地方使用類型參數(shù),用于存儲屬性以及計算屬性、方法簽名或嵌套類型。

現(xiàn)在,當你實例化一個Keeper時,Swift將確保編譯時,上午和下午的類型是相同的:

let jason = Keeper(name: "Jason", morningCare: Cat(name: "Whiskers"),afternoonCare: Cat(name: "Sleepy"))

在這里,飼養(yǎng)員Jason在早上管理著Whiskers,而在下午管理Sleepy。jason的類型是Keeper <Cat>。注意,你不必為類型參數(shù)指定值。因為你使用了Cat實例作為morningCare和afternoonCare的值,swift知道jason的類型應(yīng)該是Keeper <Cat>。

類型約束

在你對Keeper的定義中,作為標識符的Animal作為類型參數(shù)。

這就像func feed(cat: cat) {/ * open can等簡單函數(shù)中的參數(shù)cat。* / }。但在定義函數(shù)時,不能簡單地將任何參數(shù)傳遞給函數(shù)。只能傳遞類型為Cat的值。

目前,你可以使用任何類型表示Animal,甚至一些荒謬的類型,如字符串或Int。

這不是好的。你想要的是類似于函數(shù)的東西,你可以限制允許填充類型參數(shù)的類型。在Swift中,你可以使用類型約束。

有兩種約束。最簡單的類型約束是這樣的:

class Keeper<Animal: Pet> {
   /* definition body as before */
}

標識符Pet必須是命名類型或協(xié)議。在類型參數(shù)括號“Animal: Pet”中的新表達式指定了這樣的要求:無論提供給Animal的類型是什么,都必須滿足作為Pet的子類或?qū)崿F(xiàn)Pet協(xié)議的要求。

例如,你可以使用上面修改過的Keeper定義來執(zhí)行這些限制,還可以重新定義Cat和其他動物,以便使用擴展實現(xiàn)Pet或追溯模型遵從協(xié)議。

protocol Pet {
  var name: String { get }  // all pets respond to a name
}
extension Cat: Pet {}
extension Dog: Pet {}

因為Cat和Dog已經(jīng)實現(xiàn)了一個name存儲屬性。接下來,創(chuàng)建
一些貓和狗的數(shù)組:

let cats = ["Miss Gray", "Whiskers"].map { Cat(name: $0) }
let dogs = ["Sparky", "Rusty", "Astro"].map { Dog(name: $0) }
let pets: [Pet] = [Cat(name: "Mittens"), Dog(name: "Yeller")]

注意,cats、dogs和pets都有不同的類型。如果你點擊每一個,你可以看到類型是[Cat], [Dog], [Pet]

cats和dogs的數(shù)組是同質(zhì)的,因為元素具有相同的類型。pets是異質(zhì)的,因為它是dog和car類型的混合體。

通過限制類型,你可以對其進行操作。例如,既然你現(xiàn)在知道Pet有name,你就可以寫一套適合Pet的通用功能。

寫四種不同參數(shù)的herd函數(shù):

// 1
func herd(_ pets: [Pet]) {
  pets.forEach {
    print("Come \($0.name)!")
  }
}
// 2
func herd<Animal: Pet>(_ pets: [Animal]) {
  pets.forEach {
    print("Here \($0.name)!")
  }
}
// 3
func herd<Animal: Dog>(_ dogs: [Animal]) {
  dogs.forEach {
    print("Here \($0.name)! Come here!")
  }
}

1 此方法可以處理將Dog和Cat元素混合在一起的Pet類型數(shù)組。
2 此方法處理任何類型的Pet數(shù)組,但它們都需要是單一類型。函數(shù)名后面的尖括號允許你指定要在函數(shù)中使用的泛型類型。
3 只處理Dog或Dog的子類。

函數(shù)調(diào)用會找到最適合的函數(shù)。所以:

herd(pets) // Calls 1
herd(cats) // Calls 2
herd(dogs) // Calls3

第二種類型約束涉及明確表示類型參數(shù)或其關(guān)聯(lián)類型必須等于另一個參數(shù)或其一致類型之一。通過組合許多類型參數(shù)和相關(guān)類型的需求集,可以在泛型類型之上定義復(fù)雜的關(guān)系。

為了演示這一點,假設(shè)你想為cat數(shù)組添加一個擴展,并添加meow()。元素是數(shù)組的關(guān)聯(lián)類型。你可以要求該元素為類型(或子類型)Cat:

extension Array where Element: Cat {
  func meow() {
    forEach { print("\($0.name) says meow!") }
  }
}

調(diào)用meow方法

cats.meow()

但是dog元素的數(shù)組就不能使用

dogs.meow() // error: 'Dog' is not a subtype of 'Cat'

這些錯誤在這里看起來很明顯,但是類型約束可以防止你在編譯時犯一些小的錯誤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 通配符 1.數(shù)組具有協(xié)變性:可以向?qū)С鲱愋偷臄?shù)組賦予基類型的數(shù)組引用: 上面的代碼不會出現(xiàn)編譯問題,因為Apple...
    zpauly閱讀 741評論 0 0
  • 1、泛型基本概念 1.1、由來 泛型是JDK 1.5的一項新特性,在Java語言處于還沒有出現(xiàn)泛型的版本時,只能通...
    聶叼叼閱讀 254評論 0 1
  • 定義 逆變與協(xié)變用來描述類型轉(zhuǎn)換(type transformation)后的繼承關(guān)系,其定義:如果A、B表示類型...
    開發(fā)者小王閱讀 26,006評論 4 61
  • 做為主婦我不善理財,這從每個月的家庭財政赤字上足以證明。每月發(fā)餉,先生如數(shù)交到我手中,我便什么也不干,盤坐在地板上...
    薇言大義閱讀 1,113評論 100 45
  • 長途旅游回家,剛剛放下行李,人已經(jīng)摔倒在床上了。雖然已是夏天,尚未鋪上席子的床鋪依然柔軟馨香,讓人深陷其中。...
    花姑娘呀嘿閱讀 455評論 0 0

友情鏈接更多精彩內(nèi)容