enum 的用法跟在 Java 的時候其實大同小異
舉例來說,我原本有個 Java Enum 如下
這個 enum 有 name 和 code 的內容,還有一個 static 的 getName() 方法
public enum PaymentRtnCode {
RC2("token 錯誤", "2"),
RC1("系統維護中", "1"),
RCN4("無此帳戶", "-4"),
C0("有未繳費用", "0");
String name;
String code;
PaymentRtnCode(String name, String code) {
this.name = name;
this.code = code;
}
public static String getName(String code) {
for (PaymentRtnCode b : PaymentRtnCode.values()) {
if (b.code.equals(code)) {
return b.name;
}
}
return "未知的原因";
}
public String getName() {
return name;
}
public String getCode() {
return code;
}
}
改成 Kotlin 後會像是這樣
是不是簡潔多了呢,注意原本的 name 我換成了 type,因為 Kotlin enum 裡面預設就有一個 name 的屬性
static 方法的部分,就改成了 companion object 的方法
enum class PaymentRtnCode(var type: String, var code: String) {
RC2("token 錯誤", "2"),
RC1("系統維護中", "1"),
RCN4("無此帳戶", "-4"),
C0("有未繳費用", "0");
companion object {
fun getType(code: String): String {
for (b in values()) {
if (b.code == code) {
return b.type
}
}
return "未知的原因"
}
}
}
實際使用的話會像是這樣
fun main() {
println(PaymentRtnCode.getType(1)) // 系統維護中
}
或是使用 data class 來當作 constructor 參數也可以! 一樣的效果
data class RtnCode(var type: String, var code: String)
enum class PaymentRtnCode(private val rtnCode: RtnCode) {
RC2(RtnCode("token 錯誤", "2")),
RC1(RtnCode("系統維護中", "1")),
RCN4(RtnCode("無此帳戶", "-4")),
C0(RtnCode("有未繳費用", "0"));
companion object {
fun getType(code: String): String {
for (b in PaymentRtnCode.values()) {
if (b.code == code) {
return b.type
}
}
return "未知的原因"
}
}
}
sealed class 可以說是 enum 的加強版
剛剛在 enum 裡面的種類只能是某一種 class 或固定的參數
像是這樣 RC2("token 錯誤", "2"),
但如果今天我們想要很多種樣式
的 enum 時候,就可以使用 sealed class
sealed class 裡面可以包含多個不同的 class 或 object 或 data class,這些物件透過繼承 sealed class 而統一成為一個大家庭!
像下面的例子
sealed class Code {
data class RCode(val number: Double) : Code()
data class VCode(val name: String, val code: String) : Code()
object ZCode : Code()
companion object {
fun getCode(code: Code): String {
return when (code) {
is RCode -> "RCode type"
is VCode -> "VCode type"
is ZCode -> "ZCode type"
}
}
}
}
這三個類別都繼承了 sealed class Code
data class RCode(val number: Double) : Code()
data class VCode(val name: String, val code: String) : Code()
object ZCode : Code()
sealed class 通常都會搭配 when 使用,因為透過繼承 sealed class,所以 when() 的參數就可以使用 sealed class Code
接著在裡面針對子類做判斷
return when (code) {
is RCode -> "RCode type"
is VCode -> "VCode type"
is ZCode -> "ZCode type"
}
使用 sealed class 最大的優點是如果在 when 裡面你少寫了某個 case 他會自動幫你檢查!提醒你一定要加上,不然就是要用 else 來對應其他的結果。
另外 sealed class 也可以寫成像是這樣,沒有大括號 {},都是分開寫的也可以,因為主要原理是透過繼承實現的。
sealed class Code2
data class RCode(val number: Double) : Code2()
data class VCode(val name: String, val code: String) : Code2()
object ZCode : Code2()
fun getCode(code: Code2): String {
return when (code) {
is RCode -> "RCode type"
is VCode -> "VCode type"
is ZCode -> "ZCode type"
}
}
剛剛講到 sealed class 是透過繼承完成的,那是不是一般 class 也可以做到呢??
是可以做到,但是一般 class 的話,使用 when 就一定要有 else 來應對其他的狀況,所以結論是 sealed class 還是比較適合來使用在需要列舉的狀況下的。
最後我在舉一個最近看到的例子,在 ktor 的 OAuth 取得認證的 principal 的時候,資料也是用 sealed class 來表示。
sealed class OAuthAccessTokenResponse
繼承了 Principal
Principal
通常來說是代表認證的 user 物件
然後裡面定義了兩種不同的 data class OAuth1a, OAuth2 都繼承了 sealed class OAuthAccessTokenResponse
sealed class OAuthAccessTokenResponse : Principal {
data class OAuth1a(
val token: String, val tokenSecret: String,
val extraParameters: Parameters = Parameters.Empty
) : OAuthAccessTokenResponse()
data class OAuth2(
val accessToken: String, val tokenType: String,
val expiresIn: Long, val refreshToken: String?,
val extraParameters: Parameters = Parameters.Empty
) : OAuthAccessTokenResponse()
}
所以當取得使用者認證資訊時,會特別針對 principal<OAuthAccessTokenResponse.OAuth2>
這個類型取到物件然後對 principal 內的 accessToken 來做 JWT.decode ,最後取得 token
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
if (principal != null) {
val token = JWT.decode(principal.accessToken)
//todo
} else {
call.respond(HttpStatusCode.Unauthorized)
}
以上就是今天的介紹!我們明天見!
今日練習的程式在這: 請點我