Kotlin-Anko学习(4) Kotlin语法-类、继承、抽象类

本系列文章来学习 Kotlin 和 Anko 插件 通过 Kotlin 开发一个 Android 项目。

Kotlin-Anko学习(1) Kotlin、Anko 介绍
Kotlin-Anko学习(2) Kotlin 语法基础-基本类型
Kotlin-Anko学习(3) Kotlin 语法基础-关键字 package、Import、if、when、for、while、return、break、continue
Kotlin-Anko学习(4) Kotlin语法-类、继承、抽象类
Kotlin-Anko学习(5) Kotlin语法-属性、字段、接口

Kotlin 类

与Java相同,类由 class 关键字声明。

类声明的组成

  • 类名 TestKot:
class TestKot{...}
  • 类头 在类名后声明属性、指定其类型参数、主构造函数等。 如
 /**
  * val 声明不变的属性,var声明可变属性
  * firstName  lastName 指定为String类型参数 age为Int类型参数
  * Person(...) 为主构造函数,是类头的一部分,如果没有类没有声明构造函数(primary、secondary都没有),系统会默认生成一个无惨构造函数,与java类似。
  */
class Person(val firstName: String, val lastName: String, var age: Int) {
    // ...
}
  • 类体 TestKot: class TestKot{...} 大括号中为类体部分,如果类没有类头,类体可以简写成class TestKot。类体中可以包含:

构造函数、属性初始化、初始化块

  1. Kotlin 中一个类可以包含一个主构造函数和一个或多个次构造函数,用关键字constructor修饰
  2. Kotlin 中属性初始化可以在类头中,也可以在类体中写,并且可以获得主构造函数的参数值
  3. Kotlin 中由于主构造函数在类头中,不能写初始化代码,所以Kotlin 通过一个关键字 init 来包含初始化代码块
  • 主构造函数(primary constructor ):
    如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。如下:
//省略constructor的写法,默认为public
class TestKot(name: String) {……}
//包含注解
class TestKot public @Inject constructor(name: String) { …… }
//声明主构造函数为私有
class TestKot private constructor(name: String){......}
  • 次构造函数(secondary constructor ):
    如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字。
class Constructors(name:String) {
    //第一个属性初始化
    val firstProperty = "First property: $name".also(::println)
    //第一个init初始化块
    init {
        println("Init first")
    }
     //次构造函数委托给次构造函数(间接)
    constructor(name:String,age: Int,weight:Int):this(name,age) {
        println("Constructor name = $name age = $age weight = $weight kg")
    }
    //次构造函数委托给主构造函数(直接)
    constructor(name:String,age: Int):this(name) {
        println("Constructor name = $name age = $age")
    }
     //第二个init初始化块
    init {
        println("Init second")
    }
     //第二个属性初始化
    val secondProperty = "Second property: $name ".also(::println)
}

fun main(args: Array<String>) {
    Constructors("张三")
    print("--------------------------------------------\n")
    Constructors("李四",16)
    print("--------------------------------------------\n")
    Constructors("王五",17,55)
}

运行结果:

First property: 张三
Init first
Init second
Second property: 张三 
--------------------------------------------
First property: 李四
Init first
Init second
Second property: 李四 
Constructor name = 李四 age = 16
--------------------------------------------
First property: 王五
Init first
Init second
Second property: 王五 
Constructor name = 王五 age = 17
Constructor name = 王五 age = 17 weight = 55 kg

从以上代码可以看出,属性初始化和init初始化代码块是按顺序执行的,次构造函数式在属性初始化和init执行完才开始的,所以可以把属性初始化和init初始化代码块当做主构造函数体来理解。这样构造函数部分就讲完了。

类的实例化

Kotlin 中类的实例化就像普通函数一样直接调用,如上面的main函数中的示例,不需要new与java不同,也没有new关键字。

Kotlin 继承

与java不同,kotlin中类的继承在类头后加冒号: “Current类:Base类”

Any 超类

Kotlin 中所有类都有一个共同的超类 Any,不同于java的Object类,只有三个方法代码如下:

public open class Any {

    public open operator fun equals(other: Any?): Boolean

    public open fun hashCode(): Int

    public open fun toString(): String
}

open关键字

open 标注与 Java 中 final 相反,它允许其他类从这个类继承。默认情况下,在 Kotlin 中所有的类都是 final,抽象类不需要用open修饰。

//超类型
open class Base(p: Int)
//基类
class Current(p: Int) : Base(p)

上面代码可以看出超类有构造函数的话,其基类可以(并且必须) 用(基类型的)主构造函数参数就地初始化。

super 关键字

super与java类似,用于超类属性、方法的实现,这里我们看派生类如何初始化超类的构造函数

//超类型
open class Base {
    constructor(name:String ,age:Int)
    constructor(name: String,age: Int,weight:Int):this(name,age)
}
//基类 示例1
class Current(name: String, age: Int) : Base(name, age) {
  //constructor(name: String,age: Int,weight:Int):super(name,age,weight)//报错
}
//基类 示例2
class Current(name: String,age: Int,weight:Int) : Base(name,age,weight) {
 //constructor(name: String,age: Int):super(name,age)//报错
}
//基类 示例3
class Current : Base {
    constructor(name: String,age: Int):super(name,age)
    constructor(name: String,age: Int,weight:Int):super(name,age,weight)
}

