翻譯說(shuō)明:
原標(biāo)題: Inline Classes and Autoboxing in Kotlin
原文地址: https://typealias.com/guides/inline-classes-and-autoboxing/
原文作者: Dave Leeds
在上一篇文章中,我們知道了Kotlin的實(shí)驗(yàn)階段的新特性內(nèi)聯(lián)類(lèi)是如何讓我們"創(chuàng)建需要的數(shù)據(jù)類(lèi)型但是不會(huì)損失我們需要的性能"。我們了解到:
- 1、內(nèi)聯(lián)類(lèi)包裝了基礎(chǔ)類(lèi)型的值
- 2、當(dāng)代碼被編譯的時(shí)候,內(nèi)聯(lián)類(lèi)的實(shí)例將會(huì)被替換成基礎(chǔ)類(lèi)型的值
- 3、這可以大大提高我們應(yīng)用程序的性能,特別是當(dāng)基礎(chǔ)類(lèi)型是一個(gè)基本數(shù)據(jù)類(lèi)型時(shí)。
但是在某些情況下,內(nèi)聯(lián)類(lèi)實(shí)際上比傳統(tǒng)的普通類(lèi)執(zhí)行速度更慢! 在這篇文章中,我們將去探索在不同的場(chǎng)景下使用內(nèi)聯(lián)類(lèi)編譯代碼中到底會(huì)發(fā)生什么- 因?yàn)槿绻覀冎廊绾胃咝У厥褂盟麄儯覀儾拍軓闹蝎@得更高的性能。
請(qǐng)記住-內(nèi)聯(lián)類(lèi)始終還是一個(gè)實(shí)驗(yàn)性的特性。盡管我一直在寫(xiě)內(nèi)聯(lián)類(lèi)系列的文章,并且內(nèi)聯(lián)類(lèi)也會(huì)經(jīng)歷很多的迭代和修改。本文目前基于Kotlin 1.3 Release Candidate 146中實(shí)現(xiàn)的內(nèi)聯(lián)類(lèi)。
此外,如果你還沒(méi)有閱讀過(guò)有關(guān)內(nèi)聯(lián)類(lèi)的文章,那么你首先要閱讀上一篇文章 [譯]Kotlin中內(nèi)聯(lián)類(lèi)(inline class)完全解析(一)。那樣你就會(huì)全身心投入并準(zhǔn)備好閱讀這篇文章。
好的,讓我們現(xiàn)在開(kāi)始吧!
高性能的奧秘
Alan被徹底激怒了!在學(xué)習(xí)完內(nèi)聯(lián)類(lèi)之后,他決定開(kāi)始在他正在研究的游戲原型中使用內(nèi)聯(lián)類(lèi)。為了看看內(nèi)聯(lián)類(lèi)比傳統(tǒng)的普通類(lèi)到底有多好,他在他游戲評(píng)分系統(tǒng)中寫(xiě)了一些有關(guān)內(nèi)聯(lián)類(lèi)的代碼:
interface Amount { val value: Int }
inline class Points(override val value: Int) : Amount
private var totalScore = 0L
fun main() {
repeat(1_000_000) {
val points = Points(it)
repeat(10_000) {
addToScore(points)
}
}
}
fun addToScore(amount: Amount) {
totalScore += amount.value
}
Alan編寫(xiě)了這段代碼的測(cè)試用例。然后,他刪除第二行inline關(guān)鍵字,并再次運(yùn)行這個(gè)測(cè)試用例。
令他驚訝的是,使用內(nèi)聯(lián)修飾符inline運(yùn)行速度實(shí)際上明顯比沒(méi)有內(nèi)聯(lián)情況慢很多。
“到底發(fā)生了什么?”他想知道。
雖然說(shuō)內(nèi)聯(lián)類(lèi)可以比傳統(tǒng)的普通類(lèi)更高性能運(yùn)行,但是這一切都取決于我們?nèi)绾魏侠硎褂盟鼈?因?yàn)槲覀內(nèi)绾问褂盟鼈儧Q定了值是否在編譯代碼中真的進(jìn)行內(nèi)聯(lián)操作。
這是正確的 - 內(nèi)聯(lián)類(lèi)的實(shí)例并不總是在編譯的代碼中內(nèi)聯(lián)。
什么時(shí)候內(nèi)聯(lián)類(lèi)不會(huì)被內(nèi)聯(lián)
讓我們?cè)僖黄鹂聪翧lan的代碼,看看我們是否可以弄明白為什么他寫(xiě)的內(nèi)聯(lián)類(lèi)可能沒(méi)有被內(nèi)聯(lián)。
我們先來(lái)看下這段代碼:
interface Amount { val value: Int }
inline class Points(override val value: Int) : Amount
在這段代碼中,內(nèi)聯(lián)類(lèi)Points實(shí)現(xiàn)了Amount接口。當(dāng)我們調(diào)用addToScore()函數(shù)時(shí),會(huì)引發(fā)一個(gè)有趣的現(xiàn)象,盡管...
fun addToScore(amount: Amount) {
totalScore += amount.value
}
addToScore()函數(shù)可以接收任何Amount類(lèi)型的對(duì)象。由于Points是Amount的子類(lèi)型,所以我們可以傳入一個(gè)Points類(lèi)型實(shí)例對(duì)象給這個(gè)函數(shù)。
這是基本的常識(shí),沒(méi)問(wèn)題吧?
但是... 假設(shè)我們的Points類(lèi)的實(shí)例都是內(nèi)聯(lián)的-也就是說(shuō),在源碼被編譯的階段,它們(Points類(lèi)的實(shí)例)會(huì)被基礎(chǔ)類(lèi)型(這里是Int整數(shù)類(lèi)型)給替換掉。-可是addToScore()函數(shù)怎么能接收一個(gè)基礎(chǔ)類(lèi)型(這里是Int整數(shù)類(lèi)型)的實(shí)參呢?畢竟,基礎(chǔ)類(lèi)型Int并沒(méi)有去實(shí)現(xiàn)Amount的接口。
那么編譯后的代碼怎么可能會(huì)向addToScore函數(shù)發(fā)送一個(gè)Int類(lèi)型(更確切的說(shuō)是Java中的int類(lèi)型)的實(shí)參,因?yàn)閕nt類(lèi)型是不會(huì)去實(shí)現(xiàn)Amount接口的。
答案當(dāng)然是它不能啊!
因此,在這種場(chǎng)景下,Kotlin還是繼續(xù)使用為Points類(lèi)型,而不是在編譯代碼中使用整數(shù)替換。我們將這個(gè)Points類(lèi)稱為包裝類(lèi)型,而不是基礎(chǔ)類(lèi)型Int。
最重要的是需要注意,這并不意味這該類(lèi)永遠(yuǎn)不會(huì)被內(nèi)聯(lián)。它只意味著代碼中某些地方?jīng)]有被內(nèi)聯(lián)。例如,讓我們來(lái)看一下Alan中的代碼,看看Points什么時(shí)候是內(nèi)聯(lián)的,什么時(shí)候不是內(nèi)聯(lián)的。
fun main() {
repeat(1_000_000) {
val points = Points(it) // <-- Points is inlined as an Int here(Points類(lèi)在這是內(nèi)聯(lián)的,并被當(dāng)做Int替換)
repeat(10_000) {
addToScore(points) // <-- Can't pass Int here, so sends it
// as an instance of Points instead.(因?yàn)檫@里不能被傳入Int,所以這里必須傳入Points實(shí)例)
}
}
}
編譯器將盡可能使用基礎(chǔ)類(lèi)型(例如,Int,編譯為int),但是當(dāng)它不能被當(dāng)做基礎(chǔ)類(lèi)型使用時(shí),它會(huì)自動(dòng)實(shí)例化包裝類(lèi)型的實(shí)例(例如,Points)并把它傳遞出去??梢韵胂笙逻@是編譯后的代碼(在Java中)大致如下:
public static void main(String[] arg) {
for(int i = 0; i < 1000000; i++) {
int points = i; // <--- Inlined here(此處內(nèi)聯(lián))
for(short k = 0; k < 10000; k++) {
addToScore(new Points(points)); // <--- Automatic instantiation!(自動(dòng)實(shí)例化)
}
}
}
您可以將Points類(lèi)想象為包裝基礎(chǔ)Int值的箱子。
因?yàn)榫幾g器會(huì)自動(dòng)將值放入箱子中,所以我們把這個(gè)過(guò)程叫做自動(dòng)裝箱。
現(xiàn)在我們知道了為什么Alan的代碼在使用內(nèi)聯(lián)類(lèi)的時(shí)候運(yùn)行速度會(huì)比普通類(lèi)要慢。每次調(diào)用addToScore()函數(shù)時(shí),都會(huì)自動(dòng)實(shí)例化一個(gè)新的Points類(lèi)的實(shí)例。所以在內(nèi)部循環(huán)迭代過(guò)程中總共發(fā)生100億次堆分配過(guò)程,這就是速度減慢的原因。
(相比之下,使用傳統(tǒng)的普通類(lèi),而堆分配過(guò)程只發(fā)生在外層for循環(huán)中,總共也只有100萬(wàn)次).
這種自動(dòng)裝箱過(guò)程一般還是很有用的-它是保證類(lèi)型安全所必需的操作,當(dāng)然,它同時(shí)也帶來(lái)了性能開(kāi)銷(xiāo)成本,每次創(chuàng)建一個(gè)堆上新對(duì)象時(shí)就會(huì)存在這樣性能開(kāi)銷(xiāo)。所以這就意味著作為開(kāi)發(fā)者,了解哪種場(chǎng)景下會(huì)發(fā)生Kotlin進(jìn)行自動(dòng)裝箱操作是非常重要的,這樣我們就可以更明智地決定如何去使用內(nèi)聯(lián)類(lèi)了。
那么,接下來(lái)讓我們一起來(lái)看看自動(dòng)裝箱過(guò)程可能會(huì)在哪些場(chǎng)景被觸發(fā)!
引用超類(lèi)型時(shí)會(huì)觸發(fā)自動(dòng)裝箱操作
正如我們所看到的那樣,當(dāng)我們將Points對(duì)象傳遞給接收Amount類(lèi)型作為形參的函數(shù)式,就觸發(fā)了自動(dòng)裝箱操作。
即使你的內(nèi)聯(lián)類(lèi)沒(méi)有去實(shí)現(xiàn)接口,但是必須記住一點(diǎn),內(nèi)聯(lián)類(lèi)和普通類(lèi)一樣,所有內(nèi)聯(lián)類(lèi)都是Any的子類(lèi)型。所以當(dāng)你將內(nèi)聯(lián)類(lèi)的實(shí)例賦值給Any類(lèi)型的變量或者傳遞給Any類(lèi)型作為形參的函數(shù)時(shí),都會(huì)觸發(fā)預(yù)期中的自動(dòng)裝箱操作。
例如,假設(shè)我們有一個(gè)可以記錄日志的服務(wù)接口:
interface LogService {
fun log(any: Any)
}
由于這個(gè)log()函數(shù)可以接收一個(gè)Any類(lèi)型的實(shí)參,一旦你傳入一個(gè)Points的實(shí)例給這個(gè)函數(shù),那么這個(gè)實(shí)例就會(huì)觸發(fā)自動(dòng)裝箱操作。
val points = Points(5)
logService.log(points) // <--- Autoboxing happens here(此處發(fā)生自動(dòng)裝箱操作)
總之一句話 - 當(dāng)你使用內(nèi)聯(lián)類(lèi)的實(shí)例(其中需要超類(lèi)型)時(shí),可能會(huì)觸發(fā)自動(dòng)裝箱。
自動(dòng)裝箱與泛型
當(dāng)您使用具有泛型的內(nèi)聯(lián)類(lèi)時(shí),也會(huì)發(fā)生自動(dòng)裝箱。例如:
val points = Points(5)
val scoreAudit = listOf(points) // <-- Autoboxing here(此處發(fā)生自動(dòng)裝箱操作)
fun <T> log(item: T) {
println(item)
}
log(points) // <-- Autoboxing here(此處發(fā)生自動(dòng)裝箱操作)
在使用泛型時(shí),Kotlin為我們自動(dòng)裝箱是件好事,否則我們會(huì)在編譯代碼中會(huì)遇到類(lèi)型安全的問(wèn)題。例如,類(lèi)似于我們之前的場(chǎng)景,將整數(shù)類(lèi)型的值插入到MutableList<Amount>集合類(lèi)型中是不安全的,因?yàn)檎麛?shù)類(lèi)型并沒(méi)有去實(shí)現(xiàn)Amount的接口。
而且,一旦考慮到與Java互操作時(shí),它就會(huì)變得更加復(fù)雜,例如:
- 如果Java將
List<Points>保存為List<Integer>,它是否應(yīng)該可以將該類(lèi)型的集合傳遞給如下這個(gè)Kotlin函數(shù)呢?
fun receive(list: List<Int>)
- Java將它傳遞給下面這個(gè)Kotlin函數(shù)又會(huì)怎么樣呢?
fun receive(list: List<Amount>)
- Java能否可以構(gòu)建自己的整數(shù)集合并把它傳遞給下面這個(gè)Kotlin函數(shù)?
fun receive(list: List<Points>)
相反,Kotlin通過(guò)自動(dòng)裝箱的操作來(lái)避免了內(nèi)聯(lián)類(lèi)和泛型一起使用時(shí)的問(wèn)題。
我們已經(jīng)看到超類(lèi)型和泛型兩種場(chǎng)景下如何觸發(fā)自動(dòng)裝箱操作。其實(shí)我們還有一個(gè)值得去深究的場(chǎng)景 - 那就是可空性的場(chǎng)景!
自動(dòng)裝箱和可空性
當(dāng)涉及到可空類(lèi)型的值時(shí),也可能會(huì)觸發(fā)自動(dòng)裝箱操作。這個(gè)規(guī)則有點(diǎn)不同,主要取決于基礎(chǔ)類(lèi)型是引用類(lèi)型還是基本數(shù)據(jù)類(lèi)型。所以讓我們一次性來(lái)搞定它們。
引用類(lèi)型
當(dāng)我們討論內(nèi)聯(lián)類(lèi)的可空性時(shí),有兩種場(chǎng)景可以為空:
- 1、內(nèi)聯(lián)類(lèi)自己的基礎(chǔ)類(lèi)型存在可空和非空的情況
- 2、使用內(nèi)聯(lián)類(lèi)的地方存在可空和非空的情況
例如:
// 1. The underlying type itself can be nullable (`String?`)
// 1. 基礎(chǔ)類(lèi)型自己存在可空
inline class Nickname(val value: String?)
// 2. The usage can be nullable (`Nickname?`)
//使用內(nèi)聯(lián)類(lèi)時(shí)存在可空
fun logNickname(nickname: Nickname?) {
// ...
}
由于我們有兩種場(chǎng)景,并且每個(gè)場(chǎng)景下又存在非空與可空兩種情況,因?yàn)榭偣残枰紤]四種情況。所以我們?yōu)槿缦滤姆N場(chǎng)景制作一張真值表!
對(duì)于每一種情況,我們將考慮:
- 1、基礎(chǔ)類(lèi)型的可空和非空
- 2、使用內(nèi)聯(lián)類(lèi)地方的可空和非空
-
3、以及每種情況編譯后的是否觸發(fā)自動(dòng)裝箱操作
image
好消息的是,當(dāng)基礎(chǔ)類(lèi)型是引用類(lèi)型時(shí),大多數(shù)的情況下,使用的內(nèi)聯(lián)類(lèi)都將被編譯成基礎(chǔ)類(lèi)型。這就意味著基礎(chǔ)類(lèi)型的值可以被使用且不會(huì)觸發(fā)自動(dòng)裝箱操作。
這里只有一種情況會(huì)觸發(fā)自動(dòng)裝箱操作,我們需要注意 - 當(dāng)基礎(chǔ)類(lèi)型和使用類(lèi)型都為可空類(lèi)型時(shí)。
為什么在這種情況下會(huì)觸發(fā)自動(dòng)裝箱操作?
因?yàn)楫?dāng)這兩種場(chǎng)景都存在值可空情況下,你最終得到的將是不同的代碼分支,具體取決于這兩種場(chǎng)景哪一種是空的。例如,看看這段代碼:
inline class Nickname(val value: String?)
fun greet(name: Nickname?) {
if (name == null) {
println("Who's there?")
} else if (name.value == null) {
println("Hello, there.")
} else {
println("Greetings, ${name.value}")
}
}
fun main() {
greet(Nickname("T-Bone"))
greet(Nickname(null))
greet(null)
}
如果name形參是使用了基礎(chǔ)類(lèi)型的值-換句話說(shuō),如果編譯的代碼是void greet(String name)-那么它就不可能出現(xiàn)下面三個(gè)判斷分支。那就不清楚name是否為空是應(yīng)該打印Who's There還是Hello There.
相反,函數(shù)如果編譯成這樣void greet(NickName name)將是有效的.這意味著只要我們調(diào)用該函數(shù),Kotlin就會(huì)根據(jù)需要自動(dòng)觸發(fā)裝箱操作來(lái)包裝基礎(chǔ)類(lèi)型的值。
嗯,這是可以為空的引用類(lèi)型!但是可以為空的基本數(shù)據(jù)類(lèi)型呢?
基本數(shù)據(jù)類(lèi)型
當(dāng)內(nèi)聯(lián)類(lèi)、基本數(shù)據(jù)類(lèi)型和可空性這三種因素碰在一起,我們會(huì)得到一些有趣的自動(dòng)裝箱的場(chǎng)景。正如我們?cè)谏厦娴囊妙?lèi)型中看到的那樣,可空性出現(xiàn)場(chǎng)景取決于基礎(chǔ)類(lèi)型可空或非空以及使用內(nèi)聯(lián)類(lèi)地方的可空或非空。
// 1. The underlying type itself can be nullable (`Int?`)
// 1. 基礎(chǔ)類(lèi)型自己存在可空
inline class Anniversary(val value: Int?)
// 2. The usage can be nullable (`Anniversary?`)
//使用內(nèi)聯(lián)類(lèi)時(shí)存在可空
fun celebrate(anniversary: Anniversary?) {
// ...
}
讓我們構(gòu)建一個(gè)真值表,就像對(duì)上面的引用類(lèi)型一樣做出的總結(jié)
正如你所看到的那樣,上面表格中對(duì)于基本數(shù)據(jù)類(lèi)型的結(jié)果除了場(chǎng)景B不一樣,其他的場(chǎng)景都和引用類(lèi)型分析結(jié)果一樣。但是這里面還是涉及到了其他很多知識(shí),所以讓我們花點(diǎn)時(shí)間一一分析下每一種情況。
對(duì)于場(chǎng)景A. 很容易就能分析出來(lái)。因?yàn)檫@里根本就沒(méi)有可空類(lèi)型(都是非空類(lèi)型),所以類(lèi)型是內(nèi)聯(lián)的,正如我們所期望的那樣。
對(duì)于場(chǎng)景B. 這是一種完全不同于上一個(gè)真值表中的場(chǎng)景,不知道你是否還記得,JVM上的int和boolean等其他基本數(shù)據(jù)類(lèi)型實(shí)際上是不能為null的。因此,為了更好兼容null,Kotlin在此使用了包裝類(lèi)型(也就觸發(fā)了自動(dòng)裝箱操作)
對(duì)于場(chǎng)景C. 這種場(chǎng)景就更有意思了。一般來(lái)說(shuō),當(dāng)你有一個(gè)類(lèi)似Int可以為空的基本數(shù)據(jù)類(lèi)型時(shí),在Kotlin中,這種基本數(shù)據(jù)類(lèi)型會(huì)在編譯的時(shí)候轉(zhuǎn)換成Java中的基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的包裝器類(lèi)型-例如Integer,它(不像int)可以兼容null值。對(duì)于場(chǎng)景C而言,實(shí)際上在使用內(nèi)聯(lián)類(lèi)地方編譯時(shí)候卻使用基礎(chǔ)類(lèi)型,因?yàn)樗旧砬『檬且粋€(gè)Java中基本包裝器類(lèi)型。所以在某種層面上,你可以說(shuō)基礎(chǔ)類(lèi)型被自動(dòng)裝箱了,但是這種自動(dòng)裝箱操作和內(nèi)聯(lián)類(lèi)根本就沒(méi)有任何關(guān)系。
對(duì)于場(chǎng)景D. 類(lèi)似于上面引用類(lèi)型看到的那樣,當(dāng)基本類(lèi)型自身為可空以及使用內(nèi)聯(lián)類(lèi)地方為可空時(shí),Kotlin將在編譯時(shí)使用包裝器類(lèi)型。具體原因和引用類(lèi)型同理。
其他需要牢記的點(diǎn)
我們已經(jīng)介紹了可能導(dǎo)致自動(dòng)裝箱的主要場(chǎng)景。在使用內(nèi)聯(lián)類(lèi)時(shí),你可能會(huì)發(fā)現(xiàn)對(duì)Kotlin源碼編譯后的字節(jié)碼進(jìn)行反編譯,然后根據(jù)反編譯的Java代碼來(lái)分析是否出現(xiàn)自動(dòng)裝箱有很大的幫助。
要在IntelliJ或Android Studio中執(zhí)行此操作,只需轉(zhuǎn)到Tools - > Kotlin - >Show Kotlin Bytecode,然后單擊Decompile按鈕。
此外,請(qǐng)記住還有很多其他層面上都有可能影響內(nèi)聯(lián)類(lèi)的性能。即使你對(duì)自動(dòng)裝箱有了充分的了解,編譯器優(yōu)化(Kotlin編譯器和JIT編譯器)之類(lèi)的東西也會(huì)導(dǎo)致與我們的預(yù)期性能相差很大。如果需要真正了解編碼決策對(duì)性能的影響,唯一的辦法就是使用基準(zhǔn)測(cè)試工具(比如JMH)實(shí)際運(yùn)行測(cè)試。
總結(jié)
在本文中,我們探討了使用內(nèi)聯(lián)類(lèi)會(huì)出現(xiàn)一些性能影響,并了解到哪些場(chǎng)景下會(huì)進(jìn)行自動(dòng)裝箱。我們已經(jīng)看到如何使用內(nèi)聯(lián)類(lèi)并會(huì)對(duì)其性能產(chǎn)生影響,包括涉及到一些具體的使用場(chǎng)景:
- 超類(lèi)型
- 泛型
- 可空性
現(xiàn)在我們知道這一點(diǎn),我們可以做出更加明智的選擇,來(lái)高效使用內(nèi)聯(lián)類(lèi)。
你準(zhǔn)備好自己開(kāi)始使用內(nèi)聯(lián)類(lèi)了嗎? 你無(wú)需等待-你現(xiàn)在就可以在IDE中嘗試使用它!
譯者有話說(shuō)
這篇文章可以說(shuō)得上是我看過(guò)最好的一篇有關(guān)Kotlin內(nèi)聯(lián)類(lèi)性能優(yōu)化的文章了,感覺(jué)非常不錯(cuò),作者分析得很全面也很深入。就連官方也沒(méi)有給出過(guò)如此詳細(xì)介紹。關(guān)于譯文中有幾點(diǎn)我需要補(bǔ)充一下:
- 對(duì)于Alan那段糟糕的代碼使用inline class和普通class代碼比較,粗略算了下時(shí)間,對(duì)比了真的比較驚人:
可以看到inline class看似是個(gè)性能優(yōu)化操作,但是使用不當(dāng)性能反而比普通類(lèi)更加差。
- 有關(guān)譯文中的基礎(chǔ)類(lèi)型、基本數(shù)據(jù)類(lèi)型、引用類(lèi)型做一個(gè)對(duì)比解釋,怕有人發(fā)蒙。
基礎(chǔ)類(lèi)型: 實(shí)際上是針對(duì)內(nèi)聯(lián)類(lèi)中包裝的那個(gè)值的類(lèi)型,它和基礎(chǔ)數(shù)據(jù)類(lèi)型不是一個(gè)東西。這么說(shuō)吧,基礎(chǔ)類(lèi)型既可以是基本數(shù)據(jù)類(lèi)型也可以是引用類(lèi)型
基本數(shù)據(jù)類(lèi)型: 實(shí)際上就是常用的Int、Float、Double、Short、Long等類(lèi)型,注意String是引用類(lèi)型
引用類(lèi)型: 實(shí)際上就是除了基本數(shù)據(jù)類(lèi)型就是引用類(lèi)型,String和我們平時(shí)自定義的類(lèi)的類(lèi)型都屬于引用類(lèi)型。
- 關(guān)于上述基本數(shù)據(jù)類(lèi)型中的場(chǎng)景B,可能大家還是有點(diǎn)不能理解。這里給大家具體再分析下。
對(duì)于基礎(chǔ)數(shù)據(jù)類(lèi)型場(chǎng)景B,為什么會(huì)出現(xiàn)自動(dòng)裝箱操作?
這是因?yàn)樵贙otlin中使用內(nèi)聯(lián)類(lèi)的時(shí)候用了可空類(lèi)型,我們可以用反證法來(lái)理解下,假設(shè)使用可空類(lèi)型的內(nèi)聯(lián)類(lèi)地方被編譯成Java中的int等基本數(shù)據(jù)類(lèi)型,在Kotlin中類(lèi)似如下代碼:
inline class Age(val value: Int)
fun howOld(age: Age?) {
if(age == null){
...
}
}
編譯成類(lèi)似如下代碼:
void howOld(int age){
if(age == null){//這樣的代碼是會(huì)報(bào)錯(cuò)的
...
}
}
所以原假設(shè)不成立,Kotlin為了兼容null,不得不把它自動(dòng)裝箱使用包裝器類(lèi)型。
到這里有關(guān)內(nèi)聯(lián)類(lèi)的知識(shí)文章就完全結(jié)束了,由于內(nèi)聯(lián)類(lèi)還是一個(gè)實(shí)驗(yàn)性的特性,后期正式版本的API可能會(huì)有變動(dòng),當(dāng)然我也緊跟官方最新動(dòng)態(tài),如果變動(dòng)會(huì)盡快以文章形式總結(jié)出來(lái)。如果你這一期內(nèi)聯(lián)類(lèi)知識(shí)掌握了,后面在怎么變動(dòng),你都能很快掌握它,并也會(huì)得到更多自己的體會(huì)。歡迎繼續(xù)關(guān)注~~~
Kotlin系列文章,歡迎查看:
原創(chuàng)系列:
- Jetbrains開(kāi)發(fā)者日見(jiàn)聞(三)之Kotlin1.3新特性(inline class篇)
- JetBrains開(kāi)發(fā)者日見(jiàn)聞(二)之Kotlin1.3的新特性(Contract契約與協(xié)程篇)
- JetBrains開(kāi)發(fā)者日見(jiàn)聞(一)之Kotlin/Native 嘗鮮篇
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(實(shí)踐篇)
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(下篇)
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(上篇)
- Kotlin的獨(dú)門(mén)秘籍Reified實(shí)化類(lèi)型參數(shù)(下篇)
- 有關(guān)Kotlin屬性代理你需要知道的一切
- 淺談Kotlin中的Sequences源碼解析
- 淺談Kotlin中集合和函數(shù)式API完全解析-上篇
- 淺談Kotlin語(yǔ)法篇之lambda編譯成字節(jié)碼過(guò)程完全解析
- 淺談Kotlin語(yǔ)法篇之Lambda表達(dá)式完全解析
- 淺談Kotlin語(yǔ)法篇之?dāng)U展函數(shù)
- 淺談Kotlin語(yǔ)法篇之頂層函數(shù)、中綴調(diào)用、解構(gòu)聲明
- 淺談Kotlin語(yǔ)法篇之如何讓函數(shù)更好地調(diào)用
- 淺談Kotlin語(yǔ)法篇之變量和常量
- 淺談Kotlin語(yǔ)法篇之基礎(chǔ)語(yǔ)法
翻譯系列:
- [譯]Kotlin中內(nèi)聯(lián)類(lèi)(inline class)完全解析(一)
- [譯]Kotlin的獨(dú)門(mén)秘籍Reified實(shí)化類(lèi)型參數(shù)(上篇)
- [譯]Kotlin泛型中何時(shí)該用類(lèi)型形參約束?
- [譯] 一個(gè)簡(jiǎn)單方式教你記住Kotlin的形參和實(shí)參
- [譯]Kotlin中是應(yīng)該定義函數(shù)還是定義屬性?
- [譯]如何在你的Kotlin代碼中移除所有的!!(非空斷言)
- [譯]掌握Kotlin中的標(biāo)準(zhǔn)庫(kù)函數(shù): run、with、let、also和apply
- [譯]有關(guān)Kotlin類(lèi)型別名(typealias)你需要知道的一切
- [譯]Kotlin中是應(yīng)該使用序列(Sequences)還是集合(Lists)?
- [譯]Kotlin中的龜(List)兔(Sequence)賽跑
- [譯]Effective Kotlin系列之考慮使用靜態(tài)工廠方法替代構(gòu)造器
- [譯]Effective Kotlin系列之遇到多個(gè)構(gòu)造器參數(shù)要考慮使用構(gòu)建器
實(shí)戰(zhàn)系列:
- 用Kotlin擼一個(gè)圖片壓縮插件ImageSlimming-導(dǎo)學(xué)篇(一)
- 用Kotlin擼一個(gè)圖片壓縮插件-插件基礎(chǔ)篇(二)
- 用Kotlin擼一個(gè)圖片壓縮插件-實(shí)戰(zhàn)篇(三)
- 淺談Kotlin實(shí)戰(zhàn)篇之自定義View圖片圓角簡(jiǎn)單應(yīng)用

歡迎關(guān)注Kotlin開(kāi)發(fā)者聯(lián)盟,這里有最新Kotlin技術(shù)文章,每周會(huì)不定期翻譯一篇Kotlin國(guó)外技術(shù)文章。如果你也喜歡Kotlin,歡迎加入我們~~~