top
Loading...
Kotlin 類和對象

Kotlin 類和對象

類定義

Kotlin 類可以包含:構造函數和初始化代碼塊、函數、屬性、內部類、對象聲明。

Kotlin 中使用關鍵字 class 聲明類,後面緊跟類名:

class Runoob {  // 類名為 Runoob
    // 大括號內是類體構成
}

我們也可以定義一個空類:

class Empty

可以在類中定義成員函數:

class Runoob() {
    fun foo() { print("Foo") } // 成員函數
}

類的屬性

屬性定義

類的屬性可以用關鍵字 var 聲明為可變的,否則使用只讀關鍵字 val 聲明為不可變。

class Runoob {
    var name: String = ……
    var url: String = ……
    var city: String = ……
}

我們可以像使用普通函數那樣使用構造函數創建類實例:

val site = Runoob() // Kotlin 中沒有 new 關鍵字

要使用一個屬性,只要用名稱引用它即可

site.name           // 使用 . 號來引用
site.url

Koltin 中的類可以有一個 主構造器,以及一個或多個次構造器,主構造器是類頭部的一部分,位於類名稱之後:

class Person constructor(firstName: String) {}

如果主構造器沒有任何注解,也沒有任何可見度修飾符,那么constructor關鍵字可以省略。

class Person(firstName: String) {
}

getter 和 setter

屬性聲明的完整語法:

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

getter 和 setter 都是可選

如果屬性類型可以從初始化語句或者類的成員函數中推斷出來,那就可以省去類型,val不允許設置setter函數,因為它是只讀的。

var allByDefault: Int? // 錯誤: 需要一個初始化語句, 默認實現了 getter 和 setter 方法
var initialized = 1    // 類型為 Int, 默認實現了 getter 和 setter
val simple: Int?       // 類型為 Int ,默認實現 getter ,但必須在構造函數中初始化
val inferredType = 1   // 類型為 Int 類型,默認實現 getter

實例

以下實例定義了一個 Person 類,包含兩個可變變量 lastName 和 no,lastName 修改了 getter 方法,no 修改了 setter 方法。

class Person {
    var lastName: String = "zhang"
        get() = field.toUpperCase()   // 將變量賦值後轉換為大寫
        set
    var no: Int = 100
        get() = field                // 後端變量
        set(value) {
            if (value < 10) {       // 如果傳入的值小於 10 返回該值
                field = value
            } else {
                field = -1         // 如果傳入的值大於等於 10 返回 -1
            }
        }
    var heiht: Float = 145.4f
        private set
}
// 測試
fun main(args: Array<String>) {
    var person: Person = Person()
    person.lastName = "wang"
    println("lastName:${person.lastName}")
    person.no = 9
    println("no:${person.no}")
    person.no = 20
    println("no:${person.no}")
}

輸出結果為:

lastName:WANG
no:9
no:-1

Kotlin 中類不能有字段。提供了 Backing Fields(後端變量) 機製,備用字段使用field關鍵字聲明,field 關鍵詞只能用於屬性的訪問器,如以上實例:

var no: Int = 100
        get() = field                // 後端變量
        set(value) {
            if (value < 10) {       // 如果傳入的值小於 10 返回該值
                field = value
            } else {
                field = -1         // 如果傳入的值大於等於 10 返回 -1
            }
        }

非空屬性必須在定義的時候初始化,kotlin提供了一種可以延遲初始化的方案,使用 lateinit 關鍵字描述屬性:

public class MyTest {
    lateinit var subject: TestSubject
    @SetUp fun setup() {
        subject = TestSubject()
    }
    @Test fun test() {
        subject.method()  // dereference directly
    }
}

主構造器

主構造器中不能包含任何代碼,初始化代碼可以放在初始化代碼段中,初始化代碼段使用 init 關鍵字作為前綴。

class Person constructor(firstName: String) {
    init {
        println("FirstName is $firstName")
    }
}

注意:主構造器的參數可以在初始化代碼段中使用,也可以在類主體n定義的屬性初始化代碼中使用。 一種簡潔語法,可以通過主構造器來定義屬性併初始化屬性值(可以是var或val):

class People(val firstName: String, val lastName: String) {
    //...
}

如果構造器有注解,或者有可見度修飾符,這時constructor關鍵字是必須的,注解和修飾符要放在它之前。

實例

創建一個 Runoob類,併通過構造函數傳入網站名:

class Runoob  constructor(name: String) {  // 類名為 Runoob
    // 大括號內是類體構成
    var url: String = "http://www.sharebody.com"
    var country: String = "CN"
    var siteName = name
    init {
        println("初始化網站名: ${name}")
    }
    fun printTest() {
        println("我是類的函數")
    }
}
fun main(args: Array<String>) {
    val runoob =  Runoob("教程")
    println(runoob.siteName)
    println(runoob.url)
    println(runoob.country)
    runoob.printTest()
}

