## 继承的概念
[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()。
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android