這裏是 重學 Kotlin 系列第二篇,本文永久更新地址 :https://xiaozhuanlan.com/topic/0846175293 。
可能還真的就不認識了。
今天的主角是 type alias,翻譯過來叫 類型別名。先來看一下文章目錄:
什麼是 typealias ?
typealias 的本質
typealias 存在的意義是什麼?
typealias 的使用注意事項
什麼是 typealias ?
這是一個很基礎的關鍵字,但可能很多人沒有使用過。它的作用十分簡單,給已有類型取一個別名,可以像使用原類型一樣使用這個 “類型別名” 。
舉個簡單的例子:
typealias Name = String
val name : Name = "java"
println(name.length)
給 String
取個別名 Name
,在使用過程中,Name
和 String
是完全等價的。
既然是等價的,使用別名的意義是什麼呢?
別急,typealias
不僅僅支持給類取別名,它的用法豐富的讓你想象不到。
// 類和接口
typealias Name = String
typealias Data = Serializable
// 函數類型
typealias GetName = () -> String
typealias Handler = CoroutineScope.() -> Unit
// 泛型
typealias P<T> = Comparable<T>
typealias Pairs<K, V> = HashMap<K, V>
typealias Numbers = Array<Number>
// object
object Single {}
typealias X = Single
class Util {
companion object {
val count = 1
}
}
typealias Count = Util.Companion
// inner class
typealias NotificationBuilder = NotificationCompat.Builder
class Outer { inner class Inner }
typealias In = Outer.Inner
// 枚舉
enum class Color { RED, YELLOW, GREEN }
typealias color = Color
// 註解
typealias Jvm = JvmStatic
上面的枚舉用法中,需要注意的一點是,只能爲枚舉類 Color
取別名,無法爲具體的枚舉值取別名 。諸如 typealias Red = Color.RED
是不允許的。
幾乎沒有 typealias 不適用的類型。說到現在,你又得疑問了,類型別名存在的意義是什麼 ?這樣簡單的取個別名,爲什麼不直接使用原類型呢 ?
typealias 的本質
暫且別急,我們先來看一下 typealias
的實現原理,說不定可以有一些發現。
反編譯下面這個簡單的例子:
typealias Binary = ByteArray
fun getBinary(string: String) : Binary = string.toByteArray()
查看其 Java 代碼 :
public final class TypealiasKt {
@NotNull
public static final byte[] getBinary(@NotNull String string) {
Intrinsics.checkParameterIsNotNull(string, "string");
Charset var2 = Charsets.UTF_8;
boolean var3 = false;
byte[] var10000 = string.getBytes(var2);
Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).getBytes(charset)");
return var10000;
}
}
代碼中根本找不到類型別名 Binary
的蹤影。經過編譯之後,類型別名會被原類型直接替換。這僅僅只是 Kotlin 豐富的語法糖之一,編譯器在其中做了一些手腳。
typealias 存在的意義是什麼 ?
現在,你估計更加困惑了。
開發者手動聲明一個類型別名,編譯器再自動替換回原類型。意義何在?
唯一能想到的一點大概只有 "代碼可讀性" ,這裏的代碼可讀性還要打上了一個大大的引號。
複雜的業務邏輯下,你的代碼中可能會出現超長命名,多參數,多泛型類型的類名,接口名,函數名。
typealias FileTable<K> = MutableMap<K, MutableList<File>>
typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback
typealias SimpleName = LonglonglonglonglonglonglonglonglonglonglonglonglonglonglongName
用類型別名來代替原本可讀性不好(名字太長或者聲明覆雜)的類型名,這可能就是 typealias
的主要作用。
至於到底有沒有提升可讀性?我覺得這是有代價的。因此而喪失的是直觀的類型聲明。以上面代碼塊中的 FileTable
爲例,一眼看過去,你能發現它是一個 MutableMap<K, MutableList<File>>
嗎?肯定不能。特別在團隊開發中,除了這個代碼的貢獻者,可能每一位都要到類型別名聲明處進行查看。
有人可能也會有不一樣的聲音。統一的全局聲明很正常,而且也方便做統一修改,避免到代碼使用處一一修改。況且 IDE 都會自動提示類型別名的聲明。沒有不使用 typealias 的道理。
所以,這是一個仁者見仁,智者見智的問題。你覺得有用就可以使用,任何一門技術肯定都有它的使用場景,並沒有必要去過多爭論。
我用協程還是用 RxJava?我用 LiveData 還是用事件總線?我用 ViewBinding 還是 DataBinding ?......
這幾個問題可能比較常見,但是上面的每一組選擇,如果你真正深入瞭解其技術本質的話,就會發現它們的使用場景並不一樣,也就不會存在 如何選擇 的疑問了。
typealias 使用注意事項
有點跑偏了,再回到 typealias 。後面再說一些 typealias 的注意事項,內容會比較零散,後續也可能繼續增加。
typealias 可以寫在哪裏?
只能聲明在文件頂層位置,其他任何地方都不行。
與 Java 如何交互?
拒絕和 Java 進行交互。
禁止套娃!
首先我們是可以 給別名取別名 的,如下所示:
typealias Names<T> = Array<T>
typealias SubNames<T> = Names<T>
雖然沒有太大意義,但是語法上是正確的。
下面這樣套娃肯定是不行的。
typealias R = R
typealias L = List<L>
typealias A<T> = List<A<T>>
typealias R1 = (Int) -> R2
typealias R2 = (R1) -> Int
上面的每一行代碼都是無法編譯的。
可見性
頂層位置的 typealias 可以使用可見性修飾符 public
、 private
、 internal
。同時,typealias 不能修改原有類型的可見性。舉個例子:
private class LongName{}
typealias ShortName = LongName // 'public' typealias exposes 'private' in expanded type LongName
上面的代碼會編譯失敗, public
的類型別名無法應用在 private
的原類型上。類型別名和原類型的可見性必須保持一致。
導入同名類的處理
對於在同一個類中導入兩個同名類,通常的做法是, import
其中一個類,另一個使用全限定名。如下所示:
fun main() {
val user1 = User()
val user2 = com.test2.User()
}
這樣或多或少不大美觀,可以使用 typealias 處理一下。
typealias User2 = com.test2.User
fun main() {
val user1 = User()
val user2 = User2()
}
另外, import ... as ...
也可以解決這個問題。
import com.test1.User
import com.test2.User as User2
fun main() {
val user1 = User()
val user2 = User2()
}
最後
不妨翻翻你的代碼庫,看看有沒有可以使用 typealias 進行優化的 “爛” 命名。如果有的話,歡迎來到評論區交流分享。
往期目錄 :object,史上最 “快” 單例 ?