2.20 类型转换
6 分钟阅读
原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/typecasting/
2.20 类型转换
确定值的运行时类型,并为它提供更具体的类型信息。
类型转换是一种检查实例类型的方式,也可以把实例当作它所在类层级中另一个父类或子类来处理。
Swift 中的类型转换用 is 和 as 两个运算符实现。这两个运算符提供了一种简单而富有表现力的方式,用来检查值的类型,或者把值转换成另一种类型。
你也可以用类型转换检查某个类型是否遵循某个协议,详见检查协议遵循性。
为类型转换定义类层级
你可以用类型转换处理类与子类的层级关系,检查某个具体类实例的类型,并把该实例转换成同一层级中的另一个类。下面三段代码定义了一个类层级,以及一个包含这些类实例的数组,供类型转换的例子使用。
第一段代码定义了一个名为 MediaItem 的新基类。这个类为数字媒体库中出现的任何条目提供基本功能:它声明了一个 String 类型的 name 属性和一个 init(name:) 构造器。(这里假定所有媒体条目,包括所有电影和歌曲,都有名字。)
| |
第二段代码定义了 MediaItem 的两个子类。第一个子类 Movie 封装关于电影或影片的附加信息,它在基类 MediaItem 的基础上添加了一个 director 属性以及相应的构造器。第二个子类 Song 则在基类基础上添加了 artist 属性和构造器:
| |
最后一段代码创建了一个名为 library 的常量数组,其中包含两个 Movie 实例和三个 Song 实例。library 数组的类型由用来初始化它的数组字面量推断而来:Swift 的类型检查器能够推断出 Movie 和 Song 有一个公共父类 MediaItem,因此为 library 数组推断出类型 [MediaItem]:
| |
存放在 library 中的元素在背后仍然是 Movie 和 Song 实例。不过,如果你遍历这个数组的内容,取回的元素的类型是 MediaItem,而不是 Movie 或 Song。要按它们的本来类型使用,就需要检查它们的类型,或者把它们向下转换为其他类型,如下所述。
检查类型
用类型检查运算符(is)检查某个实例是否属于某个子类类型。如果实例属于该子类类型,类型检查运算符返回 true,否则返回 false。
下面的例子定义了两个变量 movieCount 和 songCount,用来统计 library 数组中 Movie 和 Song 实例的数量:
| |
这个例子遍历 library 数组中的所有元素。每一轮循环中,for-in 循环都把常量 item 设为数组中的下一个 MediaItem。
如果当前的 MediaItem 是 Movie 实例,item is Movie 返回 true,否则返回 false;类似地,item is Song 检查该元素是否为 Song 实例。for-in 循环结束时,movieCount 和 songCount 的值分别记录了找到的每种类型的 MediaItem 实例数量。
向下转换
某个类类型的常量或变量在背后实际上可能指向子类的实例。当你认为确实如此时,可以用类型转换运算符(as? 或 as!)尝试向下转换到子类类型。
由于向下转换可能失败,类型转换运算符有两种形式:条件形式 as? 返回你要向下转换到的类型的可选值;强制形式 as! 把向下转换和强制解包合并为一次操作。
当你不确定向下转换是否会成功时,请使用条件形式的类型转换运算符(as?)。它总是返回一个可选值,如果无法转换,该值就是 nil,这让你能够检查转换是否成功。
只有在你确信向下转换一定会成功时,才使用强制形式的类型转换运算符(as!)。如果你试图向下转换成错误的类类型,这种形式会触发运行时错误。
下面的例子遍历 library 中的每个 MediaItem,为每个元素打印合适的描述。为此,它需要把每个元素当作真正的 Movie 或 Song 来访问,而不只是当作 MediaItem,这样才能在描述中使用 Movie 或 Song 的 director 或 artist 属性。
在这个例子里,数组中的每个元素可能是 Movie,也可能是 Song。你事先并不知道每个元素实际的类,因此适合使用条件形式的类型转换运算符(as?),在每轮循环中检查向下转换是否成功:
| |
这个例子先尝试把当前的 item 向下转换成 Movie。由于 item 是 MediaItem 实例,它可能是 Movie,同样也可能是 Song,甚至只是基类 MediaItem。正因存在这种不确定性,在尝试向下转换成子类类型时,as? 形式的类型转换运算符返回可选值:item as? Movie 的结果类型是 Movie?,也就是"可选 Movie"。
对库数组中的 Song 实例向下转换成 Movie 会失败。为应对这种情况,上面的例子用可选绑定检查可选 Movie 是否真的包含值(也就是检查向下转换是否成功)。这个可选绑定写作 if let movie = item as? Movie,可以读作:
“尝试把 item 作为 Movie 访问。如果成功,就把返回的可选 Movie 中存放的值赋给一个名为 movie 的新临时常量。”
如果向下转换成功,就用 movie 的属性打印该 Movie 实例的描述,包括其 director 的名字。检查 Song 实例时采用类似的思路,每在库中找到一个 Song,就打印相应的描述(包括 artist 名字)。
注意:转换并不会真正修改实例或改变它的值。 底层实例保持不变,只是把它当作转换目标类型的实例来对待和访问。
Any 与 AnyObject 的类型转换
Swift 提供了两个特殊类型来处理非特定类型:
Any可以表示任何类型的实例,包括函数类型。AnyObject可以表示任何类类型的实例。
只有当你确实需要 Any 和 AnyObject 提供的行为和能力时,才使用它们。在代码中明确说明你期望处理的类型总是更好的做法。
下面是一个用 Any 处理多种不同类型的例子,其中包括函数类型和非类类型。这个例子创建了一个名为 things 的数组,可以存放 Any 类型的值:
| |
things 数组包含两个 Int 值、两个 Double 值、一个 String 值、一个 (Double, Double) 类型的元组、电影 “Ghostbusters”,以及一个接受 String 值并返回另一个 String 值的闭包表达式。
要找出只知其为 Any 或 AnyObject 类型的常量或变量的具体类型,你可以在 switch 语句的 case 中使用 is 或 as 模式。下面的例子遍历 things 数组中的元素,并用 switch 语句查询每个元素的类型。有几个 switch case 把匹配到的值绑定到指定类型的常量上,以便打印它的值:
| |
注意:
Any类型可以表示任何类型的值,包括可选类型。 如果你在需要Any类型值的地方使用可选值, Swift 会给出警告。 如果你确实需要把可选值当作Any值使用, 可以用as运算符把可选值显式转换成Any, 如下所示。
1 2 3let optionalNumber: Int? = 3 things.append(optionalNumber) // 警告 things.append(optionalNumber as Any) // 无警告