[Kotlin 小撇步 #9] Value class — 數值類別

Andy Lu
5 min readNov 13, 2021

在寫程式時,有時候我們會需要將某些值包起來,讓程式的可讀性更好。譬如,我們用來儲存密碼的資料型態為字串,我們通常會在我們的類別中宣告一個變數,並讓這個變數的型別設定為字串。

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 (數值類別)。

它有幾個特點:

  1. 只能含有一個屬性。
  2. 屬性只能使用 val
  3. 可以使用 init{ }
  4. 類別內的屬性只能使用簡單的計算(不能使用 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 有可能與基本類別混用而發生不可預期的的情況。

如果本篇文章對您有幫助,別忘了給我拍手鼓勵喔~😁謝謝大家

--

--

Andy Lu
Andy Lu

Written by Andy Lu

Android/Flutter developer, Kotlin Expert, like to learn and share.

No responses yet