Kotlin学习笔记:类与接口

作者: 小村医 | 来源:发表于2019-07-10 23:58 被阅读9次

    一、类

    使用关键字 class 声明类

    class Invoice { ... }
    

    类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的; 如果一个类没有类体,可以省略花括号。

    构造函数

    在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后。

    class Person constructor(firstName: String) { ... }
    

    如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
    主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中。
    如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:

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

    次构造函数

    类也可以声明前缀有 constructor的次构造函数:

    class Person {
        var children: MutableList<Person> = mutableListOf<Person>();
        constructor(parent: Person) {
            parent.children.add(this)
        }
    }
    

    如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:

    class Person(val name: String) {
        var children: MutableList<Person> = mutableListOf<Person>();
        constructor(name: String, parent: Person) : this(name) {
            parent.children.add(this)
        }
    }
    

    初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,

    创建类的实例

    创建一个类的实例,我们就像普通函数一样调用构造函数:

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

    注意 Kotlin 并没有 new 关键字。

    继承

    在 Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:

    class Example // 从 Any 隐式继承
    

    要声明一个显式的超类型,我们把类型放到类头的冒号之后:

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

    如果派生类有一个主构造函数,其基类型可以(并且必须) 用基类的主构造函数参数就地初始化。

    覆盖方法

    Kotlin 对于可覆盖的成员(我们称之为开放)以及覆盖后的成员需要显式修饰符:

    open class Base {
        open fun v() { ... }
        fun nv() { ... }
    }
    class Derived() : Base() {
        override fun v() { ... }
    }
    

    Derived.v() 函数上必须加上 override 修饰符。如果没写,编译器将会报错。 如果函数没有标注 open 如 Base.nv(),那么子类中不允许定义相同签名的函数, 不论加不加 override。将 open 修饰符添加到 final 类(即没有 open 的类)的成员上不起作用。

    标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用 final 关键字:

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

    覆盖属性

    属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter 方法的属性覆盖。

    open class Foo {
        open val x: Int get() { …… }
    }
    ​
    class Bar1 : Foo() {
        override val x: Int = ……
    }
    

    你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个 val 属性本质上声明了一个 getter 方法

    派生类初始化顺序

    在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。

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

    基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员。

    调用超类实现

    派生类中的代码可以使用 super 关键字调用其超类的函数与属性访问器的实现
    在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer:

    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 的 getter
            }
        }
    }
    

    抽象类

    类以及其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用 open 标注一个抽象类或者函数

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

    二、接口

    使用关键字 interface 来定义接口

    interface MyInterface {
        fun bar()
        fun foo() {
          // 可选的方法体
        }
    }
    

    实现接口

    一个类或者对象可以实现一个或多个接口。

    class Child : MyInterface {
        override fun bar() {
            // 方法体
        }
    }
    

    接口中的属性

    可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现。

    interface MyInterface {
        val prop: Int // 抽象的
    
        val propertyWithImplementation: String
            get() = "foo"
    
        fun foo() {
            print(prop)
        }
    }
    
    class Child : MyInterface {
        override val prop: Int = 29
    }
    

    解决覆盖冲突

    实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如

    interface A {
        fun foo() { print("A") }
        fun bar()
    }
    ​
    interface B {
        fun foo() { print("B") }
        fun bar() { print("bar") }
    }
    ​
    class C : A {
        override fun bar() { print("bar") }
    }
    ​
    class D : A, B {
        override fun foo() {
            super<A>.foo()
            super<B>.foo()
        }
    ​
        override fun bar() {
            super<B>.bar()
        }
    }
    

    相关文章

      网友评论

        本文标题:Kotlin学习笔记:类与接口

        本文链接:https://www.haomeiwen.com/subject/ahyhhctx.html