前言
上一篇,我们学习了Kotlin中的类和对象,今天继续来学习Kotlin中的继承。
Kotlin 继承
Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类:
class Example // 从 Any 隐式继承
Any 默认提供了三个函数:
equals()
hashCode()
toString()
注意:Any 不是 java.lang.Object。
如果一个类要被继承,可以使用 open 关键字进行修饰。
open class Base(p: Int) // 定义基类
class Derived(p: Int) : Base(p)
构造函数
子类有主构造函数
如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
open class Person(var name : String, var age : Int){
// 基类
//...
}
//子类主构造函数中必须初始化基类主构造函数
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
//...
}
// 测试
fun main(args: Array<String>) {
val s = Student("Runoob", 18, "S12346", 89)
println("学生名: ${
s.name}")
println("年龄: ${
s.age}")
println("学生号: ${
s.no}")
println("成绩: ${
s.score}")
}
子类没有主构造函数
如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。
class Student : Person {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}
示例:
/**用户基类**/
open class Person(name:String){
/**次级构造函数**/
constructor(name:String,age:Int):this(name){
//初始化
println("-------基类次级构造函数---------")
}
}
/**子类继承 Person 类**/
class Student:Person{
/**次级构造函数**/
constructor(name:String,age:Int,no:String,score:Int):super(name,age){
println("-------继承类次级构造函数---------")
println("学生名: ${
name}")
println("年龄: ${
age}")
println("学生号: ${
no}")
println("成绩: ${
score}")
}
}
fun main(args: Array<String>) {
var s = Student("Runoob", 18, "S12345", 89)
}
输出结果:
-------基类次级构造函数---------
-------继承类次级构造函数---------
学生名: Runoob
年龄: 18
学生号: S12345
成绩: 89
重写
函数重写
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词:
/**用户基类**/
open class Person{
open fun study(){
// 允许子类重写
println("我毕业了")
}
}
/**子类继承 Person 类**/
class Student : Person() {
override fun study(){
// 重写方法
println("我在读大学")
}
}
fun main(args: Array<String>) {
val s = Student()
s.study();
}
标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用 final 关键字。
属性重写
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 get 方法的属性覆盖。
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。 这是允许的,因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。
请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
class Polygon : Shape {
override var vertexCount: Int = 0 // 以后可以设置为任何数
}
子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写
派生类初始化顺序
在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。
open class Base(val name: String) {
init {
println("Initializing Base") }
open val size: Int =
name.length.also {
println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also {
println("Argument for Base: $it") }) {
init {
println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also {
println("Initializing size in Derived: $it") }
}
这意味着,基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员。
调用超类实现
派生类中的代码可以使用 super 关键字调用其超类的函数与属性访问器的实现:
open class Rectangle {
open fun draw() {
println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer:
class FilledRectangle: Rectangle() {
fun draw() {
/* …… */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() {
/* …… */ }
fun drawAndFill() {
super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
fill()
println("Drawn a filled rectangle with color ${
super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
}
}
}
尾巴
今天的学习笔记就先到这里了,下一篇我们将学习Kotlin中的拓展。
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!