Kotlin (类和继承)

类和继承

在 Kotlin 中类用 class 时:

class Invoice {
}

类的声明包含类名,类头(指定类型参数,主构造函数等等),以及类主体,用大括号包裹。类头和类体是可选的;如果没有类体可以省略大括号。

class Empty

构造函数

在 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 customerKry = name.toUpperCase()
}

事实上,声明属性并在主构造函数中初始化,在 Kotlin 中有更简单的语法:

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

就像普通的属性,在主构造函数中的属性可以是可变的(var)或只读的(val)。

如果构造函数有注解或可见性声明,则 constructor 关键字是不可少的,并且可见性应该在前:

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

参看可见性

二级构造函数

类也可以有二级构造函数,需要加前缀 constructor:

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

如果类有主构造函数,每个二级构造函数都要,或直接或间接通过另一个二级构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:

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

如果一个非抽象类没有声明构造函数(主构造函数或二级构造函数),它会产生一个没有参数的构造函数。该构造函数的可见性是 public 。如果你不想你的类有公共的构造函数,你就得声明一个拥有非默认可见性的空主构造函数:

class DontCreateMe private constructor () {
}

注意:在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。这使得 Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库。

class Customer(val customerName: String = "")

创建类的实例

我们可以像使用普通函数那样使用构造函数创建类实例:

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

注意 Kotlin 没有 new 关键字。

创建嵌套类、内部类或匿名类的实例参见嵌套类

类成员

类可以包含:

-- 构造函数和初始化代码块

-- 函数

-- 属性

-- 内部类

-- 对象声明

继承

Kotlin 中所有的类都有共同的父类 Any ,它是一个没有父类声明的类的默认父类:

class Example // 隐式继承于 Any

Any 不是 java.lang.Object;事实上它除了 equals(),hashCode()以及toString()外没有任何成员了。参看Java interoperability了解更多详情。

声明一个明确的父类,需要在类头后加冒号再加父类:

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相反:它允许别的类继承这个类。默认情形下,kotlin 中所有的类都是 final ,对应 Effective Java :Design and document for inheritance or else prohibit it.

复写方法

像之前提到的,我们在 kotlin 中坚持做明确的事。不像 java ,kotlin 需要把可以复写的成员都明确注解出来,并且重写它们:

open class Base {
    open fun v() {}
    fun nv() {}
}

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

对于 Derived.v() 来说override注解是必须的。如果没有加的话,编译器会提示。如果没有open注解,像 Base.nv() ,在子类中声明一个同样的函数是不合法的,要么加override要么不要复写。在 final 类(就是没有open注解的类)中,open 类型的成员是不允许的。

标记为override的成员是open的,它可以在子类中被复写。如果你不想被重写就要加 final:

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

等等!我现在怎么hack我的库?!

有个问题就是如何复写子类中那些作者不想被重写的类,下面介绍一些令人讨厌的方案。

我们认为这是不好的,原因如下:

最好的实践建议你不应给做这些 hack

人们可以用其他的语言成功做到类似的事情

如果你真的想 hack 那么你可以在 java 中写好 hack 方案,然后在 kotlin 中调用 (参看java调用),专业的构架可以很好的做到这一点

复写属性

复写属性与复写方法类似,在一个父类上声明的属性在子类上被重新声明,必须添加override,并且它们必须具有兼容的类型。每个被声明的属性都可以被一个带有初始化器的属性或带有getter方法的属性覆盖

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

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

您还可以使用var属性覆盖一个val属性,但反之则不允许。这是允许的,因为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
}

复写规则

在 kotlin 中,实现继承通常遵循如下规则:如果一个类从它的直接父类继承了同一个成员的多个实现,那么它必须复写这个成员并且提供自己的实现(或许只是直接用了继承来的实现)。为表示使用父类中提供的方法我们用 super<Base>表示:

open class A {
    open fun f () { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // 接口的成员变量默认是 open 的
    fun b() { print("b") }
}

class C() : A() , B {
    // 编译器会要求复写f()
    override fun f() {
        super<A>.f() // 调用 A.f()
        super<B>.f() // 调用 B.f()
    }
}

可以同时从 A 和 B 中继承方法,而且 C 继承 a() 或 b() 的实现没有任何问题,因为它们都只有一个实现。但是 f() 有俩个实现,因此我们在 C 中必须复写 f() 并且提供自己的实现来消除歧义。

抽象类

一个类或一些成员可能被声明成 abstract 。一个抽象方法在它的类中没有实现方法。记住我们不用给一个抽象类或函数添加 open 注解,它默认是带着的。

我们可以用一个抽象成员去复写一个带 open 注解的非抽象方法。

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

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

伴随对象

在 kotlin 中不像 java 或者 C# 它没有静态方法。在大多数情形下,我们建议只用包级别的函数。

如果你要写一个没有实例类就可以调用的方法,但需要访问到类内部(比如说一个工厂方法),你可以把它写成它所在类的一个成员(you can write it as a member of an object declaration inside that class)

更高效的方法是,你可以在你的类中声明一个伴随对象,这样你就可以像 java/c# 那样把它当做静态方法调用,只需要它的类名做一个识别就好了

密封类

密封类用于代表严格的类结构,值只能是有限集合中的某种类型,不可以是任何其它类型。这就相当于一个枚举类的扩展:枚举值集合的类型是严格限制的,但每个枚举常量只有一个实例,而密封类的子类可以有包含不同状态的多个实例。

声明密封类需要在 class 前加一个 sealed 修饰符。密封类可以有子类但必须全部嵌套在密封类声明内部、

sealed class Expr {
    class Const(val number: Double) : Expr()
    class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
}

注意密封类子类的扩展可以在任何地方,不必在密封类声明内部进行。

使用密封类的最主要的的好处体现在你使用 when 表达式。可以确保声明可以覆盖到所有的情形,不需要再使用 else 情形。

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

推荐阅读更多精彩内容

  • 原文地址 Classes 类在Kotlin中使用class关键字被声明 类声明由类名和类头组成(指定它的类型参数,...
    CyrusChan阅读 604评论 0 3
  • 这篇文章,主要介绍kotlin中如何创建类,以及类的继承等等。在介绍这个类之前。不得不介绍一下kotlin中的超类...
    我小时候真的可狠了阅读 1,741评论 0 1
  • 面向对象编程(OOP) 在前面的章节中,我们学习了Kotlin的语言基础知识、类型系统、集合类以及泛型相关的知识。...
    Tenderness4阅读 4,431评论 1 6
  • 写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kot...
    胡奚冰阅读 1,415评论 5 11
  • 社交软件越多,想说的话却越少,即使要说,也不知道要找谁说,要怎么说,有些事有些话只能自己藏着,但时间长了,沙漠变成...
    兔纸被用了阅读 535评论 0 1