Kotlin基础六:类和继承

146 阅读5分钟

前言

类和继承在Kotlin中是比较常见的,可以对比之前java来学习Kotlin中的类和继承。

Kotlin中定义一个类class

// 类名 类头 类体
class Apple(var name:String){}

类头和类体是可选的,如果一个类没有类体,可省略花括号

class Apple

构造函数

分为主构造函数和次构造函数。一个类中可以有多个次构造函数和一个主构造函数,主构造函数是类头的一部分,仅跟在类名后,使用constructor关键字

class Apple constructor(var name:String)

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

class Apple(var name:String){}

一个类有注解或访问修饰符,constructor关键字不可以省略,注解或访问修饰符要放到constructor关键字的前面。

//私有化主构造器
class Apple private constructor(var name:String){}
// 给构造器函数添加注解
class Apple @Inject constructor(var name:String){}
// 构造器函数注解和私有化构造器
class Apple @Inject private constructor(var name:String){}

init初始化块

主构造函数不能包含任何代码(次构造函数可以),初始化的代码需要放到init关键字作为i前缀的初始化块中

class Apple(var name:String){
    init{
        println("init")
    }
}

已经有了类体的花括号{},如果再加上一个主构造函数的花括号{}就不协调,放到init块中。init块是主构造函数的一部分,它将和类属性的初始化按照他们在类体中出现的顺序执行

class Person(var name:String){
    var height = 171.also{ println("height=$it name=$name") }
    init {
        println("person init one $height $name")
    }
    var age = 18.also { println("age=$it name=$name") }
    init {
        println("class init two $age $name")
    }
}

fun test() {
    val  person = Person("ming")
}

主构造器中声明属性

构造函数中的参数可以在类属性的初始化器和init块中访问。将参数声明成属性(var val)可以在类的方法中访问该参数。

class Person(name:String){
    private fun info(){
        println(name)// 无法访问name,报红。构造函数中的参数无法在类中的方法中访问
    }
}
class Person(var name:String){
    private fun info(){
        println(name)// name已经通过var进行了属性声明
    }
}

参数声明成类属性可以在类的方法中访问该属性。通常声明一个类的属性是放到类体中声明的,Kotlin提供了便捷语法,可以在类的主构造函数中声明一个类属性,也是主构造函数的参数。

次构造函数

一个类可以用多个次构造函数

class Person{
    constructor(name:String){
        println("name=$name")
    }
    constructor(name:String,age:Int){
        println("name:$name age:$age")
    }
}

一个类既有主构造函数又有次构造函数,每个次构造函数需要委托给主构造函数,可以委托或别的次构造函数委托。委托到同一个类的另一个次构造函数用关键字this

class Person(name:String){
    // 直接委托
    constructor(name:String,age:Int):this(name){
        println("name:$name age:$age")
    }
    // 间接委托
    constructor(name:String,age:Int,sex:Int):this(name,age){
        println("name:$name age:$age sex:$sex")
    }
}

初始化块代码会作为主构造函数的一部分,委托给主构造函数init块中的代码会作为次构造函数的第一条语句,初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使没有主构造函数,委托仍会隐式发生

fun test() {
    val person = Person("xiake",18)
}
class Person(name:String){
    val info = name.apply { println("info init name:$name") }
    init {
        println("class init name:$name")
    }
    constructor(name:String,age:Int):this(name){
        println("name:$name age:$age")
    }
    constructor(name:String,age:Int,sex:Int):this(name,age){
        println("name:$name age:$age sex:$sex")
    }
}
输出:
info init name:xiake
class init name:xiake
name:xiake age:18

创建类的实例

创建对象实例省略了new关键字

val person = Person("xiake",18)

继承

java中共同的超类Object,Kotlin中超类Any,没有类型声明的类默认超类Any。Any有三个fangf:equals hashcode toString。所有的类都定义了这些方法。

public open class Any {
    public open operator fun equals(other: Any?): Boolean
    public open fun hashCode(): Int
    public open fun toString(): String
}

默认Kotlin的类都不可被继承,即final。用关键字open修饰一个类可被继承,放在关键字class前。

open class Person // Person类可被继承

Java遵循单继承(extends)多实现(implements)原则。Kotlin也遵循这个原则,继承和实现都用冒号:表示,在类头中把超类型放在冒号之后,多个超类用冒号隔开

open class Person(name:String){}
class Student(name:String):Person(name){}
interface Callback{}
class Student1(name:String):Person(name),Callback{}

子类有一个主构造函数,父类使用子类主构造函数中的参数就地初始化。

class MainActivity :AppcompatActivity(){}

在java中不需要加小括号(),子类的构造函数必须访问父类的构造函数。当类只有次构造函数,在次构造函数去访问,可省略括号()

class Person:Any{
    constructor():super()
}
public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

AndroidStudio右键,Convert Java File to Kotlin File

