Kotlin語(yǔ)法三 面向?qū)ο?/h1>

1. 類

Kotlin 中使用關(guān)鍵字class 聲明類

class Person {
}

類聲明由類名、類頭(指定其類型參數(shù)、主 構(gòu)造函數(shù)等)和由大括號(hào)包圍的類體構(gòu)成。類頭和類體都是可選的; 如果一個(gè)類沒(méi)有類體,可以省略花括號(hào)。

class Person 

2.構(gòu)造函數(shù)

在 Kotlin 中的一個(gè)類可以有一個(gè)主構(gòu)造函數(shù)和一個(gè)或多個(gè)次構(gòu)造函數(shù)。主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(和可選的類型參數(shù))后。

class Person constructor(firstName: String) {
}

如果主構(gòu)造函數(shù)沒(méi)有任何注解或者可見(jiàn)性修飾符,可以省略這個(gè) constructor 關(guān)鍵字。

class Person(firstName: String) {
}

主構(gòu)造函數(shù)不能包含任何的代碼。初始化的代碼可以放 到以 init 關(guān)鍵字作為前綴的初始化塊(initializer blocks)中:

class Person ( name:String, age:Int){
 constructor(firstName: String) {
    init {
        println("firstName is $firstName")
    }
}

主構(gòu)造的參數(shù)可以在初始化塊中使用。它們也可以在 類體內(nèi)聲明的屬性初始化中使用:

class Person ( name:String, age:Int){
    val name:String = name
    val age:Int = age
}

如果主構(gòu)造函數(shù)中定義的參數(shù)使用 val 或者 var 修飾,則會(huì)創(chuàng)建與這個(gè)參數(shù)同名的成員變量,并使用傳入的參數(shù)值初始化這個(gè)成員變量。

class Person (val name:String,val age:Int)//等價(jià)于上面的代碼

如果類有一個(gè)主構(gòu)造函數(shù),每個(gè)次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù), 可以直接委托或者通過(guò)別的次構(gòu)造函數(shù)間接委托。委托到同一個(gè)類的另一個(gè)構(gòu)造函數(shù)用 this 關(guān)鍵字即可:

class Person ( name:String, age:Int){
    init {
        println("name is $name,age is $age")
    }
    constructor(name:String,age:Int,id:Int):this(name,age){
        println("id is $id")
    }
}

3.創(chuàng)建類的實(shí)例

    val person= Person("mlk",28,1)//name is mlk,age is 28
                                //id is 1
    person.age
    person.name

4.屬性

聲明一個(gè)屬性的完整語(yǔ)法

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

其初始器(initializer)、getter 和 setter 都是可選的。屬性類型如果可以從初始器 (或者從其 getter 返回值,如下文所示)中推斷出來(lái),也可以省略。
val定義只讀屬性,var定義可讀寫屬性。

我們可以編寫自定義的訪問(wèn)器,非常像普通函數(shù),剛好在屬性聲明內(nèi)部。這里有一個(gè)自定義 getter 的例子:

class Person(val name: String) {
    val isEmpty: Boolean //自定義訪問(wèn)器
        get() = name.length == 0
}
val person= Person("")
println("${person.isEmpty}")//true

4.1幕后字段

首先要明確什么是字段,什么是屬性,其實(shí)這一點(diǎn)在 C# 語(yǔ)言當(dāng)中有更好的體現(xiàn):

  • 字段是實(shí)際存儲(chǔ)在內(nèi)存中的變量,并且是私有的可以直接訪問(wèn)的變量,通常命名前帶有“_”符號(hào)
  • 屬性是不占用實(shí)際內(nèi)存的,它提供對(duì)字段的訪問(wèn)方法,實(shí)際是對(duì)字段和訪問(wèn)方法的封裝,因此屬性會(huì)有只讀、只寫、讀/寫等類型

而在 Kotlin 中,屬性既可以直接存儲(chǔ)值,也可以利用字段來(lái)存儲(chǔ)值的,但是這里字段沒(méi)有顯式表達(dá)出來(lái),因此叫做幕后字段,幕后字段的產(chǎn)生是有條件的,必須滿足下面兩個(gè)條件之一:

  • 屬性至少有一個(gè)訪問(wèn)器采用默認(rèn)實(shí)現(xiàn)
  • 自定義訪問(wèn)器通過(guò) field 引用幕后字段
