ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 继承的概念 [TOC] 前面主要讲解的是单个类相关的知识点。继承则是描述多个类之间的关系。要形成继承关系,需要两个类。一个称为父类,也叫基类,一个称为子类。如果B类继承A,那么这些说法都是可以的,“**B是A的子类**”、“**B的父类是A**”、“**B的基类是A**”或者“**A是B的父类**”、“**A的子类是B**”、“**A是B的基类**”。 **拥有继承关系的类,存在层级关系**,现实生活中,我们可以说“**男人女人都是人**”。程序世界里面,我们可以说“男人”和“女人”都是继承自“人”。因为它们存在如下的层级关系: ![](http://upload-images.jianshu.io/upload_images/7368752-68d01cf18e568a08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 在比如,现实生活中,我们可以说“食草动物、食肉动物都是动物,兔子和羊都是食草动物、狮子和豹子都是食肉动物”。 程序世界里面,我们可以说“食草动物、食肉动物继承动物。兔子、羊继承食草动物。狮子、豹子继承食肉动物”。因为它们也是存在如下的层级关系的: ![image.png](http://upload-images.jianshu.io/upload_images/7368752-af1eec90966b63dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **继承分为直接继承和间接继承**。兔子和羊直接继承至食草动物,兔子和羊间接继承至动物。狮子和豹子直接继承至食肉动物,狮子和豹子间接继承至动物。        ## 继承的实现 在Kotlin中,类的继承是指在一个现有类的基础上去构建一个新类,构建出来的新类被称作子类,现有类被称作父类,子类会自动拥有父类所有可继承的属性和方法。在程序中如果想声明一个类继承另一个类,则需要使用英文冒号“:”,**由于Kotlin中的所有类都默认使用final关键字修饰,不能被继承,因此,当继承某个类时,需要在这个类的前面加上open关键字**。 ### 最基本的继承关系表现 Java中的继承通过extends关键字,Kotlin中的继承关系用什么关键字表示呢?比如我们看看“男人女人都是人”怎么用程序去表示,参考代码: ![image.png](http://upload-images.jianshu.io/upload_images/7368752-591919bcc8585bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 通过这段代码可以得知两点。 * 第一,**Kotlin中的继承关系,通过冒号去表示**。 * 第二,**父类需要通过open关键字表示,才可以被继承**。 ### 继承的几种情况 * (1)在Kotlin中,一个类只能继承一个父类,不能继承多个父类,即一个类只能有一个父类,例如下面的这种情况,在编译器中会报错的。 ``` open class A{} open class B{} class C : A(),B(){ //C类不可以同时继承A 类和B 类 } ``` * (2)多个类可以继承一个父类,例如下面这种情况是可以的。 ``` open class A {} class B : A() {} class C : A() {} //类B 和类C 都可以继承类A ``` * (3)在Kotlin中,多层继承也是可以的,即一个类的父类可以再去继承另外的父类,例如C类继承B类,而B类又可以去继承A类,这时,C类也可称作A类的子类,具体示例如下所示。 ``` open class A {} open class B : A() {} // 类B 继承类A,类B 是类A 的子类 class C : B() {} // 类C 继承类B,类C 是类B 的子类,同时也是类A 的子类 ``` >[info]注意:如果类B需要被类C继承,则类B前边必须添加关键字open。 * (4)在Kotlin中,子类和父类是一种相对概念,也就是说一个类是某个父类的同时,也可以是另一个类的子类。例如上面的实例中,B类是A类的子类,同时又是C类的父类。 ### 子类可以拥有父类的方法和属性 生活中我们知道“人有名字,人会吃饭”。那么再说“男人有名字,男人会吃饭”和“女人有名字,男人会吃饭”就不觉得奇怪,会觉得是理所当然。 来到程序世界其实就是,人(Person)有名字(name)属性和吃饭(eat)方法。男人和女人都是都是继承至人,所以拥有人(Person)的名字(name)属性和吃饭(eat)方法。我们通过代码可以这样去表示,参考代码: ![image.png](http://upload-images.jianshu.io/upload_images/7368752-0ae388da43d76cb6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 针对以上代码可以看出很多知识点: * ①    代码第8行,因为父类定义了name属性,子类可以通过次构函数,接收外界传入的name属性,然后通过super关键字,传递给了父类。 * ②    代码第11行,因为父类定义了name属性,子类也可以通过主构函数,接收外界传入的name属性,通过父类的主构函数传递给父类。 * ③    代码第15行对象man获取了name属性,调用了eat方法都是父类定义的。 * ④    代码第18行对象woman获取了name属性,调用了eat方法也都是父类定义的。 ### 方法重写 在Kotlin程序中,经常会用到类的继承,子类继承父类时会自动继承父类中定义的方法或属性,但有时在子类中需要对继承的方法或属性进行一些修改,这个过程被称为方法或属性的重写。方法和属性的重写需要注意以下几点。 * 在**子类中重写的方法与在父类中被重写的方法必须具有相同的方法名、参数列表以及返回值类型,并且被重写的方法前边需要使用“override”关键字标识**。 * 在**子类中重写的属性与在父类中被重写的属性必须具有相同的名称和类型,并且被重写的属性前边也需要使用“override”关键字标识**。 * **在父类中需要被重写的属性和方法前面必须使用open关键字来修饰**。 示例 ``` open class Father() { open var name = "江小白" open var age = 35 open fun sayHello() { println("Hello!我叫$name,我今年$age 岁。") } } class Son : Father() { override var name = "小小白" override var age = 5 override fun sayHello() { println("Hello!我是江小白的儿子,我叫$name,我今年$age 岁。") } } fun main(args: Array<String>) { var father = Father() father.sayHello() var son = Son() son.sayHello() } ``` 运行结果 ``` Hello!我叫江小白,我今年35 岁。 Hello!我是江小白的儿子,我叫小小白,我今年5 岁。 ``` 在上述代码中,定义了一个Son类继承Father类,在子类Son中定义了name和age属性对父类中的属性进行重写,同时还定义了一个sayHello()方法对父类中的方法进行重写。在main()函数中,分别创建父类对象和子类对象,并调用各自的sayHello()方法。根据运行结果可知,调用子类对象中的sayHello()方法时,只会调用子类重写的这个方法,并不会调用父类的sayHello()方法,同时name和age的属性值也只调用的子类中重写的属性值。 #### 子类覆写父类的方法 比如现实生活中我们知道“人会尿尿,男人是站着尿尿,女人是蹲着尿尿”。尿尿这样的行为是男人(Man)和女人(Woman)共有的,可以定义为人(Person)的行为。男人(Man)和女人尿尿(Women)的方式各不相同,需要根据自身情况进行重写。 在程序世界里面,**共有方法定义在基类里面,子类可以根据自身情况对基类的共有方法进行修改,改成符合子类的情况**,这样的行为就叫**方法重写或者叫方法覆写**。 我们通过代码可以这样去表示,参考代码: ![image.png](http://upload-images.jianshu.io/upload_images/7368752-8987a11cdf4cf2ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 针对以上代码可以看出很多知识点: * ①    代码第6行,基类定义的niaoniao方法,如果父类允许子类重写,**需要通过open关键字标记**。 * ②    代码第14行,子类如果重写了父类的方法,需要通过 **override**标记。 #### 子类覆写父类的属性 比如现实生活中我们知道“人都有头发,男人短发居多,女人是长发居多”。人(Person)在头发样式(hairStyle)这个属性上默认值只是“有头发”。男人(Man)和女人(Woman)对头发样式(hairStyle)做了重新的定义。男人头发样式(hairStyle)多是短头发,女人男人头发样式(hairStyle)则是长头发。 在程序世界里面,**共有属性定义在基类里面,子类可以根据自身情况对基类的共有属性进行修改,改成符合子类的情况**,这样的行为就叫**属性重写**。 我们通过代码可以这样去表示,参考代码: ![image.png](http://upload-images.jianshu.io/upload_images/7368752-6f84a3618be433e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![image.png](http://upload-images.jianshu.io/upload_images/7368752-3fb2afefc1d30ddb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 针对以上代码可以看出很多知识点: * ①    代码第2行,**基类定义的hairStyel属性,如果父类允许子类重写,需要通过open关键字标记**。 * ②    代码第15行,子类如果重写了父类的属性,需要通过 **override**标记。 * ③    代码第23行,子类如果重写了父类的属性,需要通过 **override**标记。 ### super关键字 在上一小节中,当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,在Kotlin中专门提供了一个super关键字用于访问父类的成员。例如访问父类的成员变量、成员函数等。 使用super关键字调用父类的成员变量和成员方法的语法格式如下: ``` super.成员变量 super.成员方法([形参1,形参2…]) ``` 参考:[super关键字](https://www.kancloud.cn/alex_wsc/android_kotlin/1067230) 接下来我们在前面示例的基础上进行改写,通过super关键字访问父类成员变量和成员方法, ``` open class Father() { open var name = "江小白" open var age = 35 open fun sayHello() { println("Hello!我叫${name},我今年${age}岁。") } } class Son : Father() { override var name = "小小白" override var age = 5 override fun sayHello() { super.sayHello() println("Hello!我是${super.name}的儿子,我叫${name},我今年${age}岁。") } } fun main(args: Array<String>) { var son = Son() son.sayHello() } ``` 运行结果 ``` Hello!我叫江小白,我今年35 岁。 Hello!我是江小白的儿子,我叫小小白,我今年5 岁。 ``` 上述代码中,第12和13行分别是通过super关键字来调用父类Father中的成员变量name和成员方法sayHello()。