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 = nameself.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 = schoolsuper.init(name : name, age : age)}}let student1 = Student()println("学生年龄:\(student1.age)")Student1.age = 6println("学生年龄:\(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 : Stringvar age : Intfunc description() -> String {return "\(name) 年龄是: \(age)"}init (name : String, age : Int) {self.name = nameself.age = age}}class Student : Person {var school : Stringoverride 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 = schoolsuper.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 : Stringvar age : Intfunc description() -> String { ①return "\(name) 年龄是: \(age)"}class func printlnClass() ->() { ②println( "Person 打印...")}init (name : String, age : Int) {self.name = nameself.age = age}}class Student : Person {var school : Stringconvenience init() {self.init(name : "Tony", age : 18, school : "清华大学")}init (name : String, age : Int, school : String) {self.school = schoolsuper.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: Intvar grid: [Int]init(rows: Int, columns: Int) {self.rows = rowsself.columns = columnsgrid = 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 : Stringfinal var age : Int ②final func description() -> String { ③return "\(name) 年龄是: \(age)"}final class func printlnClass() ->() { ④println( "Person 打印...")}init (name : String, age : Int) {self.name = nameself.age = age}}class Student : Person { //编译错误 ⑤var school : Stringconvenience init() {self.init(name : "Tony", age : 18, school : "清华大学")}init (name : String, age : Int, school : String) {self.school = schoolsuper.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限制是非常有必要的。