以上代码可以看出,超类没有主构造函数,基类可以通过主构造函数初始化超类的任意一个次构造函数,在此基础上,基类的次构造函数不能通过super来初始化超类的其他次构造函数,如示例1,2;基类也可以不声明主构造函数,通过次构造函数super来初始化对应的次构造函数,如示例3。

override 关键字

Kotlin 中 override 只能修饰父类显式标注可覆盖的成员(开放open),父类中没有显式标注的成员在其子类中不允许定义相同签名的函数。

覆盖方法

open class Base {
    open fun v() {}
    fun nv() {}
}
//示例1
class Bar1() : Base() {
    override fun v() {}
//     override  fun nv(){}//error:'nv' in 'Base' is final and cannot be overridden
//     fun nv(){}//error: 'nv' hides member of supertype 'Base' and needs 'override' modifier
}
//示例2
class Foo {
//    open val x: Int get() { return 1 } //Warning:'open' has no effect in a final class
//示例3
open class Bar2() : Base() {
    final override fun v() {}
}

示例1:说明父类的显式标注方法,在子类中可以通过override修饰的相同方法覆盖,对于没有开放的函数, 不论加不加 override,都不允许定义与父类相同签名的方法。
示例2:说明在一个 final 类中(没有用 open 标注的类),open修饰的方法没有用
示例3:说明子类重写的方法如果想在,子类的子类中不被继续重写,可以在override前用final 进行修饰。

覆盖属性

open class Foo {
    open val x: Int =1
    open val y: Int get(){return 5}
}
示例1
class Bar1 : Foo() {
    override val x: Int = 2
    init{
        print("x= $x \n")
        print("y= $y \n")
    }

}
示例2
class Bar2 : Foo(){
    override var x : Int = 3
    init{
        print("x= $x")
    }
}
fun main(args: Array<String>) {
    Bar1()
    Bar2()
}

输出结果:

x= 2 
x= 5
x= 3

重写属性的规则也是通过override进行修饰,并且派生类的重写的属性必须兼容超类的属性,示例1可以看出
声明的属性由具有初始化器的属性或者具有 getter 方法的属性覆盖 ,示例2可以看出用一个 var 属性覆盖一个 val 属性是可以的,本质上var的get方法覆盖了val的get方法,并且在派生类额外生成一个set方法。

调用超类的实现

调用超类中的方法与java相同,通过super关键字修饰,属性属kotlin独有的。

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
    init{
        println("x= $x")
    }
}
fun main(args: Array<String>) {
    Bar().f()
}

输出结果:

x= 2
Foo.f()
Bar.f()

以上就是派生类中调用超类具体的实现,跟java相似。

覆盖规则

既有继承又有接口的实现

Kotlin 中一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super<Base>

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

interface B {
    fun f() { println("B") } // 接口成员默认就是“open”的
    fun b() { println("b") }
}

class C() : A(), B {
    init{
        println("C()")
    }
    // 编译器要求覆盖 f():
    override fun f() {
        super<A>.f() // 调用 A.f()
        super<B>.f() // 调用 B.f()
  }
}
fun main(args: Array<String>) {
     C().f()
}

输出结果:

C()
A
B

首先在派生类既有继承又有接口,并且超类、接口中存在相同的方法,派生类必须重写该方法,并且通过
surper<A>.f() 尖括号中表明实现哪个父类的方法,并且这里说明一下kotlin中接口中的方法默认是open的。

内部类中访问外部类超类的实现

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() {  println("Bar.f()")}
    override val x: Int get() = 0
    
    inner class Baz {
        fun g() {
            super@Bar.f() // 调用 Foo 实现的 f()
            println(super@Bar.x) // 使用 Foo 实现的 x 的 getter
        }
    }
}
fun main(args: Array<String>) {
    Bar().Baz().g()
}

输出结果:

Foo.f()
1

内部类需要实现外部类的属性和方法,我们super@Outer().f()、super@Outer.x来实现。

抽象类

kotlin 中抽象类和java相同,通过 abstract 关键字来修饰,抽象类中可以包含抽象的方法或属性。

open class Base {
    open fun f() {}
}
abstract class Bar : Base() {
    abstract val x :Int
    override abstract fun f()
    open fun g(){println("Bar.g()")}
}
class Child() :Bar(){
    override var x: Int =2
    override fun g() {
        super.g()
        println("Child g()")
    }
    override fun f() {
       println("Child f()")
    }
}
fun main(args: Array<String>) {
  var child = Child()
    child.g()
    child.f()   
}

输出结果:

Bar.g()
Child g()
Child f()

上面的代码可以看出,非抽象的超类可以派生出抽象类,并且开放的方法可以被重写成抽象方法,抽象方法是不允许有方法体的,abstract 修饰的属性或方法默认是open的。这里与接口不同,接口中的方法默认抽象的也是默认开放的,抽象类中的方法只有抽象的方法才是默认open的。

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