官方文檔:https://kotlinlang.org/docs/inline-classes.html
注:
inline class
關(guān)鍵字已經(jīng)被廢棄,取而代之的是value class
∪ゼ現(xiàn)在使用內(nèi)聯(lián)類需要定義類為value class
,并使用@JvmInline注解進(jìn)行標(biāo)注褂乍。
一、使用場景
有時(shí)候即硼,根據(jù)業(yè)務(wù)需求树叽,我們需要一些包裝類。但是谦絮,包裝類在運(yùn)行時(shí)會(huì)造成一些不可避免的額外開支题诵,比如堆上分配的額外空間洁仗。尤其是對于基本類型的包裝類——因?yàn)榛绢愋驮谶\(yùn)行時(shí)會(huì)有很多其他優(yōu)化,而包裝類型沒有性锭。于是赠潦,內(nèi)聯(lián)類便應(yīng)運(yùn)而生了。
內(nèi)聯(lián)類在編碼時(shí)作為一個(gè)其他類型的包裝類使用草冈,而在運(yùn)行時(shí)會(huì)被拆開作為其內(nèi)部值類型使用她奥。
例如,當(dāng)我們設(shè)計(jì)了一個(gè)動(dòng)畫:
class Animation(duration: Int) {
// ...
}
duration
參數(shù)可能會(huì)讓人迷惑:它的單位是什么怎棱?秒或者毫秒哩俭?(雖然注釋可以解決一切問題,但它不在討論范圍之內(nèi))這個(gè)時(shí)候拳恋,就可以用到內(nèi)聯(lián)類凡资。
我們可以創(chuàng)建一系列的內(nèi)聯(lián)類,來表示不同的時(shí)間單位:
@JvmInline
value class Millis(val value: Int)
@JvmInline
value class Second(val value: Int)
// ...
假使 duration
參數(shù)單位是毫秒谬运,那么將其類型修改為 Millis
類型即可:
class Animation(duration: Millis) {
// ...
}
這樣隙赁,當(dāng)創(chuàng)建 Animation
對象的時(shí)候,就需要強(qiáng)制傳入一個(gè) Millis
類型的對象梆暖;如果傳入的是一個(gè) Second
類型的對象伞访,編譯器就會(huì)報(bào)錯(cuò)。
二轰驳、內(nèi)聯(lián)類允許的成員
內(nèi)聯(lián)類允許函數(shù)厚掷、init
塊、以及沒有 backing field 的屬性级解。
@JvmInline
value class Name(val s: String) {
init {
require(s.length > 0) { }
}
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // method `greet` is called as a static method
println(name.length) // property getter is called as a static method
}
三冒黑、內(nèi)聯(lián)類和普通包裝類的區(qū)別
對于原生類型來說,在運(yùn)行時(shí)會(huì)進(jìn)行大量的優(yōu)化蠕趁,而包裝類不會(huì)進(jìn)行處理薛闪。
而內(nèi)聯(lián)類在運(yùn)行時(shí)辛馆,會(huì)自動(dòng)使用內(nèi)聯(lián)類型而不是包裝類型進(jìn)行處理俺陋。
例如以下例子中:
// 毫秒
inline class Millisecond(val value: Long)
private fun doSomething(millisecond: Millisecond) {
Log.i(TAG, "doSomething: $millisecond")
}
通過 IDE 的 Kotlin 字節(jié)碼反編譯功能,可以看到昙篙,生成的 Kotlin 字節(jié)碼已經(jīng)沒有 Millisecond
類型了腊状,而是直接使用的 Long
類型:
而普通的包裝類則不會(huì)進(jìn)行如此優(yōu)化:
class LongWrapper(val millis: Long)
private fun doSomething2(millisecond: LongWrapper) {
Log.i(TAG, "doSomething: $millisecond")
}
四、其他
1. 繼承
內(nèi)聯(lián)類只允許繼承接口苔可,而不允許繼承類缴挖,也不允許被其他類繼承。
2. 與 typealias 的相比
在讀取值的時(shí)候焚辅,value class
和 typealias
起到了類似的作用映屋;但是苟鸯,當(dāng)進(jìn)行賦值的時(shí)候,情況就變得不一樣了棚点。
假設(shè)我們現(xiàn)在使用一個(gè)類型 Name
早处,表示一個(gè)字符串值。同時(shí)瘫析,有兩個(gè)函數(shù) setString
和 setName
:
fun setName(name: Name) {}
fun setString(string: String) {}
在使用 typealias
的情況下砌梆,不管是 setString
還是 setName
,均可以傳入 Name
或是 String
類型的參數(shù):
typealias Name = String
fun main() {
val name: Name = "Bob"
setName("Bob") // √
setString(name) // √
}
但是贬循,如果使用的是內(nèi)聯(lián)類咸包,則二者均是不被允許的:
@JvmInline
value class Name(value: String)
fun main() {
val name: Name = "Bob"
setName("Bob") // ×
setString(name) // ×
}
五、總結(jié)
- 內(nèi)聯(lián)類相當(dāng)于一個(gè)包裝類杖虾,但是在編譯時(shí)會(huì)自動(dòng)進(jìn)行拆包使用內(nèi)部數(shù)據(jù)類型烂瘫。
- 在實(shí)際使用中,和普通包裝類相同。
- 內(nèi)聯(lián)類最大的優(yōu)點(diǎn)在于對于基本類型的包裝不會(huì)消耗額外的性能。