class MyView : View {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )
}

MyView中没有主构造函数,只有三个次构造函数。在次构造函数中去访问父类的构造函数,不需要小括号了()

覆盖方法

使一个方法被覆盖,依然用open关键字,在fun前面加open。

open class Person constructor(name:String){
    open fun eat(){
        println("eat")
    }
}
class Student(var name:String):Person(name){
    override fun eat() {
        super.eat()
    }
}

覆盖一个方法必须用override关键字修饰。标记为override成员本身是开放的,可以在子类中覆盖它。如果禁止覆盖,使用final关键字

class Student(var name:String):Person(name){
    final override fun eat() {
        super.eat()
    }
}

覆盖属性

在父类中声明的属性在子类中重新声明必须以override开头

open class Person{
    open val name:String = "Any"
}
class Student:Person(){
    override var name:String = "xiake"
}

可以用var属性覆盖val属性。反之不行。因为val声明了get方法,覆盖为var子类额外声明了一个set方法。可以将一个需要覆盖的属性声明到主构造函数中

open class Person{
    open val name:String = "Any"
}
class Student constructor(override var name:String):Person(){}

子类的初始化顺序

先完成父类的初始化,再初始化子类。遵循面向对象继承原则,子类的构造函数必须访问父类的构造函数

fun test() {
    val person = Student("xiake")
}
open class Person{
    init {
        println("person init")
    }
    open val name:String ="Any".also { println("person name:$it") }
}
class Student constructor(override var name:String):Person(){
    init {
        println("student init")
    }
    private val age = 20.also { println("student age:$it") }
}
输出:
person init
person name:Any
student init
student age:20

嵌套类和内部类

  • 嵌套类:在一个类或一个接口内部声明一个嵌套类
fun test() {
    val a = Outer.A()
    val b = CallBack.B()
}

class Outer {
    init {
        println("outer init")
    }

    class A {
        init {
            println("A init")
        }
    }
}
interface CallBack{
    class B{
        init {
            println("B init")
        }
    }
}

嵌套类中无法访问外部类的属性和方法

  • 内部类:使用inner关键字定义内部类,内部类的创建需要先创建外部类的实例,内部类可以访问外部类的属性和方法
fun test() {
    val a = Outer().A()
}

class Outer {
    init {
        println("outer init")
    }
    val name = "outer"
    fun getInfo() = "info"
    inner class A {
        init {
            println("A init")
        }
        val info = "$name ${getInfo()}".also { println("info=$it") }
    }
}
输出:
outer init
A init
info=outer info

访问父类中的方法和属性

子类可以使用super关键字调用父类的函数和属性访问器

open class Person{
    open fun eat(){
        println("eat")
    }
    open val work get() = "work"
    val sleep  = 5*60*1000L
}
class Student :Person(){
    override fun eat() {
        super.eat()
    }
    override val work = super.work
    val mySleep = super.sleep
}

在内部类中访问外部类中的属性和方法,使用@标签限制的super

open class Person{
    open fun eat(){
        println("eat")
    }
    open val work get() = "work"
    val sleep  = 5*60*1000L
}
class Student :Person(){
    override fun eat() {
        super.eat()
    }
    override val work = super.work
    val mySleep = super.sleep
    inner class A{
        val work = super@Student.work
        fun getInfo(){
            super@Student.eat()
        }
    }
}

覆盖多个父类中的同名方法

一个类它有多个父类,多个父类有同名方法。重写该方法,该方法内访问父类中的同名方法,使用尖括号中父类型名限定的super

open class Person{
    open fun eat(){
        println("eat")
    }
}
interface CallBack{
    fun eat(){}// kotlin接口中的方法可以有默认实现
}
class Student:Person(),CallBack{
    override fun eat() {
        super<Person>.eat()
        super<CallBack>.eat()
    }
}

抽象类

定义抽象类abstract,抽象类默认可继承的。将abstract放在class前面

abstract class BaseFragment(){}

抽象类和接口区别在于抽象类即可以定义抽象方法和属性,也可以定义非抽象方法和属性

abstract class Person{
    abstract fun eat()
    abstract val name:String
    fun work(){
        println("work")
    }
    val info  = "person info"
}
class Student:Person(){
    override fun eat() {
        println("student eat")
    }
    override val name: String="name"
}

Class和File区别

Kotlin中可以不用依靠类的作用域,创建一个属性和方法。将顶层属性 扩展函数 顶层函数放在一个File文件中,可以在任意需要的地方访问它们。或在一个文件中定义多个类或接口,直接定义成File。普通类直接定义成Class文件。在Kotlin类的作用域外部,添加任意的属性 方法 接口 类 Kotlin编译器会自动识别将Class文件转换成.kt的File文件

总结

类与继承对高阶函数和Lambda表达式是更容易理解一点。在平时开发中我们必然遇到并切运用都项目中去。