[譯]記一次Kotlin官方文件翻譯的PR(內聯類)

極客熊貓發表於2018-12-21

簡述: 這幾天突然沒更新文章了,可能有的小夥伴認為寒冬將至,是不是認為我跑路了(哈哈,確實不是哈,這幾天感冒挺厲害的,再加上前幾天連續熬夜寫文章,感覺快扛不住了,所以暫時休息停更了一週。這不這篇內聯類官網文件的翻譯,已經拖了很多天,今天總算給中文社群的大佬提了PR)。有些小夥伴在公眾號上留言問我協程文章什麼時候出。這幾天正在寫協程相關的文章了,很快就可以釋出了哈。

翻譯說明:

原標題: inline-class

原文地址: Kotlin官網

譯文地址: Kotlin中文站-內聯類

內聯類

內聯類僅在 Kotlin 1.3 之後版本可用,目前還是實驗性的。關於詳情請參見下文

有時候,業務邏輯需要圍繞某種型別建立包裝器。然而,由於額外的堆記憶體分配問題,它會引入執行時的效能開銷。此外,如果被包裝的型別是原生型別,效能的損失是很糟糕的,因為原生型別通常在執行時就進行了大量優化,然而他們的包裝器卻沒有得到任何特殊的處理。

為了解決這類問題,Kotlin引入了一種被稱為 內聯類 的特殊類,它通過在類的前面定義一個 inline 修飾符來宣告:

inline class Password(val value: String)
複製程式碼

內聯類必須含有唯一的一個屬性在主建構函式中初始化。在執行時,將使用這個唯一屬性來表示內聯類的例項 (關於執行時的內部表達請參閱 下文):

// 不存在 'Password' 類的真實例項物件
// 在執行時,'securePassword' 僅僅包含 'String'
val securePassword = Password("Don't try this in production") 
複製程式碼

這就是內聯類的主要特性,它靈感來源於 “inline” 這個名稱:類的資料被 “內聯”到該類使用的地方(類似於 行內函數 中的程式碼被內聯到該函式呼叫的地方)。

成員

內聯類支援普通類中的一些功能。特別是,內聯類可以宣告屬性與函式:

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 程式碼塊
  • 內聯類不能含有 inner
  • 內聯類不能含有幕後欄位
    • 因此,內聯類只能含有簡單的計算屬性(不能含有延遲初始化/委託屬性)

繼承

內聯類允許去繼承介面

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

表示方式

在生成的程式碼中,Kotlin編譯器為每個內聯類保留一個包裝器。內聯類的例項可以在執行時表示為包裝器或者基礎型別。這就類似於 Int 可以表示為原生型別 int 或者包裝器 Integer

為了生成效能最優的程式碼,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)  
}
複製程式碼

因為內聯類既可以表示為基礎型別有可以表示為包裝器,引用相等對於內聯類而言毫無意義,因此這也是被禁止的。

名字修飾

由於內聯類被編譯為其基礎型別,因此可能會導致各種模糊的錯誤,例如意想不到的平臺簽名衝突:

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中不能呼叫使用內聯類作為形參的函式。

內聯類與型別別名

初看起來,內聯類似乎與型別別名非常相似。實際上,兩者似乎都引入了一種新的型別,並且都在執行時表示為基礎型別。

然而,關鍵的區別在於型別別名與其基礎型別(以及具有相同基礎型別的其他型別別名)是 賦值相容 的,而內聯類卻不是這樣。

換句話說,內聯類引入了一個真實的新型別,與型別別名正好相反,型別別名僅僅是為現有的型別取了個新的替代名稱(別名):

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("") // 正確: 傳遞基礎型別的實參替代函式中別名型別的形參
    acceptNameInlineClass("") // 錯誤: 不能傳遞基礎型別的實參替代函式中內聯類型別的形參
}
複製程式碼

內聯類的實驗性狀態

內聯類的設計目前是實驗性的,這就是說此特性是正在 快速變化的,並且不保證其相容性。在 Kotlin 1.3+ 中使用內聯類時,將會得到一個警告,來表明此特性還是實驗性的。

要想移除警告,你必須通過對 kotlinc 指定 -XXLanguage:+InlineClasses引數來選擇使用該實驗性的特性。

在Gradle中啟用內聯類:


compileKotlin {
    kotlinOptions.freeCompilerArgs += ["-XXLanguage:+InlineClasses"]
}
複製程式碼

關於詳細資訊,請參見編譯器選項。關於多平臺專案的設定,請參見使用Gradle構建多平臺專案章節。

在Maven中啟用內聯類

<configuration>
    <args>
        <arg>-XXLanguage:+InlineClasses</arg> 
    </args>
</configuration>
複製程式碼

關於詳細資訊,請參見指定編譯器選項

進一步討論

關於其他技術詳細資訊和討論,請參見內聯類的語言提議

譯者有話說

為什麼要翻譯官方文件,其實只想向大家傳遞一個資訊: Kotlin的官方文件確實不錯,也許學會看官方文件才能讓你走得更快和更遠。我們都知道學習一個新的技術,最好的方式就是看官方文件。 為什麼說Kotlin官方文件不錯,以此次內聯類舉例,是不是還記得之前我翻譯幾篇國外大佬有關kotlin內聯類的文章,可以說把內聯類研究得很全面了,比如內聯類自動裝箱和效能優化,內聯類與typealias型別別名的區別等等,可以看下其實這些官網都有提到。所以國外大佬無非也是在官網基礎上對某個問題進行更深入挖掘和探索。所以多看技術官方文件,能讓你對某項技術瞭解的更加全面。

問題來了,可能很多人都厭倦看英文了,所以這次給大家安利一波Kotlin官方認準的Kotlin中文站、Kotlin中文部落格、Kotlin中文社群

Kotlin中文站

Kotlin中文部落格

Kotlin中文社群

非常歡迎熱愛Kotlin的小夥伴們加入到Kotlin中文社群,如果你想為Kotlin中文站翻譯文件提PR的話。你可以直接在中文站翻譯github地址,找到灰藍天際大佬即可。當然你可以直接到我的下面公眾號(Kotlin開發者聯盟)留言,給你引薦一波,哈哈。 如果你覺得翻譯不錯,歡迎給個star。 私人GitHub,一般人不告訴他

[譯]記一次Kotlin官方文件翻譯的PR(內聯類)

歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

Kotlin系列文章,歡迎檢視:

Effective Kotlin翻譯系列

原創系列:

翻譯系列:

實戰系列:

相關文章