輸出結果為:

初始化網站名: 教程
教程
http://www.sharebody.com
CN
我是類的函數

次構造函數

類也可以有二級構造函數,需要加前綴 constructor:

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

如果類有主構造函數,每個次構造函數都要,或直接或間接通過另一個次構造函數代理主構造函數。在同一個類中代理另一個構造函數使用 this 關鍵字:

class Person(val name: String) {
    constructor (name: String, age:Int) : this(name) {
        // 初始化...
    }
}

如果一個非抽象類沒有聲明構造函數(主構造函數或次構造函數),它會產生一個沒有參數的構造函數。構造函數是 public 。如果你不想你的類有公共的構造函數,你就得聲明一個空的主構造函數:

class DontCreateMe private constructor () {
}

注意:在 JVM 虛擬機中,如果主構造函數的所有參數都有默認值,編譯器會生成一個附加的無參的構造函數,這個構造函數會直接使用默認值。這使得 Kotlin 可以更簡單的使用像 Jackson 或者 JPA 這樣使用無參構造函數來創建類實例的庫。

class Customer(val customerName: String = "")

實例

class Runoob  constructor(name: String) {  // 類名為 Runoob
    // 大括號內是類體構成
    var url: String = "http://www.sharebody.com"
    var country: String = "CN"
    var siteName = name
    init {
        println("初始化網站名: ${name}")
    }
    // 次構造函數
    constructor (name: String, alexa: Int) : this(name) {
        println("Alexa 排名 $alexa")
    }
    fun printTest() {
        println("我是類的函數")
    }
}
fun main(args: Array<String>) {
    val runoob =  Runoob("教程", 10000)
    println(runoob.siteName)
    println(runoob.url)
    println(runoob.country)
    runoob.printTest()
}

輸出結果為:

初始化網站名: 教程
Alexa 排名 10000
教程
http://www.sharebody.com
CN
我是類的函數

抽象類

抽象是面向對象編程的特征之一,類本身,或類中的部分成員,都可以聲明為abstract的。抽象成員在類中不存在具體的實現。

注意:無需對抽象類或抽象成員標注open注解。

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

嵌套類

我們可以把類嵌套在其他類中,看以下實例:

class Outer {                  // 外部類
    private val bar: Int = 1
    class Nested {             // 嵌套類
        fun foo() = 2
    }
}
fun main(args: Array<String>) {
    val demo = Outer.Nested().foo() // 調用格式:外部類.嵌套類.嵌套類方法/屬性
    println(demo)    // == 2
}

內部類

內部類使用 inner 關鍵字來表示。

內部類會帶有一個對外部類的對象的引用,所以內部類可以訪問外部類成員屬性和成員函數。

class Outer {
    private val bar: Int = 1
    var v = "成員屬性"
    /**嵌套內部類**/
    inner class Inner {
        fun foo() = bar  // 訪問外部類成員
        fun innerTest() {
            var o = this@Outer //獲取外部類的成員變量
            println("內部類可以引用外部類的成員,例如:" + o.v)
        }
    }
}
fun main(args: Array<String>) {
    val demo = Outer().Inner().foo()
    println(demo) //   1
    val demo2 = Outer().Inner().innerTest()   
    println(demo2)   // 內部類可以引用外部類的成員,例如:成員屬性
}

為了消除歧義,要訪問來自外部作用域的 this,我們使用this@label,其中 @label 是一個 代指 this 來源的標籤。


匿名內部類

使用對象表達式來創建匿名內部類:

class Test {
    var v = "成員屬性"
    fun setInterFace(test: TestInterFace) {
        test.test()
    }
}
/**
 * 定義接口
 */
interface TestInterFace {
    fun test()
}
fun main(args: Array<String>) {
    var test = Test()
    /**
     * 采用對象表達式來創建接口對象,即匿名內部類的實例。
     */
    test.setInterFace(object : TestInterFace {
        override fun test() {
            println("對象表達式創建匿名內部類的實例")
        }
    })
}

類的修飾符

類的修飾符包括 classModifier 和_accessModifier_:

  • classModifier: 類屬性修飾符,標示類本身特性。

    abstract    // 抽象類  
    final       // 類不可繼承,默認屬性
    enum        // 枚舉類
    open        // 類可繼承,類默認是final的
    annotation  // 注解類
    
  • accessModifier: 訪問權限修飾符

    private    // 僅在同一個文件中可見
    protected  // 同一個文件中或子類可見
    public     // 所有調用的地方都可見
    internal   // 同一個模塊中可見
    

實例

// 文件名:example.kt
package foo
private fun foo() {} // 在 example.kt 內可見
public var bar: Int = 5 // 該屬性隨處可見
internal val baz = 6    // 相同模塊內可見
北斗有巢氏 有巢氏北斗