[從 Effective Java 了解 Kotlin]— 用 Companion Object 來重複使用物件

Item 1: Consider static factory methods instead of constructors

Image provided by Pixabay

Java 與 Kotlin 都是屬於物件導向的程式語言。在物件導向的程式語言中,「物件」可謂是這種形式的語言的關鍵,我們知道所謂的物件,是將真實世界的東西抽象成一個類別(在 Java 與 Kotlin 中都是以 class 關鍵字定義),而在程式中,我們就可以將這些類別產生出一個又一個的物件。所以說,類別就是定義這個東西的樣貌,我們使用屬性(Property)、函式(method)來定義,當一個物件被建立出來之後,我們就可以使用這個類別中的公開屬性 (public property)、公開方法 (public method)。

範例:一個 Human 類別。

class Human(val name:String, val age:Int){
fun greeting() = println("Hi, my name is $name and I'm $age years old.")
}

範例:建立一個名為 Curry 且 年紀為 20 的物件。

val curry = Human("Curry", 20)

範例:使用 Curry 物件的屬性與函式

curry.name //Curry
curry.age //20
curry.greeting() //Hi, my name is Curry and I'm 20 years old.

物件的建立就是這麼簡單,不是嗎?

其實,物件可以分為兩種建立方式,一種是我們上面所說的直接使用類別的建構式(constructor)來建立物件,另一種則是使用靜態方法取得物件

這兩種有什麼樣的分別呢?

用建構式的建立方式,我們已經在上面示範,下面我將使用靜態的方式來建立一個物件。

Companion object

在 Kotlin 中,我們可以使用 companion object 在類別中定義一塊空間,而這個空間會伴隨著類別的產生而存在。類似於 Java 的 static 宣告。

範例:加入 companion object。

class Human(val name: String, val age: Int) {
fun greeting() = println("Hi, my name is $name and I'm $age years old.")
companion object {
private val curry = Human("Curry", 20)
fun create(name: String, age: Int): Human {
if (name == "Curry" && age == 20) {
return curry
}
return Human(name, age)
}
}
}

我們發現,我在 companion object 中,設定了一個名為 curry 的屬性,它會建立一個 Human(”Curry”, 20) 的物件。另外,同樣在 companion object 裡面,還有另外一個名為 create 的函式。

仔細看,我在 create 函式裡會先判斷 name 以及 age 是否為 “Curry”、20,當傳進來的數值完全吻合時,我將直接使用 curry 作為函式的回傳值,而不是建立一個新的 Human

這樣做有什麼好處呢?

我們嘗試用兩種方式建立類別,並分別使用 hashCode() 來察看它們所產生出來的雜湊碼是否相同。

fun main(){
val curry1 = Human.creat("Curry", 20)
val curry2 = Human.create("Curry", 20)
val curry3 = Human("Curry", 20)
val curry4 = Human("Curry", 20)
println(curry1.hashCode())
println(curry2.hashCode())
println(curry3.hashCode())
println(curry4.hashCode())
}
929338653
929338653
1259475182
1300109446

可以發現由 create() 產生出來的物件的雜湊碼是完全相同的,也就是說這兩個產生出來的物件是完完全全相同的,從屬性到記憶體位置,而這就是 Item 1* 所介紹的內容。

如果我們在 create() 函式中沒有帶入 Curry, 20 ,那麼每次所產生的物件就會是新建立的,而不會是存在於 companion object 裡的屬性。

Item 1: Consider static factory methods instead of constructors

整理一下用 companion object 中的方法建立物件有什麼優點:

  1. 使用函式建立物件,所以可以有名稱,Ex: create()
  2. 可以避免每次都建立新的物件。

書中介紹了幾個函式的命名方式,各位可以參考看看:

  • from: 型別轉換方法(type-conversion method)
  • of: 聚合方法(aggregation method)
  • valueOf: 有 from, of 的意思(名稱較長)
  • instance 或 getInstance: 可以根據帶入的參數來產生不同的物件。或是沒有參數,直接回傳一個固定的物件。
  • create 或 newInstance:跟 instance 與 getInstance 類似,不過使用 create 更有建立一個新的物件的意思。
  • getType: 與 getInstance 相似,不過用在回傳不同型別上面。
  • newType: 與 newInstance 相似,不過用在回傳不同型別上面。
  • type: 意思是取得或建立一個物件,也就是具有 getType, newType 的意思。

小結

利用靜態方法來產生物件,我們除了可以避免建立相同的物件,還可以利用不同函式名稱來讓這個動作更容易讓人理解。

在某些時候,我們沒有必要建立一個內容相同,但是佔用不同記憶體區塊,在大部分的物件使用中,我們在意的是物件的內容,而不是它的位置,例如上面範例的 Human(”Curry”,20) 這個物件,當我們利用靜態方法來建立物件時,我們可以減少記憶體的使用,進而提高系統效能。

在 Item 6 (Avoid creating unnecessary objects),也是有著相同的概念,當我們建立的物件是很耗費資源時,我們要考慮的是是不是可以把共用的部分改用靜態方法來取得,而不是新建一個類別。

您的拍手鼓勵,是我寫作的動力

--

--

Android/Flutter developer, like to learn and share.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Andy Lu

Andy Lu

Android/Flutter developer, like to learn and share.