Kotlin 类和继承

类声明

Kotlin使用class关键字声明类:

class Foo {
}

声明分为声明头和声明体。其中声明体是可选的,如下:

class Bar

构造函数

Kotlin构造函数分为主构造函数(primary constructor)和辅助构造函数(secondary constructor)。

主构造函数

主构造函数在类头中,紧跟类名之后。如果不修改主构造函数的可见性也不为主构造函数添加注解,那么constructor关键字是可选的。主构造函数不包含任何代码,但是主构造函数将会执行init关键字标记的代码块。并且主构造函数的形参可以在init代码块中访问,并用于初始化类成员变量:

class Person(name: String) {
    init {
        this.name = name.toUpperCase()
    }
}

// 带有可见性声明或者是注解的主构造函数,contructor 关键字不能省略!!!
class Person private @Inject constructor(name: String) {
    // ...
}

我们甚至可以直接在主构造函数中声明并初始化类属性(能做的也仅止于此了~)。如下:

class Person(val firstName: String, val lastName: String, var age: Int) {
    // ...
}

辅助构造函数

类可以在类体中定义若干个辅助构造函数。辅助构造函数使用constructor 标记:

class Person(name: String) {
    constructor(parent: Person, name: String): this(name) {
        parent.children.add(this)
    }
}

如上,如果函数定义了一个主构造函数,那么所有的辅助构造函数必须使用this调用主构造函数(直接调用或者通过调用其他辅助构造函数间接调用)。

如果没有主构造函数或者辅助构造函数被定义,编译器将自动生成无参主构造函数。主构造函数的默认访问权限为public,如果需要修改可见性,可以定义一个空的无参构造函数同时指定可见性。

class DontCreateMe private constructor() {
}

如果运行在JVM上,类的主构造函数的所有参数都有默认值,编译器将会自动生成无参构造函数,并使用默认参数值初始化。这有益于Kotlin类用于某些Java库中。

创建类的实例

Kotlin中没有new 关键字。所以相对于Java,省略掉new就可以了~ 如下:

val apple = Apple()
val person = Person("Tom")

类继承

类似于Java中的Object类,Any 类是Kotlin中所有其它类的默认基类。Any类不是Java中的Object类Any 类只包含equals()hashCode()toString()方法。

描述继承关系的方法与C++类似使用冒号,而不是使用Java中的“extend”或“implement”,Kotlin中没有这俩关键字。

Koltin中的类默认是final不可以被继承,对于需要被继承的类,必须显式地标记为open

  • 如果派生类有主构造函数,那么必须在派生类主构造函数调用基类的构造函数完成基类初始化。
  • 如果派生类没有主构造函数,那么就必须在派生类的每一个构造函数中都调用super()函数完成基类的初始化。
// 需要被继承的基类,必须用open标记!
open class Base(p: Int)

// 派生类有主构造函数,必须在主构造函数之后调用基类的构造函数
class Derived_1(p: Int): Base(p)

// 派生类没有主构造函数,必须在所有的辅助构造函数中调用基类的构造函数
class Derived_2 {
    constructor(p: Int): super(p)

    constructor(): super(0)
}

方法覆盖(override)

不同于Java中可选的注解,Kotlin中方法覆盖需要强制使用关键字openoverride

基类中被覆盖的方法必须使用open关键字标记,而派生类中覆盖基类方法的新方法必须使用override关键字标记:

open class Base {
    open fun f1() {}
    fun f2() {}
}

class Derived(): Base() {
    override fun f1() {}
}

如上,基类中只有f1()是可以被覆盖的,并且在派生类中覆盖f1()必须使用override关键字。

方法的标记总是不能和类的标记冲突,也即含有open函数的类必须也是open的!

如果派生类是open的,含有override标记的方法会默认成为open,这与之前的默认规则不同。如果不想让派生类中覆盖的方法进一步被覆盖,那就需要添加final关键字。如下:

open AnotherDerived(): Base() {
    final override fun f1() {}
}

属性覆盖

属性覆盖和方法覆盖差不多,同样的openoverride使用规则!

可以通过指定新的 get 方法,或提供初始值来覆盖属性。

可以使用var属性覆盖val属性,这相当于在派生类中新增一个 set 方法;但是不能使用val 属性覆盖var属性,这相当于继承了基类的 set 方法却无法再派生类中使用。

interface Foo {
    val count: Int
    val x: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
    override val x: Int get {...}
}

调用基类的方法

一句话!使用 super ~

如果是内部类需要访问外部类的基类。假设外部类名为 Outer,那么调用方法为super@Outer。如下:

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: String get() = "..."

    inner class Baz {
        fun g() {
            super@Bar.f() // Calls Foo's implementation of f()
            println(super@Bar.x) // Uses Foo's implementation of x's getter
        }
    }
}

覆盖规则

  1. 如果继承来的多个名称(来自接口或者基类)冲突,派生类必须覆盖这个方法;
  2. 在派生类中访问基类(或者接口)名称的实现,如果存在多个实现那么必须使用super<BaseType>语法指定基类。
open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // interface members are 'open' by default
    fun b() { print("b") }
}

class C() : A(), B {
    // The compiler requires f() to be overridden:
    override fun f() {
        super<A>.f() // call to A.f()
        super<B>.f() // call to B.f()
    }
}

抽象类

包含抽象方法的类必须是抽象类。

抽象类和抽象方法都是用abstract关键字标记。

和接口一样,抽象类和抽象接口默认为open

可以用一个抽象方法覆盖非抽象方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值