Kotlin入门-父子身份更分明,继承篇

Kotlin入门-父子身份更分明,继承篇_第1张图片

前言

前文讲到,Kotlin中万物皆对象,连《基础数值类型》都不放过。

除了文字版本,也有Xmind版本 github地址

带几个问题吧
① Any 跟object什么区别?
② 子类的初始化顺序如何?
③ super的覆盖规则是什么?
④ 与java对比,kotlin在继承有什么变化?加强了什么?

本文从以下几个方面讲继承

  • 根是Any类
  • 构造函数
  • 重写方法
  • 重写属性
  • 中场小结
  • 派生类初始化顺序
  • 调用超类实现
  • super覆盖规则

根是Any类

首先,Any 不是 java.lang.Object。
在Kotlin中,Any是所有类的超类
如果没有显式声明超类型声明的类,其默认的超类是Any

Any 默认提供了三个函数

  • equals()
  • hashCode()
  • toString()

把超类型 放在冒号 之后

open class Base(p: Int)
class Derived(p: Int) : Base(p)

构造函数

子类有主构造函数

如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
这话看起来看起来,比较难懂。
先看个范例

open class Person(var name : String, var age : Int, var score : Int){// 基类
}

class Student(name : String, age : Int, var no : String, score : Int) : Person(name, age, score) {
}
// 测试
fun main(args: Array) {
    val s =  Student("wangxueming", 18, "S12346", 89)
    println("学生名: ${s.name}")
    println("年龄: ${s.age}")
    println("学生号: ${s.no}")
    println("成绩: ${s.score}")
}

讲解
Person是基类。Student是子类。
Person定义了name, age, score。Student有参数:name、age、no、score。
如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
就是说:如果基类Person定义的name、age、score,在子类中必须要进行赋值。
即 : 冒号之后,Person(name, age, score) 必须要把参数传入。

如果在name上加上var呢?

class Student(var name : String, age : Int, var no : String, score : Int) : Person(name, age, score) {
}

输出结果

‘name’ hides member of supertype ‘Person’ and needs ‘override’ modifier

报错了,因为Person的name与Student的name重名了;

换言之。如果你把Person的name,换个名字,改成otherName。
或者把Person的name改成otherName结果怎么样呢?

会错吗?答案是不会的。

open class Person(var otherName : String, var age : Int, var score : Int){// 基类
}

class Student(var myName : String, age : Int, var no : String, score : Int) : Person(myName, age, score) {
}

// 测试
fun main(args: Array) {
    val s =  Student("wangxueming", 18, "S12346", 89)
    println("学生名: ${s.otherName}")
    println("年龄: ${s.age}")
    println("学生号: ${s.no}")
    println("成绩: ${s.score}")
}

这里会正常输出结果

学生名: wangxueming
年龄: 18
学生号: S12346
成绩: 89

子类没有主构造函数

必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数
看范例

/**用户基类**/
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):this(name,age,no,score, 0){
        println("-------继承类次级构造函数 A---------")
    }
    /**次级构造函数**/
    constructor(name:String,age:Int,no:String,score:Int, sex : Int):super(name,age){
        println("-------继承类次级构造函数 B---------")
        println("学生名: ${name}")
        println("年龄: ${age}")
        println("学生号: ${no}")
        println("成绩: ${score}")
        println("性别: ${sex}")
    }
}

fun main(args: Array) {
    var s =  Student("wangxueming", 18, "S12345", 89)
    println()
    println()
    println()
    var s2 =  Student("wangxueming", 18, "S12345", 89, 1)
}

输出结果

-------基类次级构造函数---------
-------继承类次级构造函数 B---------
学生名: wangxueming
年龄: 18
学生号: S12345
成绩: 89
性别: 0
-------继承类次级构造函数 A---------

-------基类次级构造函数---------
-------继承类次级构造函数 B---------
学生名: wangxueming
年龄: 18
学生号: S12345
成绩: 89
性别: 1

这个结果。
Student(“wangxueming”, 18, “S12345”, 89)
用this在代理另一个构造函数是

Student(“wangxueming”, 18, “S12345”, 89, 1)
用super 关键字初始化基类


重写方法

Kotlin 力求清晰显式

加强了继承的限制。也就加强了 类之间的关联界定。

有几种情况要注意:

  • 使用fun声明函数时,此函数默认为final修饰,不能被子类重写
  • 如果允许子类重写该函数,那么就要手动添加 open 修饰它
  • 子类重写方法使用 override 关键词

跟java的差异

  • 能否被继承需要open关键字标明
  • 默认无法被继承

重写属性

用override关键字。
这基本跟java一致。

在Kotlin中,override某变量,必须要标注override!
这也是加强了类继承中,变量重写的界定。


中场小结

kotlin较java
kotlin对继承更细化,对继承中,增强 改变的部分 的标注


派生类初始化顺序

先说结论
1、父结构体
2、父结构体内参数处理
3、子类 init & 属性
4、父类init & 属性

看范例

open class Base(val name: String) {
    open val size: Int = 
        name.length.also { println("Initializing size in Base: $it") }
    init { println("Initializing Base"+size) }
}

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") }
}
fun main() {
    println("Constructing Derived(\"hello\", \"world\")")
    val d = Derived("hello", "world")
}

结果输出

Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing size in Base: 5
Initializing Base0
Initializing Derived
Initializing size in Derived: 10
注意
  • 初始化是有顺序的
  • 留意:打印的属性值
  • 看Base中的size值,在init中是无法正确获取的
  • 基类的open属性,不能被构造函数、属性初始化器、init块中使用open成员

调用超类实现

使用 super 关键字
基本与java一致

留意

在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer
我们来看个范例,了解下

class FilledRectangle: Rectangle() {
    fun draw() { /* …… */ }
    val borderColor: String get() = "black"
    
    inner class Filler {
        fun fill() { /* …… */ }
        fun drawAndFill() {
            [email protected]() // 调用 Rectangle 的 draw() 实现
            fill()
            println("Drawn a filled rectangle with color ${[email protected]}") // 使用 Rectangle 所实现的 borderColor 的 get()
        }
    }
}

super覆盖规则

一个类继承多个

为了表示采用从哪个超类型继承的实现,需要显式的标明。
用由尖括号中超类型名限定的 super进行标明,如 super

范例

open class Rectangle {
    open fun draw() { /* …… */ }
}

interface Polygon {
    fun draw() { /* …… */ } // 接口成员默认就是“open”的
}

class Square() : Rectangle(), Polygon {
    // 编译器要求覆盖 draw():
    override fun draw() {
        super.draw() // 调用 Rectangle.draw()
        super.draw() // 调用 Polygon.draw()
    }
}

你可能感兴趣的:([kotlin])