Kotlin学习之类与对象篇—类及继承


一. 类

Kotlin中的类用class关键字声明:

class Invoice{
}

类的声明包含类的名称、类头部(包括类的类型参数,主构造函数等)以及用大括号包围的类体。类头和类体都是可选项;如果类没有类体,大括号也可以省略。

class Empty


主构造函数

一个Kotlin类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头部的一部分,通常跟在类名(以及参数类型)后面

class Person constructor(firstName: String){
}

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

class Person(firstName: String){
}

主构造函数不能有任何代码。初始化的代码可以放到初始化块(initializer block)中,初始化块使用关键字init作为前缀。 在类实例初始化期间,初始化块以及类的属性按照它们在类中的顺序初始化。
下面这段代码:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)

    init {
        println("First initializer block that prints ${name}")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")
    }
}

使用如下代码获取一个实例:

InitOrderDemo demo = InitOrderDemo("hello")

打印结果如下:

First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

可以看到,属性初始化块是按照顺序一个一个初始化的。

注意:主构造函数里的参数可以在初始化块中被使用,也可以用来初始化类属性。

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

事实上,Kotlin有简洁的语法在主构造函数中声明及初始化属性:

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

主构造函数的属性可以声明为变量(var)或只读变量(val

如果主构造函数有注解或者可见性修饰符,constructor关键字就不能省略,并且修饰符要放在它前面:

class Customer public @Inject constructor(name: String) { //... }


次构造函数

次构造函数在类体中声明,也使用constructor关键字,如下:

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

如果类有主构造函数,那么次构造函数必须委托给主构造函数,可以直接委托或间接通过其它次构造函数委托。委托使用关键字this,如下:

class Student(var name: String) {

    constructor(name: String, age: Int) : this(name) {

    }

    constructor(name: String, age: Int, gender: Int) : this(name, age){

    }
}

注意:初始化块中的代码实际上会成为主构造函数的一部分。而委托给主构造函数是次构造函数的第一个语句,因此初始化块中的代码将先于次构造函数被执行。即使类没有主构造函数,委托仍然会隐式发生,初始化块仍然会先于次构造函数执行。

如果一个非抽象类没有声明任何构造函数,它将产生一个没有参数的主构造函数,其可见性为public。如果不想让某个类有public构造函数,需要声明一个空的非默认可见性主构造函数,如下:

class DontCreateMe private constructor () {
}

注意:在JVM中,如果主构造函数的所有参数都有默认值,编译器将使用默认值生成一个额外的无参构造函数。


创建类的实例

创建类的实例前面的文章中已经提到过。和Java的区别是不用new关键字。

val invoice = Invoice()
val customer = Customer("Joe Smith")


类的成员

类可以包含以下内容:
- 构造函数 和 初始化块
- 方法/函数
- 属性
- 嵌套类 和 内部类
- 对象声明


二. 继承

所有的Kotlin类都有一个共同的父类Any,它是所有没声明父类的类的默认父类。和Java中的java.lang.Object类不同的是,Any类有且只有equals(), hashCode()toString()三个成员。

要声明一个父类,我们把父类类型放在子类头部的后面,用:隔开

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

Kotlin类的open注解和Java中的final正好相反,它意味着允许其它类来继承此类。Kotlin中所有的类都是默认为final的,这种设计契合《Effective Java》中所阐述的:要么设计好被继承,要么禁止继承。

如果子类有主构造函数,那么在继承时必须立即使用父类的主构造函数来初始化父类。
如果子类没有主构造函数,那么其每个次构造函数都必须使用super关键字来初始化父类,或者通过委托其它次构造函数来完成这件事。需要注意子类中的次构造函数可以任意实现父类中不同的构造函数。

class MyView : View {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}


重写方法

正如上面所提及的,Kotlin力求清晰明确。可以被重写的成员和重写的成员都要明确地标注。

open class Base {
    open fun v() {} //可以被重写的方法用关键字 open 标注
    fun nv() {}
}
class Derived() : Base() {
    override fun v() {} //重写的方法用关键字 override 标注
}

如果方法 Derived.v()没有override注解,编译器将会报错。如果父类中的方法没有open注解,比如上面的Base.nv()方法,在其子类中定义同名方法将被视为非法。在一个final类中,成员的open注解将被视为无效。

一个被标注为 override 的成员默认是 open 的,它可以被其子类重写。如果想禁止 override 成员被重写,可以将其标注为final,如下:

open class AnotherDerived() : Base() {
    final override fun v() {}
}


重写属性

重写属性和重写方法方式一样

open class Employee {
    // Use "open" modifier to allow child classes to override this property
    open val baseSalary: Double = 30000.0
}

class Programmer : Employee() {
    // Use "override" modifier to override the property of base class
    override val baseSalary: Double = 50000.0
}

也可以重写属性的get和set方法:

open class Person(open var name: String, open var age: Int){

}

class Student : Person("", 0){
    override var age: Int = 10
        get() = if(field > 100) 100 else field
        set(value) {
            field = if(value > 0) value else throw IllegalArgumentException("age can not be zero or negative")
        }
}

fun main(args: Array<String>) {
    val student = Student()
    person.age = 111 
    println(student.age)  //将打印出100

    val student1 = Student()
    student1.age = -1    //将抛出异常
    println(student1.age) 
}

上面这段代码是在子类Student中重写父类Personage属性的getset方法,在get方法中限定获取到的age大小不超过100,在set方法中限定age不能为0或负值。

子类的初始化顺序

在子类的实例化过程中,初始化父类是其第一步,然后才执行子类的初始化逻辑

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") }
}

调用代码var derivedClass = Derived("hello", "world")初始化子类Derived,打印出来的结果如下:

Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10


调用父类中的属性和方法

在子类中可以通过关键字super来调用父类中的属性和方法,如下:

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }

    override val x: Int get() = super.x + 1
}

在子类的内部类中可以通过关键字[super + @ + 外部类名]来调用外部类父类的方法和属性,如下:

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

    inner class Baz {
        fun g() {
            super@Bar.f() // 调用Foo类中的方法f()
            println(super@Bar.x) // 调用Foo类中属性x的get方法
        }
    }
}


重写规则

在Kotlin中,如果一个类继承了多个父类,并且父类具有相同的成员,那么它必须重写这些成员并提供自己的实现方式。如果要调用某个父类中的成员,为了区分调用的成员来自哪个父类,可以使用关键字super<BaseClassName>来调用。

open class A { //父类A
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {  //接口B
    fun f() { print("B") } // 接口的成员默认是 open 的
    fun b() { print("b") }
}

class C() : A(), B { //继承类A和接口B
    // 编译器将要求必须重写方法f()
    override fun f() {
        super<A>.f() // 调用类A的方法A.f()
        super<B>.f() // 调用接口B的方法B.f()
    }
}


抽象类

抽象类使用abstract来声明,在Kotlin中,抽象类或抽象方法不需要用open来注释,因为它默认就是open的。
可以把一个非抽象open方法重写为抽象方法:

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

当然,抽象方法只能写在抽象类中。


伴生对象

这个之后再详细介绍。

一个知识点:Kotlin中没有static方法。

猜你喜欢

转载自blog.csdn.net/chenrenxiang/article/details/80899642