15.3 重写
一个类继承另一个类的属性、方法、下标等特征后,子类可以重写(overriding)这些特征,overriding也有人翻译为“覆盖”,为了统一名称本书全部翻译为“重写”。下面我们就逐一介绍这些特征的重写。
15.3.1 属性重写
我们可以在子类中重写从父类继承来的属性,属性的重写一方面可以重写getter和setter访问器,另一方面可以重写属性观察者。
通过对第12章属性的学习,我们知道,计算类型属性需要使用getter和setter访问器,而存储属性不需要。子类在继承父类后,也可以通过getter和setter访问器重写父类的存储属性和计算属性。
下面看一个示例:
class Person {
var name : String ①
var age : Int ②
func description() -> String {
return "\(name) 年龄是: \(age)"
}
init (name : String, age : Int) {
self.name = name
self.age = age
}
}
class Student : Person {
var school : String ③
override var age : Int { ④
get {
return super.age ⑤
}
set {
super.age = newValue < 8 ? 8 : newValue ⑥
}
} ⑦
convenience init() {
self.init(name : "Tony", age : 18, school : "清华大学")
}
init (name : String, age : Int, school : String) {
self.school = school
super.init(name : name, age : age)
}
}
let student1 = Student()
println("学生年龄:\(student1.age)")
Student1.age = 6
println("学生年龄:\(student1.age)")
上述代码第①行在Person
类中定义存储name
属性,第②行定义存储age
属性。然后在Person
的子类Student
中重写age
属性,其中第④~⑦行是重写代码,重写属性前面要添加override
关键字,见代码第④行。在getter方法器中,第⑤行代码返回super.age
,super
指代Person
类实例,super.age
是直接访问父类的age
属性。在setter访问器中,第⑥行代码super.age = newValue < 8 ? 8 : newValue
,是比较新值是否小于8岁(8岁为上学年龄),如果小于8岁,把8赋值给父类的age
属性,否则把新值赋值给父类的age
属性。
从属性重写可见,子类本身并不存储数据,数据是存储在父类的存储属性中的。
以上示例是重写属性getter和setter访问器,我们还可以重写属性观察者,代码如下:
class Person {
var name : String
var age : Int
func description() -> String {
return "\(name) 年龄是: \(age)"
}
init (name : String, age : Int) {
self.name = name
self.age = age
}
}
class Student : Person {
var school : String
override var age : Int { ①
willSet { ②
println("学生年龄新值:\(newValue)") ③
}
didSet{ ④
println("学生年龄旧值:\(oldValue)") ⑤
}
} ⑥
convenience init() {
self.init(name : "Tony", age : 18, school : "清华大学")
}
init (name : String, age : Int, school : String) {
self.school = school
super.init(name : name, age : age)
}
}
let student1 = Student()
println("学生年龄:\(student1.age)")
Student1.age = 6 ⑦
println("学生年龄:\(student1.age)")
上述代码第①~⑥行重写了age
属性观察者。重写属性前面要添加override
关键字,见代码第①行。如果只关注修改之前的调用,可以只重写willSet
观察者;如果只关注修改之后的调用,可以只重写didSet
观察者,总之是比较灵活的。在观察者中,还可以使用系统分配默认参数newValue
和oldValue
。
代码第⑦行修改了age
属性,修改前后的输出结果如下:
学生年龄新值:6
学生年龄旧值:18
提示 一个属性重写了观察者后,就不能同时对getter和setter访问器重写。另外,常量属性和只读计算属性也都不能重写属性观察者。
15.3.2 方法重写
我们可以在子类中重写从父类继承来的实例方法和静态方法(又称为类方法)。
下面看一个示例:
class Person {
var name : String
var age : Int
func description() -> String { ①
return "\(name) 年龄是: \(age)"
}
class func printlnClass() ->() { ②
println( "Person 打印...")
}
init (name : String, age : Int) {
self.name = name
self.age = age
}
}
class Student : Person {
var school : String
convenience init() {
self.init(name : "Tony", age : 18, school : "清华大学")
}
init (name : String, age : Int, school : String) {
self.school = school
super.init(name : name, age : age)
}
override func description() -> String { ③
println("父类打印 \(super.description())") ④
return "\(name) 年龄是: \(age), 所在学校: \(school)。"
}
override class func printlnClass() ->() { ⑤
println( "Student 打印...")
}
}
let student1 = student()
println("学生1:\(student1.description())") ⑥
Person.printlnClass() ⑦
Student.printlnClass() ⑧
在Person
类中,第①行代码是定义实例方法description
,第②行代码是定义静态方法printlnClass
,然后在Person
类的子类Student
类中重写description
和printlnClass
方法,代码第③行是重写实例方法description
,重写的方法前面要添加关键字override
。第④行代码使用super.description()
语句调用父类的description
方法,其中super
指代父类实例。
第⑤行代码是重写静态方法printlnClass
,在静态方法中不能访问实例属性。
最后第⑥行调用了description
方法。由于在子类中重写了该方法,所以调用的是子类中的description
方法。输出结果是:
父类打印 Tony 年龄是: 18
学生1:Tony 年龄是: 18, 所在学校: 清华大学。
为了测试静态方法重写,第⑦行调用了Person.printlnClass()
语言,它是调用父类的printlnClass
静态方法,输出结果是:
Person 打印...
第⑧行调用了Student.printlnClass()
语言,它是调用子类的printlnClass
静态方法,输出结果是:
Student 打印...
15.3.3 下标重写
下标是一种特殊属性。子类属性重写是重写属性的getter和setter访问器,对下标的重写也是重写下标的getter和setter访问器。
下面看一个示例:
class DoubleDimensionalArray { ①
let rows: Int, columns: Int
var grid: [Int]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(count: rows * columns, repeatedValue: 0)
}
subscript(row: Int, col: Int) -> Int { ②
get {
return grid[(row columns) + col]
}
set {
grid[(row columns) + col] = newValue
}
} ③
}
class SquareMatrix : DoubleDimensionalArray { ④
override subscript(row: Int, col: Int) -> Int { ⑤
get { ⑥
return super.grid[(row columns) + col] ⑦
}
set { ⑧
super.grid[(row columns) + col] = newValue * newValue ⑨
} ⑩
}
}
var ary2 = SquareMatrix(rows: 5, columns: 5)
for var i = 0; i < 5; i++ {
for var j = 0; j < 5; j++ {
ary2[i,j] = i + j
}
}
for var i = 0; i < 5; i++ {
for var j = 0; j < 5; j++ {
print("\t\t \(ary2[i,j])")
}
print("\n")
}
上述代码第①行定义了类DoubleDimensionalArray
,它在代码第②行和第③行定义了下标。第④行代码定义了类SquareMatrix
,它继承了DoubleDimensionalArray
类,并且在第④~⑩行重写了父类的下标。与其他类的重写类似,前面需要添加关键字override
,见代码第⑤行。第⑥行是重写getter访问器,其中的第⑦行super.grid[(row * columns) + col]
语句中使用super
调用父类的grid
属性。第⑧行代码是重写setter访问器,其中的第⑨行super.grid[(row columns) + col] = newValue newValue
语句是给父类的grid
属性赋值。
15.3.4 使用final
关键字
我们可以在类的定义中使用final
关键字声明类、属性、方法和下标。final
声明的类不能被继承,final
声明的属性、方法和下标不能被重写。
下面看一个示例:
final class Person { ①
var name : String
final var age : Int ②
final func description() -> String { ③
return "\(name) 年龄是: \(age)"
}
final class func printlnClass() ->() { ④
println( "Person 打印...")
}
init (name : String, age : Int) {
self.name = name
self.age = age
}
}
class Student : Person { //编译错误 ⑤
var school : String
convenience init() {
self.init(name : "Tony", age : 18, school : "清华大学")
}
init (name : String, age : Int, school : String) {
self.school = school
super.init(name : name, age : age)
}
override func description() -> String { //编译错误 ⑥
println("父类打印 \(super.description())")
return "\(name) 年龄是: \(age), 所在学校: \(school)。"
}
override class func printlnClass() ->() { //编译错误 ⑦
println( "Student 打印...")
}
override var age : Int { //编译错误 ⑧
get {
return super.age
}
set {
super.age = newValue < 8 ? 8 : newValue
}
}
}
上述代码第①行定义Person
类,它被声明为final
,说明它是不能被继承的,因此代码第⑤行定义Student
类,并声明为Person
子类时,会报如下编译错误:
Inheritance from a final class 'Person'
第②行定义的age
属性也是final
,那么在代码第⑧行试图重写age
属性时,会报如下编译错误:
Var overrides a 'final' var
第③行定义description
实例方法,它被声明为final
,那么在代码第⑥行试图重写description
实例方法时,会报如下编译错误:
Instance method overrides a 'final' instance method
第④行定义printlnClass
静态方法,它被声明为final
,那么在代码第⑦行试图重写printlnClass
静态方法时,会报如下编译错误:
Class method overrides a 'final' class method
使用final
可以控制我们的类被有限地继承,特别是在开发一些商业软件时,适当地添加final
限制是非常有必要的。