class Person{
    var name: String =""
        get() = field
        set(value) {
            field = value //使用field引用幕后字段
        }
}

幕后字段是非常重要的,因?yàn)樵谠趯傩缘?get 和 set 方法中,不能直接使用該屬性,否則會(huì)發(fā)生棧溢出錯(cuò)誤,這時(shí)就只能用幕后字段來(lái)操作相應(yīng)的值。

4.2幕后屬性

class Person{
    var _name:String = ""
    var name: String
        get() = this._name
        set(value) {
            this._name = value
        }
}

4.3 編譯期常量

已知值的屬性可以使用 const 修飾符標(biāo)記為 編譯期常量。

4.4 延遲初始化屬性

一般地,屬性聲明為非空類型必須在構(gòu)造函數(shù)中初始化。 然而,這經(jīng)常不方便。例如:屬性可以通過(guò)依賴注入來(lái)初始化, 或者在單元測(cè)試的 setup 方法中初始化。 這種情況下,你不能在構(gòu)造函數(shù)內(nèi)提供一個(gè)非空初始器。 但你仍然想在類體中引用該屬性時(shí)避免空檢查。

為處理這種情況,你可以用 lateinit 修飾符標(biāo)記該屬性:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // 直接解引用
    }
}

該修飾符只能用于在類體中(不是在主構(gòu)造函數(shù)中)聲明的 var 屬性,并且僅當(dāng)該屬性沒(méi)有自定義 getter 或 setter 時(shí)。該屬性必須是非空類型,并且不能是原生類型。

在初始化前訪問(wèn)一個(gè) lateinit 屬性會(huì)拋出一個(gè)特定異常,該異常明確標(biāo)識(shí)該屬性被訪問(wèn)及它沒(méi)有初始化的事實(shí)。

4.5 可觀察屬性

Delegates.observable()
接受兩個(gè)參數(shù):初始值和修改時(shí)處理程序(handler)。 每當(dāng)我們給屬性賦值時(shí)會(huì)調(diào)用該處理程序(在賦值執(zhí)行)。它有三個(gè)參數(shù):被賦值的屬性、舊值和新值:

package cn.malinkang.kotlin

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$prop,$old,$new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first" 
    user.name = "second"
}
//輸出結(jié)果
//var cn.malinkang.kotlin.User.name: kotlin.String,<no name>,first
//var cn.malinkang.kotlin.User.name: kotlin.String,first,second

4.6 把屬性儲(chǔ)存在映射中

package cn.malinkang.kotlin

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

fun main(args: Array<String>) {
    val user = User(mapOf(
            "name" to "John Doe",
            "age" to 25
    ))
    println(user.name) //John Doe
    println(user.age)// 25
}

4.7 代理屬性

class Example {
    var p: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name} in $thisRef.'")
    }
}
val e = Example()
println(e.p)
//輸出Example@33a17727, thank you for delegating ‘p’ to me!

5.繼承

open class Base(p: Int) //父類

class Derived(p: Int) : Base(p) //子類

類上的 open 標(biāo)注與 Java 中 final 相反,它允許其他類從這個(gè)類繼承。默認(rèn)情況下,在 Kotlin 中所有的類都是 final。

如果該類有一個(gè)主構(gòu)造函數(shù),其基類型并且必須用基類型的主構(gòu)造函數(shù)參數(shù)就地初始化。

如果類沒(méi)有主構(gòu)造函數(shù),那么每個(gè)次構(gòu)造函數(shù)必須使用 super 關(guān)鍵字初始化其基類型,或委托給另一個(gè)構(gòu)造函數(shù)做到這一點(diǎn)。 注意,在這種情況下,不同的次構(gòu)造函數(shù)可以調(diào)用基類型的不同的構(gòu)造函數(shù):

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

5.1.覆蓋方法

我們之前提到過(guò),Kotlin 力求清晰顯式。與 Java 不同,Kotlin 需要顯式標(biāo)注可覆蓋的成員(我們稱之為開(kāi)放)和覆蓋后的成員:

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

Derived.v() 函數(shù)上必須加上 override標(biāo)注。如果沒(méi)寫,編譯器將會(huì)報(bào)錯(cuò)。 如果函數(shù)沒(méi)有標(biāo)注 open 如 Base.nv(),則子類中不允許定義相同簽名的函數(shù), 不論加不加 override。在一個(gè) final 類中(沒(méi)有用 open 標(biāo)注的類),開(kāi)放成員是禁止的。

