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

类(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#调用静态方法相同的语法:使用类名作为限定符来访问其成员。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容