Kotlin
的語法糖用起來很爽,但咱們不該只知足於會用的狀態。本系列文章介紹 Kotlin
關鍵字的使用以及其背後的實現html
本文摘自 Kotlin Vocabulary 系列文章,原文請移步 Alter type with typealias 和 Zero-cost* abstractions in Kotlinjava
使用 Java 開發一段時間可能以爲 Java 中的變量名太長了!雖然優秀的命名最好是望文知義,但一堆很長的變量很影響可讀性。android
C 和 C++ 中提供了 typedef 關鍵字來定義別名,而 Kotlin
中也有相似的存在git
typealias
容許在不引入新類型的狀況下爲類或函數類型提供別名github
可使用 typealias
命名函數類型安全
typealias TeardownLogic = () -> Unit
fun onCancel(teardown : TeardownLogic){ }
private typealias OnDoggoClick = (dog: Pet.GoodDoggo) -> Unit
val onClick: OnDoggoClick
複製代碼
這樣作的缺點是名稱隱藏了傳遞給函數的參數,從而下降了可讀性app
typealias TeardownLogic = () -> Unit
typealias TeardownLogic = (exception: Exception) -> Unit
fun onCancel(teardown : TeardownLogic){
// 不能直接看到 TeardownLogic 內部的邏輯
}
複製代碼
typealias
容許縮短長泛型的名稱函數
typealias Doggos = List<Pet.GoodDoggo>
fun train(dogs: Doggos){ … }
複製代碼
固然也能夠縮短長類名性能
typealias AVD = AnimatedVectorDrawable
複製代碼
不過上面的場景使用 import alias 更合適ui
import android.graphics.drawable.AnimatedVectorDrawable as AVD
複製代碼
這種狀況下使用短命名並不能幫助咱們提升可讀性而且 IDE 會自動爲咱們補全類名
可是,若是須要區分來自不一樣包的同名類時,導入別名變得特別有用
import io.plaidapp.R as appR
import io.plaidapp.about.R
複製代碼
以上用例來自 Alter type with typealias
typealias D = Data
fun add(item: D) {
}
fun usage() {
add(D("name"))
}
複製代碼
將 Data 聲明別名 D 並使用,Decompiled 爲 Java
能夠看到 typealias
並無聲明新的類型
您不該該依賴類型別名來進行編譯時類型檢查。 相反,您應該考慮使用 inline class
例如咱們的 play 方法須要傳遞 dog 的 id
fun play(dogId: Long)
複製代碼
在嘗試傳遞錯誤的 id 時,爲 Long 建立類型別名不會幫助咱們防止錯誤
typealias DogId = Long
fun play(dogId: DogId) { … }
fun usage() {
val cat = Cat(1L)
// 實際傳遞貓的 id ,可是能夠編譯經過
play(cat.catId)
}
複製代碼
咱們知道聲明方法時能夠指定傳入參數的類型範圍
// 只容許傳入 layout 資源
public AppCompatActivity(@LayoutRes int contentLayoutId) {
super(contentLayoutId);
}
複製代碼
可是若是咱們限制使用的不是 Android 的資源,而是 Dog Cat 這樣實體類的 id,咱們須要將其包裝到一個類中。這樣作的缺點就是須要付出額外的性能成本,本來可能只須要一個基本數據類型,如今使用時額外實例化了一個對象
Kotlin
inline classes 容許您建立包裝類型而且沒有性能損耗。這是 Kotlin 1.3
中添加的實驗功能。inline class
必須有且僅有一個屬性。 在編譯時,將 inline class
實例替換爲其內的屬性(沒裝箱的基本數據類型),從而下降常規包裝類的性能損耗。 對於包裝對象是基本類型的狀況,基本數據類型包裝在 inline class
中會致使在運行時使用基本數據類型的值
內聯類的惟一做用是成爲類型的包裝,所以 Kotlin
對其作了不少限制:
inline class
能夠
interface Id
inline class DoggoId(val id: Long) : Id {
val stringId
get() = id.toString()
fun isValid()= id > 0L
}
複製代碼
typealias
看起來與inline class
很像,可是typealias
只是爲現有類型提供了別名,而inline class
會建立新類型
讓咱們看一個簡單的 inline class
interface Id
inline class DoggoId(val id: Long) : Id
複製代碼
public final class DoggoId implements Id {
// $FF: synthetic method
private DoggoId(long id) {
this.id = id;
}
public static long constructor_impl/* $FF was: constructor-impl*/(long id) {
return id;
}
}
複製代碼
DoggoId 有兩個構造器
DoggoId(long id)
constructor_impl
當建立新的實例時將使用公開的構造函數
val myDoggoId = DoggoId(1L)
// decompiled
static final long myDoggoId = DoggoId.constructor-impl(1L);
複製代碼
當咱們嘗試在 Java 中建立 doggo 時,會報錯
DoggoId u = new DoggoId(1L);
// Error: DoggoId() in DoggoId cannot be applied to (long)
複製代碼
沒法在 Java 中實例化
inline class
參數化的構造函數是私有的,第二個構造函數在名稱中包含 -(在 Java 中爲無效字符)。 這意味着沒法在 Java 實例化 inline class
這裏的 id 經過兩種方式使用
getId()
box_impl
建立 DoggoId
的實例化對象public final long getId() {
return this.id;
}
public static final DoggoId box_impl/* $FF was: box-impl*/(long v) {
return new DoggoId(v);
}
複製代碼
若是在可使用基本數據類型的地方使用 inline class
,Kotlin
編譯器將直接使用基本數據類型
fun walkDog(doggoId: DoggoId) {}
// decompiled Java code
public final void walkDog_Mu_n4VY(long doggoId) { }
複製代碼
當須要一個對象時,Kotlin
編譯器將使用基本數據類型的裝箱版本,從而每次都建立一個新的對象
下面咱們來看看須要裝箱的幾種狀況
fun pet(doggoId: DoggoId?) {}
// decompiled Java code
public static final void pet_5ZN6hPs/* $FF was: pet-5ZN6hPs*/(@Nullable InlineDoggoId doggo) {}
複製代碼
只有引用數據類型才能爲 null ,所以須要裝箱
val doggos = listOf(myDoggoId)
// decompiled Java code
doggos = CollectionsKt.listOf(DoggoId.box-impl(myDoggoId));
複製代碼
// CollectionsKt.listOf
fun <T> listOf(element: T): List<T>
複製代碼
因爲該方法須要引用數據類型,所以須要裝箱
fun handleId(id: Id) {}
fun myInterfaceUsage() {
handleId(myDoggoId)
}
// decompiled Java code
public static final void myInterfaceUsage() {
handleId(DoggoId.box-impl(myDoggoId));
}
複製代碼
這裏也須要裝箱
Kotlin
編譯器會盡其所能使用沒裝箱的基本數據類型參數,所以,inline class
具備三種相等檢查的方式:1 個重寫 equals 和 2 個生成的方法
public final class DoggoId implements Id {
public static boolean equals_impl/* $FF was: equals-impl*/(long var0, @Nullable Object var2) {
if (var2 instanceof DoggoId) {
long var3 = ((DoggoId)var2).unbox-impl();
if (var0 == var3) {
return true;
}
}
return false;
}
public static final boolean equals_impl0/* $FF was: equals-impl0*/(long p1, long p2) {
return p1 == p2;
}
// 重寫 equals 方法
public boolean equals(Object var1) {
return equals-impl(this.id, var1);
}
}
複製代碼
equals
方法調用一個生成的方法:equals_impl(long,Object)
。 因爲 equals
指望有一個對象,所以將對doggo2 值進行裝箱,可是將 doggo1 用做基本數據類型
DoggoId.equals-impl(doggo1, DoggoId.box-impl(doggo2))
複製代碼
使用 ==
等價於 DoggoId.equals-impl0(doggo1, doggo2)
所以使用 ==
doggo1 和 doggo2 均使用基本數據類型
若是 Kotlin
編譯器可以肯定 doggo1 是 long 類型,那麼這種相等性檢查有效。 可是,因爲 inline class
是類型安全的,所以,編譯器要作的第一件事是檢查這兩個對象的類型是否相同。 若是不相同,咱們會收到編譯器錯誤:Operator ==
can’t be applied to long and DoggoId
因爲 Kotlin
編譯器使用 equals 實現,所以須要一個 long 和一個 Object 進行相等檢查。 可是,因爲此方法的第一件事是檢查 Object 的類型,所以該相等性檢查將爲 false,由於 Object 不是 DoggoId
Zero-cost* abstractions in Kotlin 原文還介紹了在 Java 中使用 inline class
以及如何選擇是否使用 inline class
,感興趣的小夥伴可移步原文查看,這裏不作介紹
我是 Fly_with24