泛型(二)

解剖泛型

泛型提供了,使用一组类型定义一组新类型的机制。

在示例中,可以为Keeper定义泛型类型:

class Keeper<Animal> {}

这个定义立即定义了所有相应的Keeper类型:


QQ20180702-125720@2x.png

你可以通过创建这些类型的值来验证这些类型是否真实,并在初始化器中指定整个类型:

var aCatKeeper = Keeper<Cat>()

首先,Keeper是泛型类型的名称。

但是你可能会说,泛型类型实际上根本不是类型。它更像是制作真正类型或具体类型的方法。这其中的一个标志是,如果你尝试在孤立状态下实例化它,你会得到错误:

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

编译器在这里报错,因为它不知道你想要什么样的Keeper。尖括号中的动物是类型参数,它指定你所保存的动物的类型。

一旦你提供了所需的类型参数(如Keeper<Cat>),泛型Keeper就会变成一个新的具体类型。Keeper<Cat>与Keeper<Dog>不同,尽管它们是相同的泛型。这些生成的具体类型称为泛型类型的特殊化。

为了总结机制,为了定义一个泛型类型,如Keeper<Animal>,你只需选择泛型类型的名称和类型参数的名称。类型参数应该澄清类型参数和泛型类型之间的关系。有时你会遇到像T(类型的缩写)这样的名称,但是当类型参数具有明确的角色(如Animal)时,应该避免使用这些名称。

请注意,Keeper类型目前不存储任何东西,甚至不使用任何类型的Animal。从本质上讲,泛型是一种系统定义类型集的方法。

使用类型参数

但是,通常你需要对类型参数进行处理。

假设你想要更好地表达个体。首先,类型定义要包含有标识性的属性(如名称)。这让每个值都代表单个动物或饲养员的身份:

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
  }
}

你还需要跟踪哪个饲养员照料哪些动物。假设每个饲养员早上负责一只,下午负责另一只。你可以为泛型添加上午的动物和下午的动物属性来表达这一点。但是还需要什么属性呢?

很明显,如果一个饲养员只管理狗,那么它的属性只能是狗。如果是猫,那就是猫。一般来说,如果它是动物的饲养者,那么早上和下午的动物属性应该是Animal这样的类型。

要表达这一点,你只需使用类型参数,也就是之前用于区分你是哪种饲养员的属性:

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,你可以表达出早晨和下午的动物一定是管理员最了解的动物。

正如函数参数成为函数体中要使用的常量一样,你可以在整个类型定义中使用类型参数,比如Animal。在Keeper<Animal>的定义中,你可以在任何地方使用类型参数,用于存储属性以及计算属性、方法签名或嵌套类型。

现在,当你实例化一个Keeper时,Swift将确保编译时,上午和下午的类型是相同的:

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

在这里,饲养员Jason在早上管理着Whiskers,而在下午管理Sleepy。jason的类型是Keeper <Cat>。注意,你不必为类型参数指定值。因为你使用了Cat实例作为morningCare和afternoonCare的值,swift知道jason的类型应该是Keeper <Cat>。

类型约束

在你对Keeper的定义中,作为标识符的Animal作为类型参数。

这就像func feed(cat: cat) {/ * open can等简单函数中的参数cat。* / }。但在定义函数时,不能简单地将任何参数传递给函数。只能传递类型为Cat的值。

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

这不是好的。你想要的是类似于函数的东西,你可以限制允许填充类型参数的类型。在Swift中,你可以使用类型约束。

有两种约束。最简单的类型约束是这样的:

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

标识符Pet必须是命名类型或协议。在类型参数括号“Animal: Pet”中的新表达式指定了这样的要求:无论提供给Animal的类型是什么,都必须满足作为Pet的子类或实现Pet协议的要求。

例如,你可以使用上面修改过的Keeper定义来执行这些限制,还可以重新定义Cat和其他动物,以便使用扩展实现Pet或追溯模型遵从协议。

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

因为Cat和Dog已经实现了一个name存储属性。接下来,创建
一些猫和狗的数组:

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的数组是同质的,因为元素具有相同的类型。pets是异质的,因为它是dog和car类型的混合体。

通过限制类型,你可以对其进行操作。例如,既然你现在知道Pet有name,你就可以写一套适合Pet的通用功能。

写四种不同参数的herd函数:

// 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类型数组。
2 此方法处理任何类型的Pet数组,但它们都需要是单一类型。函数名后面的尖括号允许你指定要在函数中使用的泛型类型。
3 只处理Dog或Dog的子类。

函数调用会找到最适合的函数。所以:

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

第二种类型约束涉及明确表示类型参数或其关联类型必须等于另一个参数或其一致类型之一。通过组合许多类型参数和相关类型的需求集,可以在泛型类型之上定义复杂的关系。

为了演示这一点,假设你想为cat数组添加一个扩展,并添加meow()。元素是数组的关联类型。你可以要求该元素为类型(或子类型)Cat:

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

调用meow方法

cats.meow()

但是dog元素的数组就不能使用

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

这些错误在这里看起来很明显,但是类型约束可以防止你在编译时犯一些小的错误。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 通配符 1.数组具有协变性:可以向导出类型的数组赋予基类型的数组引用: 上面的代码不会出现编译问题,因为Apple...
    zpauly阅读 697评论 0 0
  • 1、泛型基本概念 1.1、由来 泛型是JDK 1.5的一项新特性,在Java语言处于还没有出现泛型的版本时,只能通...
    聂叼叼阅读 217评论 0 1
  • 定义 逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型...
    开发者小王阅读 25,605评论 4 61
  • 做为主妇我不善理财,这从每个月的家庭财政赤字上足以证明。每月发饷,先生如数交到我手中,我便什么也不干,盘坐在地板上...
    薇言大义阅读 908评论 100 45
  • 长途旅游回家,刚刚放下行李,人已经摔倒在床上了。虽然已是夏天,尚未铺上席子的床铺依然柔软馨香,让人深陷其中。...
    花姑娘呀嘿阅读 404评论 0 0