美文网首页kotlin程序员Kotlin编程
Kotlin面向对象之类与继承(Classes and Inhe

Kotlin面向对象之类与继承(Classes and Inhe

作者: 已迁至知乎_此不再维护 | 来源:发表于2017-05-22 10:57 被阅读36次

    类(Classes)

    在Kotlin中,类使用class关键字声明:

    class Invoice {
    }
    

    类的声明由类名,类头(指定其类型参数,主构造函数等)和类体组成,类体由大括号括起来。类头和类体都是可选的; 如果一个类没有类体,则可以省略花括号,如下:

    class Empty
    

    构造器(Constructors)

    在Kotlin中,一个类可以有一个主构造器和若干个副构造器。主构造器作为类头的一部分,紧跟类名:

    class Person constructor(firstName: String) {
    }
    

    如果主构造器没有被其他注解或可见修饰符修饰,则constructor关键字可以省略:

    class Person(firstName: String) {
    }
    

    主构造器不能包含任何的代码片段。主构造器声明的参数的的初始化工作可以放在初始化块中完成,初始化块由init关键字做为前缀:

    class Customer(name: String) {
        init {
            logger.info("Customer initialized with value ${name}")
        }
    }
    

    主构造器的参数不仅可以在初始化代码块中初始化,它们也可以被用于类体中其他属性的初始化:

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

    事实上,在主构造器中声明并初始化若干属性,Kotlin有着更加简洁的语法:

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

    与常规属性类似,主构造函数中声明的属性可以可变(var)的也可以是只读(read-only)的。

    若主构造器被注解或可见性操作符修饰,则constructor关键字不可省略,修饰符在constructor关键字的前面:

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

    副构造器(Secondary Constructors)

    类也可以通过constructor关键字声明副构造器:

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

    如果一个类具有主构造器,则每个副构造器都应该调用这个主构造器,或直接调用、或通过其他副构造器调用。一个类中,调用其他构造器通过使用this关键字完成:

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

    如果一个非抽象类没有声明任何构造器,则系统将为其自动生成一个public的无参构造器。如果你不想让你的类具有一个public的构造器,你需要手动声明一个private的非默认主构造器:

    class DontCreateMe private constructor () {
    }
    

    注意:在JVM虚拟机中,如果主构造器的所有参数都有默认值,编译器将额外生成一个无参的构造器,该构造器将使用主构造器中的默认值。这使Kotlin的使用变的更加容易,在使用诸如Jackson或JPA的库时,可以通过无参数构造函数创建类实例:

    class Customer(val customerName: String = "")
    

    创建类的实例(Creating instances of classes)

    要创建一个类的实例,我们可以通过调用构造器的方式来完成,就像调用一个常规函数一样:

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

    注意:Kotlin中没有new关键字。

    关于嵌套类、内部类以及匿名内部类的创建方式,在Nested Classes介绍。

    类成员(Class Members)

    一个类可以包含:

    1. 构造器与初始化块
    2. 函数Functions
    3. 属性Properties
    4. 嵌套和内部类Nested and Inner Classes
    5. 对象声明Object Declarations

    对于新知识点,将后面的内容中逐步介绍到。

    继承(Inheritance)

    Kotlin中的所有类都有一个公用的基类:Any,该类是没有显式声明继承关系的类的父类:

    class Example // Implicitly inherits from Any
    

    Any类不是java.lang.Object类:事实上,Any类除了equals()、hashCode()、toString()方法室外没有任何的其他成员。关于更多细节,参见这里

    为了明确表达继承关系,可以将父类型放置在类头的最后边,并以冒号分割:

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

    如果子类有主构造器,则基类必须在子类的主构造器右侧完成初始化,使用子类主构造器中的参数。

    如果子类没有主构造器,则每一个副构造器必须使用super关键字来完成基类的初始化,或者通过调用其他的副构造器完成上述操作(基类的初始化)。注意:此时,不同的副构造器可以调用不同的基类构造器:

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

    Kotlin中,open注解的含义和Java中的final是相反的:open允许其他类作为该类的子类。默认地,Kotlin中的所有类(包括方法)都被隐式声明为final。

    方法重载(Overriding Methods)

    如前所述,使用Kotlin应坚持显式声明(因为默认都是final)。因此,不像Java,Kotlin需要对可覆盖成员进行显式注解(我们称之为open)并重写:

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

    Derived类的v()函数由override注解修饰。如果该方法没有被该注解修饰,则编译器将会报错。如果一个方法没有被open修饰,向Basenv()方法,在子类中声明一个同样的方法签名将是非法的,无论子类中的方法是否被override修饰。在一个final类中,即没有被open修饰的类,用open修饰该类的成员是被禁止的。

    一个被override修饰的成员,默认是open的,它可以被子类重写。如果你想禁止子类重写该override方法,应使用final关键字显式声明:

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

    重写属性(Overriding Properties)

    属性的重写与方法的重写非常类似,父类声明的属性,若在子类中被重新声明,则应以override修饰,且它们必须互相兼容。每个被声明的属性可以通过属性初始化或该属性的getter方法被重写:

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

    可以将val属性重写为var属性,反之则不行。val属性仅有相匹配的getter方法,但是将其重写为var属性需要子类额外声明一个setter方法。

    注意:在主构造器中,override关键字作为可以属性声明的一部分:

    interface Foo {
        val count: Int
    }
    
    class Bar1(override val count: Int) : Foo
    
    class Bar2 : Foo {
        override var count: Int = 0
    }
    

    重写规则(Overriding Rules)

    在Kotlin中,实现继承需要遵循以下规则:如果一个类从其直接父类中继承了同一成员的多个实现,则它必须覆盖该成员并提供自己的实现(当然,也可以使用其中一个父类的实现)。为了在自己的实现中表示父类的实现,我们使用super关键字,并在尖括号中使用父类名称进行限定,也就是super<Base>:

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

    关于对A和B的继承,我们对a()和b()没有异议,因为C只继承了每些个函数的一个实现。 但是对于f(),我们从C继承了两个实现,因此我们必须在C中重写f(),并提供我们自己的实现来消除歧义。

    抽象类(Abstract Classes)

    一个类和它的成员可以被abstract关键字修饰。一个抽象成员在该类中不能有其实现。需要注意的是我们不必对一个抽象类或抽象方法声明open,这是毫无疑问的。

    在一个抽象类中,我们可以重写一个非抽象的但open的方法(也就是将一个非抽象方法重写为抽象方法):

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

    伴生对象(Companion Objects)

    Kotlin不像Java或C#,类没有静态方法。常见的,推荐仅适用包级函数来代替。

    如果需要编写一个不需要类实例就可以调用的函数,且需要访问一个类的内部(例如,一个工厂方法),则可以将其作为类内部的Object Declarations成员。

    更具体地说,如果您在类中声明了一个伴生对象,则可以使用与在Java / C#调用静态方法相同的语法:使用类名作为限定符来访问其成员。

    相关文章

      网友评论

        本文标题:Kotlin面向对象之类与继承(Classes and Inhe

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