在寫程式時,有時候我們會需要將某些值包起來,讓程式的可讀性更好。譬如,我們用來儲存密碼的資料型態為字串,我們通常會在我們的類別中宣告一個變數,並讓這個變數的型別設定為字串。
class Login(val password: String)
這樣做沒有什麼不好,為了更好的可讀性,我們可以用一個類別將密碼包起來。如下:
data class Password(val password: String)
這邊我們使用 data class
來宣告這個類別。
那麼,我們就可以將上面的 Login 類別改成
class Login(val password: Password)
當我們看到這個類別的時候,我們會知道這邊需要帶入一個 Password
類別,比起使用 String ,這樣的方式看起來更直觀了。
在 Kotlin 1.5 時,如果我們包起來屬性只有一個,而且是基本型態時,我們可以使用 value class
來替代 data class
。
將上方的 data class
替換成 value class
@JvmInline
value class Password(val password: String)
同樣的,我們可以將原本的 Login
類別中的 String 替換成 Password。( 這邊與上方相同,就不貼程式碼了)
value class
我們在 class 前方加上 value
修飾,讓這個類別成為一個 value class (數值類別)。
它有幾個特點:
- 只能含有一個屬性。
- 屬性只能使用
val
。 - 可以使用
init{ }
。 - 類別內的屬性只能使用簡單的計算(不能使用 lateinit)
為什麼需要使用 value class 呢?
從上方的特性我們發現,因為只能擁有一個屬性,而且屬性只能使用 val
修飾,也就是說,當我們建立一個數值類別(value class)時,產生出來的實例它就是一個不可變(Immutable)的類別。
所以使用 value class 就會強迫讓這個資料成為不可變的值,當我們在程式內部傳遞這個值時,就不會在某處被更改,導致錯誤發生。
在 1.4.30 版允許在 value class 使用 init{ }
後,value class 變得更好用一些。我們可以在 init { } 中做一些操作,在數值存入類別屬性之前做判斷。如下:
@JvmInline
value class Password(private val password: String) {
init {
require(password.isNotEmpty())
}
}
在 Kotlin 1.5 版本之前,value class 都還是實驗性(Experimental)的項目,原本定義一個 value class 需要使用inline
來修飾,而在 1.4.30 時,可以使用 value
修飾,目的是要跟原本的 inline function
做一個區隔。
另外,如果我們是在 JVM 上使用,我們需要加上 @JvmInline
的註釋。
對使用者來說,我們可以讓一個基本型態包成一個類別,而這個類別是不可變的,這就是 value class 要做的是。
有鑑於此,因為在 value class 中只是儲存數值,所以它不支援 ===
的比對方式。因為沒有意義,所有儲存在 value class 中的值,只要內部的值相同,都有同樣的實例 (Instance)。
value class Name(val name: String)fun main(){
val name1 = Name("Andy")
val name2 = Name("Andy")
println(name1 == name2)
}//true
- 註: 在 Kotlin 中,
===
是用來比對兩個物件的位置是否相同。(Referential equality)
繼承、實作
value class 可以實作介面。
interface IName{
fun nickName()
}@JvmInline
value class Name(val s: String): IName { override fun nickName() {
TODO("Not yet implemented")
}
}
那繼承呢?無論是繼承或是被繼承,都是不允許的。
原因:因為 value class 是用來將基本型態包裝起來,讓編譯器可以針對這個型態做最佳化,如果與一般類別做混用,最佳化可能不容易實作,甚至違反了 value class 設計的目的(將一個基本型態包裝起來),繼承其他類別時,就會有可能多出了父類別的其他屬性,這會讓 value class 變得更加複雜。
另外, value class 也是無法被繼承的,因為 value class 是 final 的,所以它無法被任何形式的繼承。
結論
使用 value class
可以讓我們的基本型態用更好的方式包起來,包起來之後,該類別可以更好的代表某個用途,而它不可變的特性,也讓我們不須要考慮它是否會在程式碼的某個地方被修改。
它只能實作介面而不能繼承,說明了它只能用在包裝基本型別上,無法做更多複雜的功能。
也就是這樣,我們可以使用 value class 來替換我們的基本型別,它讓我們的基本型別多了更多功能(自定義方法,init { } 判斷數值…),而不需要考慮 value class 有可能與基本類別混用而發生不可預期的的情況。
如果本篇文章對您有幫助,別忘了給我拍手鼓勵喔~😁謝謝大家