標(biāo)記為 override 的成員本身是開(kāi)放的,也就是說(shuō),它可以在子類中覆蓋。如果你想禁止再次覆蓋,使用 final 關(guān)鍵字:

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

5.2 覆蓋屬性

屬性覆蓋與方法覆蓋類似;在超類中聲明然后在派生類中重新聲明的屬性必須以 override 開(kāi)頭,并且它們必須具有兼容的類型。每個(gè)聲明的屬性可以由具有初始化器的屬性或者具有 getter 方法的屬性覆蓋。

open class Foo {
    open val x: Int =0
}

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

你也可以用一個(gè) var 屬性覆蓋一個(gè) val 屬性,但反之則不行。這是允許的,因?yàn)橐粋€(gè) val 屬性本質(zhì)上聲明了一個(gè) getter 方法,而將其覆蓋為 var 只是在子類中額外聲明一個(gè) setter 方法。

請(qǐng)注意,你可以在主構(gòu)造函數(shù)中使用 override 關(guān)鍵字作為屬性聲明的一部分。

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}

5.3 覆蓋規(guī)則

在 Kotlin 中,實(shí)現(xiàn)繼承由下述規(guī)則規(guī)定:如果一個(gè)類從它的直接超類繼承相同成員的多個(gè)實(shí)現(xiàn), 它必須覆蓋這個(gè)成員并提供其自己的實(shí)現(xiàn)(也許用繼承來(lái)的其中之一)。 為了表示采用從哪個(gè)超類型繼承的實(shí)現(xiàn),我們使用由尖括號(hào)中超類型名限定的 super,如 super<Base>:

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

interface B {
    fun f() { print("B") } // 接口成員默認(rèn)就是“open”的
    fun b() { print("b") }
}

class C() : A(), B {
    // 編譯器要求覆蓋 f():
    override fun f() {
        super<A>.f() // 調(diào)用 A.f()
        super<B>.f() // 調(diào)用 B.f()
  }
}

同時(shí)繼承 A 和 B 沒(méi)問(wèn)題,并且 a() 和 b() 也沒(méi)問(wèn)題因?yàn)?C 只繼承了每個(gè)函數(shù)的一個(gè)實(shí)現(xiàn)。 但是 f() 由 C 繼承了兩個(gè)實(shí)現(xiàn),所以我們必須在 C 中覆蓋 f() 并且提供我們自己的實(shí)現(xiàn)來(lái)消除歧義。

6.抽象類

類和其中的某些成員可以聲明為 abstract。 抽象成員在本類中可以不用實(shí)現(xiàn)。 需要注意的是,我們并不需要用 open 標(biāo)注一個(gè)抽象類或者函數(shù)——因?yàn)檫@不言而喻。
我們可以用一個(gè)抽象成員覆蓋一個(gè)非抽象的開(kāi)放成員

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

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

7.接口

接口定義

interface MyInterface {
    fun bar()
    fun foo() {
      // 可選的方法體
    }
}

接口實(shí)現(xiàn)

class Child : MyInterface {
    override fun bar() {
        // 方法體
    }
}

你可以在接口中定義屬性。在接口中聲明的屬性要么是抽象的,要么提供訪問(wèn)器的實(shí)現(xiàn)。在接口中聲明的屬性不能有幕后字段(backing field),因此接口中聲明的訪問(wèn)器不能引用它們。

interface MyInterface {
    val prop: Int // 抽象的

    val propertyWithImplementation: String
        get() = "foo"

    fun foo() {
        print(prop)
    }
}

class Child : MyInterface {
    override val prop: Int = 29
}

8.可見(jiàn)性修飾符

  • private:意味著只在這個(gè)類內(nèi)部(包含其所有成員)可見(jiàn);
  • protected:這個(gè)類內(nèi)部及其子類中可見(jiàn)。
  • internal:能見(jiàn)到類聲明的 本模塊內(nèi) 的任何客戶端都可見(jiàn)其 internal 成員;
  • public:能見(jiàn)到類聲明的任何客戶端都可見(jiàn)其 public 成員。

Kotlin 中外部類不能訪問(wèn)內(nèi)部類的 private 成員。
如果你覆蓋一個(gè) protected 成員并且沒(méi)有顯式指定其可見(jiàn)性,該成員還會(huì)是 protected 可見(jiàn)性。

