# 內聯類
[TOC]
> 內聯類僅在 Kotlin 1.3 之后版本可用,目前還是*實驗性的*。關于詳情請參見[下文](http://www.kotlincn.net/docs/reference/inline-classes.html#%E5%86%85%E8%81%94%E7%B1%BB%E7%9A%84%E5%AE%9E%E9%AA%8C%E6%80%A7%E7%8A%B6%E6%80%81)
有時候,業務邏輯需要圍繞某種類型創建包裝器。然而,由于額外的堆內存分配問題,它會引入運行時的性能開銷。此外,如果被包裝的類型是原生類型,性能的損失是很糟糕的,因為原生類型通常在運行時就進行了大量優化,然而他們的包裝器卻沒有得到任何特殊的處理。
為了解決這類問題,Kotlin 引入了一種被稱為 `內聯類` 的特殊類,它通過在類的前面定義一個 `inline` 修飾符來聲明:
```kotlin
inline class Password(val value: String)
```
內聯類必須含有唯一的一個屬性在主構造函數中初始化。在運行時,將使用這個唯一屬性來表示內聯類的實例(關于運行時的內部表達請參閱[下文](#表示方式)):
```kotlin
// 不存在 'Password' 類的真實實例對象
// 在運行時,'securePassword' 僅僅包含 'String'
val securePassword = Password("Don't try this in production")
```
這就是內聯類的主要特性,它靈感來源于 “inline” 這個名稱:類的數據被 “內聯”到該類使用的地方(類似于[內聯函數](http://www.kotlincn.net/docs/reference/inline-functions.html)中的代碼被內聯到該函數調用的地方)。
## 成員
內聯類支持普通類中的一些功能。特別是,內聯類可以聲明屬性與函數:
```kotlin
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // `greet` 方法會作為一個靜態方法被調用
println(name.length) // 屬性的 get 方法會作為一個靜態方法被調用
}
```
然而,內聯類的成員也有一些限制:
* 內聯類不能含有 *init*代碼塊
* 內聯類不能含有[幕后字段](http://www.kotlincn.net/docs/reference/properties.html#%E5%B9%95%E5%90%8E%E5%AD%97%E6%AE%B5)
* 因此,內聯類只能含有簡單的計算屬性(不能含有延遲初始化/委托屬性)
## 繼承
內聯類允許去繼承接口
```kotlin
interface Printable {
fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
}
fun main() {
val name = Name("Kotlin")
println(name.prettyPrint()) // 仍然會作為一個靜態方法被調用
}
```
禁止內聯類參與到類的繼承關系結構中。這就意味著內聯類不能繼承其他的類而且必須是 *final*{: .keyword }。
## 表示方式
在生成的代碼中,Kotlin 編譯器為每個內聯類保留一個包裝器。內聯類的實例可以在運行時表示為包裝器或者基礎類型。這就類似于 `Int` 可以[表示](http://www.kotlincn.net/docs/reference/basic-types.html#%E8%A1%A8%E7%A4%BA%E6%96%B9%E5%BC%8F)為原生類型 `int` 或者包裝器 `Integer`。
為了生成性能最優的代碼,Kotlin 編譯更傾向于使用基礎類型而不是包裝器。 然而,有時候使用包裝器是必要的。一般來說,只要將內聯類用作另一種類型,它們就會被裝箱。
```kotlin
interface I
inline class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun <T> id(x: T): T = x
fun main() {
val f = Foo(42)
asInline(f) // 拆箱操作: 用作 Foo 本身
asGeneric(f) // 裝箱操作: 用作泛型類型 T
asInterface(f) // 裝箱操作: 用作類型 I
asNullable(f) // 裝箱操作: 用作不同于 Foo 的可空類型 Foo?
// 在下面這里例子中,'f' 首先會被裝箱(當它作為參數傳遞給 'id' 函數時)然后又被拆箱(當它從'id'函數中被返回時)
// 最后, 'c' 中就包含了被拆箱后的內部表達(也就是 '42'), 和 'f' 一樣
val c = id(f)
}
```
因為內聯類既可以表示為基礎類型有可以表示為包裝器,[引用相等](equality.html#引用相等)對于內聯類而言毫無意義,因此這也是被禁止的。
### 名字修飾
由于內聯類被編譯為其基礎類型,因此可能會導致各種模糊的錯誤,例如意想不到的平臺簽名沖突:
```kotlin
inline class UInt(val x: Int)
// 在 JVM 平臺上被表示為'public final void compute(int x)'
fun compute(x: Int) { }
// 同理,在 JVM 平臺上也被表示為'public final void compute(int x)'!
fun compute(x: UInt) { }
```
為了緩解這種問題,一般會通過在函數名后面拼接一些穩定的哈希碼來重命名函數。 因此,`fun compute(x: UInt)` 將會被表示為 `public final void compute-<hashcode>(int x)`,以此來解決沖突的問題。
> 請注意在 Java 中 `-` 是一個 *無效的* 符號,也就是說在 Java 中不能調用使用內聯類作為形參的函數。
## 內聯類與類型別名
初看起來,內聯類似乎與[類型別名](http://www.kotlincn.net/docs/reference/type-aliases.html)非常相似。實際上,兩者似乎都引入了一種新的類型,并且都在運行時表示為基礎類型。
然而,關鍵的區別在于類型別名與其基礎類型(以及具有相同基礎類型的其他類型別名)是 *賦值兼容* 的,而內聯類卻不是這樣。
換句話說,內聯類引入了一個真實的新類型,與類型別名正好相反,類型別名僅僅是為現有的類型取了個新的替代名稱(別名):
```kotlin
typealias NameTypeAlias = String
inline class NameInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
val nameAlias: NameTypeAlias = ""
val nameInlineClass: NameInlineClass = NameInlineClass("")
val string: String = ""
acceptString(nameAlias) // 正確: 傳遞別名類型的實參替代函數中基礎類型的形參
acceptString(nameInlineClass) // 錯誤: 不能傳遞內聯類的實參替代函數中基礎類型的形參
// And vice versa:
acceptNameTypeAlias(string) // 正確: 傳遞基礎類型的實參替代函數中別名類型的形參
acceptNameInlineClass(string) // 錯誤: 不能傳遞基礎類型的實參替代函數中內聯類類型的形參
}
```
## 內聯類的實驗性狀態
內聯類的設計目前是實驗性的,這就是說此特性是正在 *快速變化*的,并且不保證其兼容性。在 Kotlin 1.3+ 中使用內聯類時,將會得到一個警告,來表明此特性還是實驗性的。
如需移除警告,必須通過指定編譯器參數 `-Xinline-classes` 來選擇使用這項實驗性的特性。
### 在 Gradle 中啟用內聯類
``` groovy
compileKotlin {
kotlinOptions.freeCompilerArgs += ["Xinline-classes"]
}
```
關于詳細信息,請參見[編譯器選項](http://www.kotlincn.net/docs/reference/using-gradle.html#%E7%BC%96%E8%AF%91%E5%99%A8%E9%80%89%E9%A1%B9)。關于[多平臺項目](http://www.kotlincn.net/docs/reference/whatsnew13.html#%E5%A4%9A%E5%B9%B3%E5%8F%B0%E9%A1%B9%E7%9B%AE)的設置,請參見[使用 Gradle 構建多平臺項目](http://www.kotlincn.net/docs/reference/building-mpp-with-gradle.html#%E8%AF%AD%E8%A8%80%E8%AE%BE%E7%BD%AE)章節。
### 在 Maven 中啟用內聯類
```xml
<configuration>
<args>
<arg>-Xinline-classes</arg>
</args>
</configuration>
```
關于詳細信息,請參見[指定編譯器選項](http://www.kotlincn.net/docs/reference/using-maven.html#%E6%8C%87%E5%AE%9A%E7%BC%96%E8%AF%91%E5%99%A8%E9%80%89%E9%A1%B9)。
## 進一步討論
關于其他技術詳細信息和討論,請參見[內聯類的語言提議](https://github.com/Kotlin/KEEP/blob/master/proposals/inline-classes.md)
- 前言
- Kotlin簡介
- IntelliJ IDEA技巧總結
- idea設置類注釋和方法注釋模板
- 像Android Studion一樣創建工程
- Gradle
- Gradle入門
- Gradle進階
- 使用Gradle創建一個Kotlin工程
- 環境搭建
- Androidstudio平臺搭建
- Eclipse的Kotlin環境配置
- 使用IntelliJ IDEA
- Kotlin學習路線
- Kotlin官方中文版文檔教程
- 概述
- kotlin用于服務器端開發
- kotlin用于Android開發
- kotlin用于JavaScript開發
- kotlin用于原生開發
- Kotlin 用于數據科學
- 協程
- 多平臺
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 開始
- 基本語法
- 習慣用法
- 編碼規范
- 基礎
- 基本類型
- 包與導入
- 控制流
- 返回與跳轉
- 類與對象
- 類與繼承
- 屬性與字段
- 接口
- 可見性修飾符
- 擴展
- 數據類
- 密封類
- 泛型
- 嵌套類
- 枚舉類
- 對象
- 類型別名
- 內嵌類
- 委托
- 委托屬性
- 函數與Lambda表達式
- 函數
- Lambda表達式
- 內聯函數
- 集合
- 集合概述
- 構造集合
- 迭代器
- 區間與數列
- 序列
- 操作概述
- 轉換
- 過濾
- 加減操作符
- 分組
- 取集合的一部分
- 取單個元素
- 排序
- 聚合操作
- 集合寫操作
- List相關操作
- Set相關操作
- Map相關操作
- 多平臺程序設計
- 平臺相關聲明
- 以Gradle創建
- 更多語言結構
- 解構聲明
- 類型檢測與轉換
- This表達式
- 相等性
- 操作符重載
- 空安全
- 異常
- 注解
- 反射
- 作用域函數
- 類型安全的構造器
- Opt-in Requirements
- 核心庫
- 標準庫
- kotlin.test
- 參考
- 關鍵字與操作符
- 語法
- 編碼風格約定
- Java互操作
- Kotlin中調用Java
- Java中調用Kotlin
- JavaScript
- 動態類型
- kotlin中調用JavaScript
- JavaScript中調用kotlin
- JavaScript模塊
- JavaScript反射
- JavaScript DCE
- 原生
- 并發
- 不可變性
- kotlin庫
- 平臺庫
- 與C語言互操作
- 與Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 調試
- FAQ
- 協程
- 協程指南
- 基礎
- 取消與超時
- 組合掛起函數
- 協程上下文與調度器
- 異步流
- 通道
- 異常處理與監督
- 共享的可變狀態與并發
- Select表達式(實驗性)
- 工具
- 編寫kotlin代碼文檔
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin與OSGI
- 編譯器插件
- 編碼規范
- 演進
- kotlin語言演進
- 不同組件的穩定性
- kotlin1.3的兼容性指南
- 常見問題
- FAQ
- 與Java比較
- 與Scala比較(官方已刪除)
- Google開發者官網簡介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin樣品
- Kotlin零基礎到進階
- 第一階段興趣入門
- kotlin簡介和學習方法
- 數據類型和類型系統
- 入門
- 分類
- val和var
- 二進制基礎
- 基礎
- 基本語法
- 包
- 示例
- 編碼規范
- 代碼注釋
- 異常
- 根類型“Any”
- Any? 可空類型
- 可空性的實現原理
- kotlin.Unit類型
- kotlin.Nothing類型
- 基本數據類型
- 數值類型
- 布爾類型
- 字符型
- 位運算符
- 變量和常量
- 語法和運算符
- 關鍵字
- 硬關鍵字
- 軟關鍵字
- 修飾符關鍵字
- 特殊標識符
- 操作符和特殊符號
- 算術運算符
- 賦值運算符
- 比較運算符
- 邏輯運算符
- this關鍵字
- super關鍵字
- 操作符重載
- 一元操作符
- 二元操作符
- 字符串
- 字符串介紹和屬性
- 字符串常見方法操作
- 字符串模板
- 數組
- 數組介紹創建及遍歷
- 數組常見方法和屬性
- 數組變化以及下標越界問題
- 原生數組類型
- 區間
- 正向區間
- 逆向區間
- 步長
- 類型檢測與類型轉換
- is、!is、as、as-運算符
- 空安全
- 可空類型變量
- 安全調用符
- 非空斷言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函數
- 函數式編程概述
- OOP和FOP
- 函數式編程基本特性
- 組合與范疇
- 在Kotlin中使用函數式編程
- 函數入門
- 函數作用域
- 函數加強
- 命名參數
- 默認參數
- 可變參數
- 表達式函數體
- 頂層、嵌套、中綴函數
- 尾遞歸函數優化
- 函數重載
- 控制流
- if表達式
- when表達式
- for循環
- while循環
- 循環中的 Break 與 continue
- return返回
- 標簽處返回
- 集合
- list集合
- list集合介紹和操作
- list常見方法和屬性
- list集合變化和下標越界
- set集合
- set集合介紹和常見操作
- set集合常見方法和屬性
- set集合變換和下標越界
- map集合
- map集合介紹和常見操作
- map集合常見方法和屬性
- map集合變換
- 集合的函數式API
- map函數
- filter函數
- “ all ”“ any ”“ count ”和“ find ”:對集合應用判斷式
- 別樣的求和方式:sumBy、sum、fold、reduce
- 根據人的性別進行分組:groupBy
- 扁平化——處理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 區間、數組、集合之間轉換
- 面向對象
- 面向對象-封裝
- 類的創建及屬性方法訪問
- 類屬性和字段
- 構造器
- 嵌套類(內部類)
- 枚舉類
- 枚舉類遍歷&枚舉常量常用屬性
- 數據類
- 密封類
- 印章類(密封類)
- 面向對象-繼承
- 類的繼承
- 面向對象-多態
- 抽象類
- 接口
- 接口和抽象類的區別
- 面向對象-深入
- 擴展
- 擴展:為別的類添加方法、屬性
- Android中的擴展應用
- 優化Snackbar
- 用擴展函數封裝Utils
- 解決煩人的findViewById
- 擴展不是萬能的
- 調度方式對擴展函數的影響
- 被濫用的擴展函數
- 委托
- 委托類
- 委托屬性
- Kotlin5大內置委托
- Kotlin-Object關鍵字
- 單例模式
- 匿名類對象
- 伴生對象
- 作用域函數
- let函數
- run函數
- with函數
- apply函數
- also函數
- 標準庫函數
- takeIf 與 takeUnless
- 第二階段重點深入
- Lambda編程
- Lambda成員引用高階函數
- 高階函數
- 內聯函數
- 泛型
- 泛型的分類
- 泛型約束
- 子類和子類型
- 協變與逆變
- 泛型擦除與實化類型
- 泛型類型參數
- 泛型的背后:類型擦除
- Java為什么無法聲明一個泛型數組
- 向后兼容的罪
- 類型擦除的矛盾
- 使用內聯函數獲取泛型
- 打破泛型不變
- 一個支持協變的List
- 一個支持逆變的Comparator
- 協變和逆變
- 第三階段難點突破
- 注解和反射
- 聲明并應用注解
- DSL
- 協程
- 協程簡介
- 協程的基本操作
- 協程取消
- 管道
- 慕課霍丙乾協程筆記
- Kotlin與Java互操作
- 在Kotlin中調用Java
- 在Java中調用Kotlin
- Kotlin與Java中的操作對比
- 第四階段專題練習
- 朱凱Kotlin知識點總結
- Kotlin 基礎
- Kotlin 的變量、函數和類型
- Kotlin 里那些「不是那么寫的」
- Kotlin 里那些「更方便的」
- Kotlin 進階
- Kotlin 的泛型
- Kotlin 的高階函數、匿名函數和 Lambda 表達式
- Kotlin協程
- 初識
- 進階
- 深入
- Kotlin 擴展
- 會寫「18.dp」只是個入門——Kotlin 的擴展函數和擴展屬性(Extension Functions / Properties)
- Kotlin實戰-開發Android