Kotlin Constructor


Kotlin Constructor

Constructor 建構子是在各種語言中都有的東西,Constructor 其實也是 function 的一種,可以這麼理解當一個 Class 被產生 Instance 時執行的第一個 function 叫做 Constructor,既然 Constructor 概念上也是 function 那就也可以多載(Overload)、繼承(Extend)

在 Java 的 Constructor 像是這樣使用

// Java code
public class User {
    String name;
 
    public User(String name) {
        this.name = name
    }
}

翻譯成 Kotlin 就像這樣

class User{
    var name = ""
    constructor(name: String) {
        this.name = name  
    }
}

如果你是用 IntelliJ IDEA 或是你使用 Android Stdio 你會發現有一條黃線,IDE 會提示你 Convert to primary constructor ,提示你可以把程式寫成這樣

class User(var name: String) {

}

是不是神奇的短!! 那問題是什麼是 Primary constructor

既然有 Primary constructor 就會有 Secondary constructor

Primary constructor


Primary constructor 顧名思義就是主要的建構子

在 Class name 後面接上的 constructor 就是 Primary constructor

class User constructor(name: String) {

}

也能加入 annotations(注釋)

class User public @Inject constructor(name: String) {

}

如果你沒有 annotations 還能連同 constructor 一起省略

class User(name: String) {

}

Constructor 中如同一般的 function 一樣可使用多載(Overload)

class User(name: String, age: Int = 18) {
    
}

到這裡你可以發現 Primary constructor 無法包含任何程式碼,如果你需要則可寫在 init 區塊內

class User(name: String) {
    var userName: String
    init {
        userName = name
        // do something 
    }
}

但如果只是上述這樣把 name 存到 userName 那就不建議這樣像下面這樣寫,但如果你要 do something 一下什麼的就建議上述寫法

class User(name: String) {
    var userName = name
}

此時你以許會發現,當你在非 Constructor function 內需要呼叫建構子的引數那會跑出 Unresolved reference: name

class User constructor(name: String) {
    var userName = name
    init {
        userName = name // 
    }

    fun doSomething(){
        val sonmeName = name // Unresolved reference: name
    }
}

其實這也不難理解我們改成 Java 對照看就會很好懂

// Java code
public class User {
    String userName;
 
    public User(String name) {
        this.userName = name
    }

    public void doSomething() {
        String sonmeName = name; // Unresolved reference: name
    }
}

答案很明顯,因為建構子的引數 name 根本不在 doSomething() 的 scope 內,因此呼叫得到 name 的只有同在建構子內的 init function

Kotlin 也提供很友善的寫法解決這個簡單的問題,只要在引數名稱前面加上 val or var 即可,引數也能加上存取修飾字(Visibility modifiers)

class User constructor(private val name: String) {
    var userName = name
    init {
        userName = name 
    }

    fun doSomething(){
        val sonmeName = name
    }
}

除了引數可以加上取修飾字(Visibility modifiers) Constructor 本身也能有存取修飾字(Visibility modifiers),例如我們簡實作做一個 Singleton 就會需要避免 Class 從外部產生 Instance,因此需要把 Constructor 變成 private

class Singleton private constructor() {
    init {
        println("Singleton init")
    }
    companion object {
        val instance = Singleton()
    }
}

如果直接 Singleton() 編譯器會報錯 Cannot access '<init>': it is private in 'Singleton' 因此只能使用 Singleton.instance

Secondary constructor


Secondary constructor 語意上說是次要的建構子,可以理解成輔助 Primary constructor,並且 Secondary constructor 必須委派 Primary constructor,如果你忘記委派 Primary constructor 編譯時則會出現 Primary constructor call expected

class Priflie(name: String) {}

class User(name: String) {
    constructor(profile: Profile) { // Primary constructor call expected
        // ...
    }
}

要寫成這樣才對

class Priflie(name: String) {}

class User(name: String) {
    constructor(profile: Profile): this(profile.name) {
        // ...
    }
}

翻譯成 Java 會是這樣

// Java code
public class User {
 
    public User(String name) {
        // ...
    }

    public User(Proffile profile) {
        this(profile.name)
    }
}

藉此可得知在 Kotlin 中使用 Secondary constructor 也必然會使用到 Primary constructor,例如下面這樣使用

class Priflie(name: String) {}

class User(name: String){
    init {
        println("Primary constructor")
    }
    constructor(profile: Profile): this(profile.name) {
        println("Secondary constructor")
    }
}

fun main(){
    User(Profile("Aki"))
}

執行後 Terminal 會印出

Primary constructor
Secondary constructor

至於為什麼 Kotlin 要這樣設計我也不是很清楚


當然如果你不需要 Constructor 也是可以什麼都不寫

class User {  

}

說到這裡 Constructor 用法大致上用法都說過一次了,可以發現跟 Java 比起來 Kotlin 看似很多變化但撰寫起來更為省事。

這篇文寫的意外的長,謝謝各位觀看


WRITTEN BY
Aki

熱愛寫code的開發者,專注於 Android 手機 Native App 開發,對於 IOS 也有涉略。閒暇之餘也學習 JavaScript 等前端框架