open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // 默認(rèn) public
    
    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可見(jiàn)
    // b、c、d 可見(jiàn)
    // Nested 和 e 可見(jiàn)

    override val b = 5   // “b”為 protected
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可見(jiàn)
    // o.c 和 o.d 可見(jiàn)(相同模塊)
    // Outer.Nested 不可見(jiàn),Nested::e 也不可見(jiàn)
}

9.數(shù)據(jù)類

我們經(jīng)常創(chuàng)建一些只保存數(shù)據(jù)的類。在這些類中,一些標(biāo)準(zhǔn)函數(shù)往往是從數(shù)據(jù)機(jī)械推導(dǎo)而來(lái)的。在 Kotlin 中,這叫做 數(shù)據(jù)類 并標(biāo)記為 data

data class User(val name: String, val age: Int)

編譯器自動(dòng)添加如下方法:

  • equals()、hashCode()方法
  • toString()格式是 "User(name=John, age=42)"
  • componentN()函數(shù) 按聲明順序?qū)?yīng)于所有屬性,
  • copy() 函數(shù)

如果這些函數(shù)中的任何一個(gè)在類體中顯式定義或繼承自其基類型,則不會(huì)生成該函數(shù)。
為了確保生成的代碼的一致性和有意義的行為,數(shù)據(jù)類必須滿足以下要求:

  • 主構(gòu)造函數(shù)需要至少有一個(gè)參數(shù);
  • 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var;
data class User(val name: String = "", val age: Int = 0)

在很多情況下,我們需要復(fù)制一個(gè)對(duì)象改變它的一些屬性,但其余部分保持不變。 copy() 函數(shù)就是為此而生成。對(duì)于上文的 User 類,其實(shí)現(xiàn)會(huì)類似下面這樣:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)     

標(biāo)準(zhǔn)庫(kù)提供了 PairTriple

10.密封類

密封類用來(lái)表示受限的類繼承結(jié)構(gòu):當(dāng)一個(gè)值為有限集中的類型、而不能有任何其他類型時(shí)。在某種意義上,他們是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類的一個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例。

要聲明一個(gè)密封類,需要在類名前面添加 sealed 修飾符。雖然密封類也可以有子類,但是所有子類都必須在與密封類自身相同的文件中聲明。(在 Kotlin 1.1 之前, 該規(guī)則更加嚴(yán)格:子類必須嵌套在密封類聲明的內(nèi)部)。

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

(上文示例使用了 Kotlin 1.1 的一個(gè)額外的新功能:數(shù)據(jù)類擴(kuò)展包括密封類在內(nèi)的其他類的可能性。 )
請(qǐng)注意,擴(kuò)展密封類子類的類(間接繼承者)可以放在任何位置,而無(wú)需在同一個(gè)文件中。
使用密封類的關(guān)鍵好處在于使用 when表達(dá)式 的時(shí)候,如果能夠驗(yàn)證語(yǔ)句覆蓋了所有情況,就不需要為該語(yǔ)句再添加一個(gè) else
子句了

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    // 不再需要 `else` 子句,因?yàn)槲覀円呀?jīng)覆蓋了所有的情況
}

11.嵌套類

class Outer {
    private val bar: Int = 1
    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

類可以標(biāo)記為 inner 以便能夠訪問(wèn)外部類的成員。內(nèi)部類會(huì)帶有一個(gè)對(duì)外部類的對(duì)象的引用:

private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

使用對(duì)象表達(dá)式創(chuàng)建匿名內(nèi)部類實(shí)例:

window.addMouseListener(object: MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ……
    }
                                                                                                            
    override fun mouseEntered(e: MouseEvent) {
        // ……
    }
})

12.枚舉類

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

因?yàn)槊恳粋€(gè)枚舉都是枚舉類的實(shí)例,所以他們可以是初始化過(guò)的。

enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
}

枚舉常量也可以聲明自己的匿名類

enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },

    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}

及相應(yīng)的方法、以及覆蓋基類的方法。注意,如果枚舉類定義任何成員,要使用分號(hào)將成員定義中的枚舉常量定義分隔開(kāi),就像在 Java 中一樣。

就像在 Java 中一樣,Kotlin 中的枚舉類也有合成方法允許列出定義的枚舉常量以及通過(guò)名稱獲取枚舉常量。這些方法的簽名如下(假設(shè)枚舉類的名稱是 EnumClass):

EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>

13.類委托

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容