Kotlin 类与继承

kotlin 中使用关键字 class 声明类

类声明由类名,类头(指定其类型参数,主构造函数等),花括号包围的类体构成,类头 和类体都是可选的。

class Student {
    
}

如果一个类没有类体,则可以省略花括号

class Empty 

主构造函数

一个类可以有一个主构造函数和一个或多个次构造函数,主构造函数是类头的一部分,主构造函数跟在 类名后。

class 类名 constructor(形参1,形参2,形参3{
    
}
class Employee constructor(firstName: String, lastName: String, age: Int) {
    private var name: String
    private var firstName: String
    private var lastName: String
    private var workTime: Int

    init {
        println("first init")
        this.firstName = firstName   // 主构造函数不能包含任何初始化代码,所以放在 init 中初始化成员变量 firstName,lastName
        this.lastName = lastName
        this.name = this.firstName + this.lastName    // 成员变量可以在 init 初始化代码块中初始化
        workTime = age / 2
    }

    init {
        println("second init")
    }
}

// 输出结果:
first init
second init

注意点:

  • 关键字 constructor ,在 Java 中,构造方法名必须和类名相同,而在 kotlin 中,是通过 contructor 关键字来标明的,且对于主构造函数而言,它的位置是在 类头(即紧跟类名的后面),而不是在类体中。
  • 关键字 init : init 被称作是初始化代码块,他的作用是为主构造函数服务的,由于主构造函数是放在 类头中的,是不能包含任何初始化语句的(这是语法规定的),这个时候就是 init 的用武之地了,我们可以把初始化执行语句放在 init 代码块中进行初始化,为类的成员属性赋值。
  • 在实例初始化期间,初始化代码块按照他们出现在类体重的顺序执行,与属性初始化器交织在一起。

如果主构造函数没有任何注解或可见性修饰符,则可以省略这个 constructor 关键字。

class Employee(fristName: String, lastName: String, age: Int) { // 省略 constructor 关键字

}

类的成员属性初始化不是必须放在 init 代码块中的,可以在定义属性时直接将主构造函数中的形参赋值给他。

class Employee constructor(firstName: String, lastName: String, age: Int) {
    private var name: String = firstName + lastName
    private var firstName: String = firstName
    private var lastName: String = lastName
    private var workTime: Int = age / 2

    init {
        println("first init")
        this.firstName = firstName   // 主构造函数不能包含任何初始化代码,所以放在 init 中初始化成员变量 firstName,lastName
        this.lastName = lastName
        this.name = this.firstName + this.lastName    // 成员变量可以在 init 初始化代码块中初始化
        workTime = age / 2
    }

    init {
        println("second init")
    }
}

可以发现,这种在构造函数中声明形参,然后在属性定义进行赋值,有点繁琐,我们可以直接在主构造函数中定义类的属性。

class Computer(private var name: String, private var price: Int) {

    fun printInfo() {
        println("name = $name , price  = $price")
    }
}
fun main(args: Array<String>) {
    var appleComputer = Computer("apple", 10000)
	appleComputer.printInfo()
}

输出结果:

name = apple , price  = 10000

当我们在定义一个类时,如果没有显式的提供主构造函数,Kotlin 编译器会默认生成一个无参主构造函数,这点和 Java 是一样的

class Mouse {
    private var name: String = "LenovoMousc"
    private var price: Int = 23
    fun printInfo() {
        println("name = $name , price = $price")
    }
}

fun main(args: Array<String>) {
    var lenovoMouse = Mouse()
    lenovoMouse.printInfo()
}

输出结果:

name = LenovoMousc , price = 23

次构造函数

与主构造函数不同的是: 次构造函数是定义在类体中的,并且次构造函数可以有多个,而主构造函数只能有一个。

class MyButton : AppCompatButton {
    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, R.attr.buttonStyle)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

注意点:

  • 可以使用 this 关键字来调用 自己的其他构造函数
  • 使用 super 关键字来调用父类构造函数

我们看下,同时定义主构造函数和多个次构造函数

class Mouse(name: String, price: Int, address: String) {

    private var name: String = name
    private var price: Int = price
    private var address: String = address

    constructor(name: String) : this(name, 0)

    constructor(name: String, price: Int) : this(name, price, "")

    fun printInfo() {
        println("name = $name , price  = $price, address = $address")
    }
}

fun main(args: Array<String>) {
    var lenovoMouse = Mouse("applemouse")
    lenovoMouse.printInfo()
}

一个 三个参数的主构造函数,两个次构造函数。一个参数的次构造函数调用了两个参数的次构造函数,两个参数的次构造函数调用了三个参数的主构造函数。

我们把 两个参数的次构造函数的 this(name,price,"") 删除掉,会出现什么问题呢?

class Mouse(name: String, price: Int, address: String) {

    private var name: String = name
    private var price: Int = price
    private var address: String = address

    constructor(name: String) : this(name, 0)

    constructor(name: String, price: Int) {}    // 删除掉调用三个参数的主构造器,会提示: Primary constructor call expected

    fun printInfo() {
        println("name = $name , price  = $price, address = $address")
    }
}

fun main(args: Array<String>) {
    var lenovoMouse = Mouse("applemouse")
    lenovoMouse.printInfo()
}

删除三个参数的主构造函数会提示: Primary constructor call expected

结论:次构造函数会直接或间接调用主构造函数的

创建类的实例

要创建一个类的实例,可以普通函数一样调用 构造函数。

kotlin 中没有 new 关键字

var employee = Employee("1001", "xiaohe", 12)

类成员

类可以包含:

  • 构造函数与初始化代码块
  • 函数
  • 属性
  • 嵌套类和内部类
  • 对象声明

继承

在 kotlin 中所有类都有一个共同的超类 Any , 这对于没有声明超类型的类提供了默认的超类

注意点:

  • Kotlin 中如果类前面不加关键字 open, 则该类默认是 final 的 ,也就是不能被继承,如果继承该类,会报错: This type is final, so it cannot be inherited from.

  • Any 类 默认提供了三个方法 : equal() , hashCode() ,toString()

  • 基类中没有定义构造器时,Kotlin 会默认添加一个无参的构造器,所以子类在继承该基类时,需要实现该基类的无参构造器。

    // 编译器会提供的默认无参的构造器
    open class Phone {
    }
    
    // 继承基类时,需要实现基类中的无参的构造器
    class ApplePhone(private var rom: Int) : Phone() {
    
        fun printInfo() {
            println("the rom is $rom")
        }
    }
    

    输出结果:

    the rom is 4
    
  • 在写子类是,如果子类中有主构造函数,那么基类必须要在主构造函数中立即初始化

    // open 标识该类可以被继承
    open class Mouse(name: String, price: Int, address: String) {
        private var name: String = name
        private var price: Int = price
        private var address: String = address
        
        constructor(name: String) : this(name, 0)
    
        constructor(name: String, price: Int) : this(name, price, "")
    
        fun printInfo() {
            println("name = $name , price  = $price, address = $address")
        }
    }
    
    class AppleMouse(name: String, price: Int, address: String, wireless: Boolean) : Mouse(name, price, address) {
    
    }
    
  • 如果子类中没有主构造函数,那么必须要在此构造函数中调用 super 关键字初始化父类,或者调用另一个此构造函数。初始化父类时,可以调用父类的不同的构造函数。

重写

重写方法

在继承基类时,子类可以重写基类中的有 open 关键字修饰的方法,如果没有 open 修饰的方法,是允许子类重写的,重写方法时,需要使用 override 关键字标识。

在 IDEA IDE 中,在子类中可以使用快捷键 Ctrl + O 快速重写父类中被 open 关键字修饰的方法

// 基类有默认的无参构造器,所以子类继承时,
open class Phone {
    open fun getColor() {
        println("phone has many colors")
    }
}

class ApplePhone(private var rom: Int) : Phone() {

    override fun getColor() {
        super.getColor()
        println("iPone color is red")
    }

    fun printInfo() {
        println("the rom is $rom")
    }
}

fun main(args:Array<String>) {
    var iPhone = ApplePhone(4)
    iPhone.getColor()
}

输出结果:

iPone color is red

如果一个类继承了多个类或接口,并且父类或接口中有相同的名字的方法需要重写,那么子类这时候必须重写该方法,并且如果子类想区分父类中的方法,可以使用 super 关键字调用不同的父类方法。

/**
 * 类中的 open 关键字标识该类可以被继承
 */
open class Fly {
    /**
     * 方法中的 open 关键字标识该方法可以被重写
     */
    open fun show() {
        println("Fly ......")
    }
}

/**
 * interface 不用写 open, 默认就是 open 的
 */
interface Run {
    /**
     * 接口中的方法默认是 open 的
     */
    fun show() {
        println("Run .....")
    }
}

/**
 * 子类实现接口时不用写 (), 因为接口是没有构造函数的
 */
class Bird : Fly(), Run {
    override fun show() {
        super<Fly>.show()   // 使用 super 关键字来调用不同父类中的同名方法
        super<Run>.show()
    }
}

输出结果:

Fly ......
Run .....
重写变量
  • 基类中被重写的变量也要有 open 关键字的声明,
  • 子类可以使用 var 类型的变量去重写父类中 val 类型的变量,但是不能使用 val 类型的变量去重写父类中 var 类型的变量(如果使用 val 类型的变量去重写父类的 var 类型的变量,那么子类这个 val 类型的变量就会多一个 set 方法,而 val 类型的变量是不允许有 set 方法的)
open class Car(maxSpeed: Int, price: Int) {
    open val maxSpeed: Int = maxSpeed
    open var price: Int = price
}

class Maserati(maxSpeed: Int, price: Int) : Car(maxSpeed, price) {
    /**
     * 子类使用 var 类型重写父类中的 val 类型的变量
     */
    override var maxSpeed: Int = maxSpeed
    /**
     * 子类使用 val 类型重写父类中的 var 类型的变量,========>>>>>>编译报错
     */
    override val price: Int = price
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章