kotlin程序员Kotlin编程

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

2017-05-22  本文已影响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#调用静态方法相同的语法:使用类名作为限定符来访问其成员。

上一篇下一篇

猜你喜欢

热点阅读