Kotlin类与对象篇(1)--类与继承(Inheritance)


欢迎关注 二师兄Kotlin
转载请注明出处 二师兄kotlin


对象

Kotlin中对象使用 class关键字来进行声明:

class Invoice {
}

类的声明由三部分组成:类名、类头(具体化的类型参数、主构造函数等)以及类体,被一堆花括号包围{}( curly braces)。类头和类体均是可选的。如果类没有类体,花括号也可以被删掉。

class Empty

构造函数(Constructors)

Kotlin中的类可以包含 一个主构造函数和多个次构造函数。而主构造函数是类头的一部分:紧随在类名之后(类型参数可选)。

class Person constructor(firstName: String) {
}

主构造函数不能包含任何代码。初始化代码必须放置在 初始化块(initializer blocks)中,通过关键字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}")
    }
}

注意主构造函数的参数可以在初始化块中使用。同样的也可以被用来进行属性的初始化声明。

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) { ... }

想了解更多细节,可以看Visibility Modifiers.

次构造函数

类也可以声明次要构造器,次要构造器以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)
    }
}

注意:在初始化代码块的代码高效地成为首要构造器的一部分。首要构造器的代理会作为次要构造器的第一行语句执行,所以在所有初始化代码块的代码都在次要构造器之前执行。甚至,如果类没有首要构造器,代理都会隐式地发生,初始化代码块依旧会执行:

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

如果,一个非抽象类没有声明任何构造器(包括首要和次要),类仍然会生成一个没有参数的首要构造器。该构造器的可见性是public、如果你不想你的类有一个public构造器,你需要声明一个空的首要构造器,并使用非默认可见性修饰符:

class DontCreateMe private constructor () {
}

NOTE:注意: 在JVM中,如果首要构造器的所有参数都有默认数值,编译器将会生成一个额外的无参数的构造器,该构造器会使用默认数值。这样使得更容易在Kotlin中使用库,例如Jackson或者JPA(通过无参数构造器创建类的实例)

class Customer(val customerName: String = "")

创建类的实例

为了创建类的实例,我们将如调用普通函数一样调用构造器:

val invoice = Invoice()
val customer = Customer("Joe Smith")
  • 注意Kotlin中不使用关键字new
    嵌套类、内部类、匿名内部类的创建在嵌套类中Nested classes
    进行介绍。

类的成员

类可以包含:

  • 构造函数和初始化块
  • 函数
  • 属性
  • 嵌套类和内部类
  • 对象声明

继承

在Kotlin中所有类都有一个共同的超类Any, 该超类是默认的,不需要超类的声明:

class Example // 隐式继承自Any

Any不是java.lang.Object;特别地,它除了equals(), hashCode() and toString()没有其他任何成员。请参考Java interoperability
(Java互通性)章节。

为了声明一个显式的超类,我们将类型放置在类头后的冒号后面:

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

open注解在类中是与java中的final相反的内容:open允许其他类继承自该类。默认的,在Kotlin中所有的类都是final(重点),这对应于Effective Java
, Item 17: Design and document for inheritance or else prohibit it.

重写方法

正如我们之前提到的,我们坚持在Kotlin中让事情都是显式的。不像Java,Kotlin对于覆盖的成员(这些成员需要有open修饰符)需要显式的注释:

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

override 注解需要修饰Derived.v(). 如果该注解遗漏,编译器就会报错。如果一个函数没有open注解,类似Base.nv(), 在子类中声明相同签名的方法就是非法的,无论是否有overide。在一个final类中(即,一个没有open注解的类), 禁止拥有open的成员(open修饰符无效).

overide标记的成员其本身就是open的,即,该成员可以在子类被重写。如果你想禁止“再重写”(重写父类的方法继续被子类重写),可以使用final关键字:

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

属性重写

与方法重写类似,在父类中已经声明的属性,如果要在一个派生类(derived class)中再次声明, 则必须使用override打头,而且他们必须类型可以兼容。一个拥有初始化或者getter方法的属性都可以去重写旧属性。

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

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

你可以用一个var属性去重写一个val属性,但是,但是反过来则不行(vice versa)。这样被允许的原因是一个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
}

调用父类实现

派生类中的代码通过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关键字: super@Outer:

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: Int get() = 0
    
    inner class Baz {
        fun g() {
            super@Bar.f() // Calls Foo's implementation of f()
            println(super@Bar.x) // Uses Foo's implementation of x's getter
        }
    }
}

重写规则

在Kotlin中,继承的实现被这样一条规则所限制:如果一个类继承了他的多个超类中的同一个函数的多个实现,那么他必须重写这个函数且提供自己的实现(比如,使用所继承实现中的某一个实现)。为了表示你到底是用了哪个超类中哪个实现,我们可以使用 尖括号(angle brackets)包围的超类类名来标记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()
    }
}

继承AB没有问题,而且对于在继承类C中对于函数a()b()的实现也没有问题。但是对于函数f(),对于C来说我们有两个实现,所以我们必须去重写f()并且提供自己的实现来消除歧义(eliminates the ambiguity)。

抽象类

类和他的一些成员可以声明成abstract。一个抽象成员不可以包含实现。注意我们不需要用open来标注一个抽象类或者他的函数,默认就是open(it goes without saying 不用说)。

我们可用一个抽象的成员来重写一个非抽象、但开放的成员。

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

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

友元对象(Companion Objects)

在Kotlin中,不想java或者C#,类没有静态方法。大多数情况下,更推荐使用 包级方法(package-level functions )来替代。

如果你需要写这样一个函数,他可以访问一个类的内部但并非通过类对象实例(比如,一个工厂方法),你可以把它写成一个object declaration成员放在类中。

更具体地说(Even more specifically ),如果你在类中声明了一个

companion object,你将可以实现像调用Java/C#中静态方法那样的调用语法来调用companion object的成员,只需要类名作为标识。

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

推荐阅读更多精彩内容