Kotlin 1.5 宣布了一個(gè)重磅特性 value class
不翩,這是一個(gè)非常實(shí)用的特性齿诞,提高代碼的可讀性同時(shí)衡便,還可以提高性能献起,因?yàn)榫幾g器會(huì)對(duì)它進(jìn)行更深層次的優(yōu)化洋访,減少對(duì)象的創(chuàng)建。
隨著 Kotlin 不斷的完善谴餐,出現(xiàn)了一系列的特性 inner class
姻政、 data class
、 sealed class
岂嗓、sealed interface
汁展、inline class
、value class
等等厌殉,之前寫過幾篇文章專門分析 sealed class
和 sealed interface
善镰,可以點(diǎn)擊下方鏈接前去查看。
而今天這篇文章主要分析 inline class
和 value class
炫欺。
通過這篇文章你將學(xué)習(xí)到以下內(nèi)容:
- 什么是內(nèi)聯(lián)函數(shù)?
- 什么情況下使用內(nèi)聯(lián)函數(shù)熏兄?
- 什么是
inline class
品洛? - 什么是
value class
?-
value class
不能被繼承 -
value class
可以實(shí)現(xiàn)接口 - 當(dāng)傳遞的對(duì)象為空時(shí)摩桶,會(huì)失去內(nèi)聯(lián)效果
-
value class
禁止使用===
可以使用==
-
-
inline class
和value class
有什么區(qū)別桥状? - Java 如何調(diào)用接受內(nèi)聯(lián)類的函數(shù)?
內(nèi)聯(lián)函數(shù)
在 Kotlin 中通過 inline-functions
(內(nèi)聯(lián)函數(shù)) 實(shí)現(xiàn)函數(shù)內(nèi)聯(lián)硝清,內(nèi)聯(lián)的作用:提升運(yùn)行效率辅斟,調(diào)用被 inline
修飾符的函數(shù),會(huì)把方法體內(nèi)的代碼放到調(diào)用的地方芦拿,其主要目的提高性能士飒,減少對(duì)象的創(chuàng)建。內(nèi)聯(lián)函數(shù)代碼如下所示蔗崎。
fun testInline() {
printByteCode()
}
inline fun printByteCode() {
println("printByteCode")
}
// 編譯之后
public static final void testInline() {
String var1 = "printByteCode";
System.out.println(var1);
}
inline
修飾的函數(shù)適用于以下情況
-
inline
修飾符適用于把函數(shù)作為另一個(gè)函數(shù)的參數(shù)酵幕,例如高階函數(shù)filter
、map
缓苛、joinToString
或者一些獨(dú)立的函數(shù)repeat
-
inline
操作符適合和reified
操作符結(jié)合在一起使用 - 如果函數(shù)體很短芳撒,使用
inline
操作符提高效率
如果使用 inline
修飾符標(biāo)記普通函數(shù),Android Studio 會(huì)給一個(gè)大大大的警告未桥,如下圖所示笔刹,這是為了防止 inline
操作符濫用而帶來的性能損失。
但是如果函數(shù)體很短冬耿,想通過 inline
操作符提高效率舌菜,又想消除掉警告,可以前往查看 為數(shù)不多的人知道的 Kotlin 技巧及解析(三) 文章中的 「Kotlin 注解在項(xiàng)目中的使用」淆党。
代碼示例倉庫地址:https://github.com/hi-dhl/KtKit
內(nèi)聯(lián)類
內(nèi)聯(lián)類是一個(gè)被忽略酷师,非常有用的特性。有時(shí)必要的業(yè)務(wù)邏輯染乌,需要將基本數(shù)據(jù)類型山孔、String 等等參數(shù)封裝在一個(gè) Model 中,然后在 Model 中封裝一些方法荷憋,對(duì)這個(gè)參數(shù)做檢查台颠、驗(yàn)證等等操作。
參數(shù)被封裝之后勒庄,需要?jiǎng)?chuàng)建包裝對(duì)象串前,對(duì)象的創(chuàng)建在堆中進(jìn)行分配,數(shù)據(jù)量很大的情況实蔽,對(duì)性能的損耗也非常大荡碾,例如:內(nèi)存的占用,運(yùn)行時(shí)的效率局装,頻繁創(chuàng)建對(duì)象坛吁,導(dǎo)致 GC 回收大量對(duì)象帶來的卡頓問題等等。
基本數(shù)據(jù)類型铐尚、String 等等運(yùn)行時(shí) JVM 會(huì)對(duì)它進(jìn)行優(yōu)化拨脉,但是如果將這些參數(shù)封裝在一個(gè)類中,包裝類不會(huì)做任何處理宣增,依然會(huì)在堆中創(chuàng)建對(duì)象玫膀。
所以為了減少性能的損耗,避免對(duì)象的創(chuàng)建爹脾,因此 Kotlin 推出了一個(gè)內(nèi)聯(lián)類 inline-classes帖旨。內(nèi)聯(lián)類只能在構(gòu)造函數(shù)中傳入一個(gè)參數(shù),參數(shù)需要用 val 聲明灵妨,編譯之后碉就,會(huì)替換為傳進(jìn)去的值,代碼如下所示闷串。
inline class User(val name: String)
fun testInline() {
println(User("DHL"))
}
// 編譯之后
public static final void testInline() {
System.out.println("DHL");
}
正如你所見瓮钥,編譯后的 Java 代碼并沒有創(chuàng)建額外的對(duì)象,在 Kotlin 中創(chuàng)建的對(duì)象被替換為傳進(jìn)去的值烹吵。
Java 如何調(diào)用接受內(nèi)聯(lián)類的函數(shù)
在 Kotlin 中聲明一個(gè)函數(shù)碉熄,并且將 inline class
作為參數(shù)傳遞,編譯成 Java 代碼之后肋拔,函數(shù)名稱會(huì)被打亂锈津,代碼如下所示。
inline class User(val name: String)
fun login(user: User) {
}
// 編譯后的代碼
public static final void login_FY_U7ig/* $FF was: login-FY_U7ig*/(@NotNull String user) {
}
編譯后的函數(shù)名稱會(huì)被打亂凉蜂,例如 login-FY_U7ig
琼梆。 但是這樣存在一個(gè)問題性誉, Java 無法調(diào)用接受內(nèi)聯(lián)類的函數(shù),代碼如下所示茎杂。
inline class User(val name: String)
fun login(user: User) {
}
fun login(passwd: String) {
}
// Kotlin 編譯正常
login(User("DHL"))
login("ByteCode")
// Java 編譯失敗
MainKt.login("DHL");
在 Kotlin 中調(diào)用错览,可以正常編譯,因?yàn)閮?nèi)聯(lián)的原因煌往,導(dǎo)致 Java 調(diào)用就會(huì)失敗倾哺,如下圖所示。
為了能夠在 Java 中正常調(diào)用刽脖,因此添加了注解 @JvmName
更改函數(shù)名稱羞海,來解決這個(gè)問題,代碼如下所示曲管。
inline class User(val name: String)
@JvmName("loginWithName")
fun login(user: User) {
}
fun login(passwd: String) {
}
// Kotlin 編譯正常
login(User("DHL"))
login("ByteCode")
// Java 編譯正常
MainKt.loginWithName("DHL");
所以無論是 Inline classes
還是 Value classes
如果沒有添加 @JvmName
注解却邓,都會(huì)存在這個(gè)問題。將生成的函數(shù)名稱打亂院水,是為了防止方法重載沖突申尤,或者 Java 意外調(diào)用。
Inline classes
是在 Kotlin 1.3 引入的 衙耕,在 Kotlin 1.5 時(shí)進(jìn)入了穩(wěn)定版本昧穿,廢棄了 inline
修飾符,引入了 Value classes
橙喘。
什么是 Value classes
Inline classes
是 Value classes
的子集时鸵, Value classes
比 Inline classes
會(huì)得到更多優(yōu)化,現(xiàn)階段 Value classes
和 Inline classes
一樣厅瞎,只能在構(gòu)造函數(shù)中傳入一個(gè)參數(shù)饰潜,參數(shù)需要用 val
聲明,將來可以在構(gòu)造函數(shù)中添加多個(gè)參數(shù)和簸,但是每個(gè)參數(shù)都需要用 val
聲明彭雾,官方說明如下圖所示。
將來如果支持添加多個(gè)參數(shù)锁保,那么它的使用范圍會(huì)越來越廣的薯酝。
升級(jí)到 Kotlin 1.5 之后,Inline classes
將被棄用爽柒,如下圖所示吴菠,編譯器將會(huì)給出警告。
根據(jù)提示目前唯一需要改變的是語法 inline
替換為 value
, 然后在添加 @JvmInline
注解即可浩村。
@JvmInline
value class User(val name: String)
編譯后的效果和 Inline classes
是一樣的做葵,因此后面的案例將會(huì)使用 Value classes
。
Value classes 不能被繼承
因?yàn)?value class
編譯后將會(huì)添加 fianl
修飾符心墅,因此不能被繼承酿矢,同樣也不能繼承其他的類榨乎,如下圖所示。
Value classes 可以實(shí)現(xiàn)接口
雖然 value class
不能繼承其他的類瘫筐,但是可以實(shí)現(xiàn)接口蜜暑,代碼如下所示。
interface LoginInterface
@JvmInline
value class User(val name: String) : LoginInterface
fun testInline() {
println(User("DHL"))
}
// 編譯后的代碼
public static final void testInline() {
User var0 = User.box-impl(User.constructor-impl("DHL"));
System.out.println(var0);
}
public static final User box_impl/* $FF was: box-impl*/(String v) {
return new User(v);
}
正如代碼所示严肪,當(dāng) value class
實(shí)現(xiàn)接口時(shí)史煎,失去了內(nèi)聯(lián)效果谦屑,依然會(huì)在堆中創(chuàng)建 User
對(duì)象驳糯,除了實(shí)現(xiàn)接口的時(shí)候,沒有內(nèi)聯(lián)效果氢橙,當(dāng)對(duì)象為空的時(shí)候酝枢,也會(huì)失去了內(nèi)聯(lián)效果。
當(dāng)傳遞的對(duì)象為空的時(shí)候悍手,會(huì)失去內(nèi)聯(lián)效果
當(dāng)構(gòu)造函數(shù)的參數(shù)為基本數(shù)據(jù)類型帘睦,且傳遞的參數(shù) value class
的對(duì)象為空時(shí),將失去內(nèi)聯(lián)效果坦康,代碼如下所示竣付。
@JvmInline
value class User(val age: Int)
fun login(user: User?): Int = user?.age ?: 0
fun testInline() {
println(login(User(10)))
}
// 編譯后的代碼
public static final int login_js0Jwf8/* $FF was: login-js0Jwf8*/(@Nullable User user) {
return user != null ? user.unbox-impl() : 0;
}
public static final void testInline() {
int var0 = login-js0Jwf8(User.box-impl(10));
System.out.println(var0);
}
public static final User box_impl/* $FF was: box-impl*/(int v) {
return new User(v);
}
正如你所見,依然會(huì)在堆中創(chuàng)建 User
對(duì)象滞欠,失去了內(nèi)聯(lián)效果古胆,這是因?yàn)?Int
不能直接轉(zhuǎn)換為 null
。
當(dāng)構(gòu)造函數(shù)的參數(shù)為 String
筛璧,且傳遞的參數(shù) value class
的對(duì)象為空時(shí)逸绎,內(nèi)聯(lián)效果不會(huì)受到影響,代碼如下所示夭谤。
@JvmInline
value class User(val name: String)
fun login(user: User?): String = user?.name ?: ""
fun testInline() {
println(login(User("DHL")))
}
// 編譯后的代碼
public static final String login_js0Jwf8/* $FF was: login-js0Jwf8*/(@Nullable String user) {
// ......
return var10000;
}
public static final void testInline() {
String var0 = login-js0Jwf8("DHL");
System.out.println(var0);
}
編譯后的 Java 代碼并沒有創(chuàng)建對(duì)象棺牧,傳遞給方法 login
的參數(shù) User
被替換為傳進(jìn)去的值 String
。
Value class 禁止使用 === 可以使用 ==
Kotlin 中的操作符 ==
和 ===
的區(qū)別朗儒,以及它們分別在什么場景下使用颊乘,更多信息可以前往查看 揭秘 Kotlin 中的 == 和 ===,這里簡單介紹一下醉锄。
Kotlin 提供了兩種方式用于對(duì)象的比較疲牵。
-
比較對(duì)象的結(jié)構(gòu)是否相等(
==
或者equals
)Kotlin 中的操作符
==
等價(jià)于equals
用于比較對(duì)象的結(jié)構(gòu)是否相等, 很多情況下使用的是==
,因?yàn)閷?duì)于浮點(diǎn)類型 Float 和 Double榆鼠,其實(shí)現(xiàn)方法equals
不遵循 IEEE 754 浮點(diǎn)運(yùn)算標(biāo)準(zhǔn)纲爸。 -
比較對(duì)象的引用是否相等 (
===
)Kotlin 中的操作符
===
用于比較對(duì)象的引用是否指向同一個(gè)對(duì)象,運(yùn)行時(shí)如果是基本數(shù)據(jù)類型===
等價(jià)于==
妆够。
其中 value class
比較特殊识啦,禁止使用操作符 ===
负蚊,如下圖所示,編譯器將會(huì)給出警告颓哮。
因?yàn)椴僮鞣?===
用于比較對(duì)象的引用是否相等家妆,因?yàn)閮?nèi)聯(lián)的原因,最終會(huì)替換為傳進(jìn)去的值冕茅。
但是操作符 ==
是不受影響的伤极,操作符 ==
用于比較對(duì)象的結(jié)構(gòu)是否相等,代碼如下所示姨伤。
fun testInline() {
val u1 = User("DHL")
val u2 = User("DHL")
println(u1 == u2)
}
// 編譯后的代碼
public static final void testInline() {
String u1 = "DHL";
String u2 = "DHL";
boolean var2 = User.equals-impl0(u1, u2);
System.out.println(var2);
}
如果有幫助 點(diǎn)個(gè)贊 就是對(duì)我最大的鼓勵(lì)
最后推薦長期更新和維護(hù)的項(xiàng)目:
個(gè)人博客哨坪,將所有文章進(jìn)行分類,歡迎前去查看 https://hi-dhl.com
KtKit 小巧而實(shí)用乍楚,用 Kotlin 語言編寫的工具庫当编,歡迎前去查看 KtKit
計(jì)劃建立一個(gè)最全、最新的 AndroidX Jetpack 相關(guān)組件的實(shí)戰(zhàn)項(xiàng)目 以及 相關(guān)組件原理分析文章徒溪,正在逐漸增加 Jetpack 新成員忿偷,倉庫持續(xù)更新,歡迎前去查看 AndroidX-Jetpack-Practice
LeetCode / 劍指 offer / 國內(nèi)外大廠面試題 / 多線程 題解臊泌,語言 Java 和 kotlin鲤桥,包含多種解法、解題思路渠概、時(shí)間復(fù)雜度茶凳、空間復(fù)雜度分析
近期必讀熱門文章
- Google 宣布廢棄 LiveData.observe 方法
- 使用 kotlin 需要注意的一個(gè)細(xì)節(jié)
- 跟源碼學(xué)數(shù)據(jù)結(jié)構(gòu) | 循環(huán)隊(duì)列
- 圖解 ArrayDeque 比 LinkedList 快
- 為什么不推薦 ArrayDeque 代替 Stack
- 算法動(dòng)畫圖解 | 被 "廢棄" 的 Java 棧,為什么還在用
- 影響性能的 Kotlin 代碼(一)
- Jetpack Splashscreen 解析 | 助力新生代 IT 農(nóng)民工 事半功倍
- 為數(shù)不多的人知道的 Kotlin 技巧及解析(三)
- 為數(shù)不多的人知道的 Kotlin 技巧以及 原理解析(二)
- 為數(shù)不多的人知道的 Kotlin 技巧以及 原理解析(一)
- 揭秘 Kotlin 中的 == 和 ===
- Kotlin 密封類進(jìn)化了
- Kotlin 中的密封類 優(yōu)于 帶標(biāo)簽的類
- Kotlin Sealed 是什么高氮?為什么 Google 都在用
- Android 12 行為變更慧妄,對(duì)應(yīng)用產(chǎn)生的影響
- 圖解多平臺(tái) AndroidStudio 技巧(三)
- Kotlin StateFlow 搜索功能的實(shí)踐 DB + NetWork
- Kotlin 插件的落幕,ViewBinding 的崛起