Kotlin 程式碼產生指南
proto2 和 proto3 產生的程式碼之間的任何差異都會被突顯 — 請注意,這些差異在於本文檔中描述的產生的程式碼,而不是基礎訊息類別/介面,它們在兩個版本中是相同的。在閱讀本文檔之前,您應該先閱讀 proto2 語言指南 和/或 proto3 語言指南。
編譯器調用
protocol buffer 編譯器產生建立在 Java 程式碼之上的 Kotlin 程式碼。因此,它必須使用兩個命令列旗標 --java_out=
和 --kotlin_out=
來調用。--java_out=
選項的參數是您希望編譯器寫入 Java 輸出的目錄,--kotlin_out=
也是如此。對於每個 .proto
檔案輸入,編譯器都會建立一個包裝 .java
檔案,其中包含一個代表 .proto
檔案本身的 Java 類別。
無論您的 .proto
檔案是否包含如下行
option java_multiple_files = true;
編譯器都會為每個類別和工廠方法建立單獨的 .kt
檔案,這些類別和工廠方法將為 .proto
檔案中宣告的每個頂層訊息產生。
每個檔案的 Java 套件名稱與 Java 產生的程式碼參考 中描述的產生的 Java 程式碼使用的名稱相同。
輸出檔案是透過串連 --kotlin_out=
的參數、套件名稱(句點 [.] 替換為斜線 [/])和後綴 Kt.kt
檔案名稱來選擇的。
因此,舉例來說,假設您如下調用編譯器
protoc --proto_path=src --java_out=build/gen/java --kotlin_out=build/gen/kotlin src/foo.proto
如果 foo.proto
的 Java 套件是 com.example
並且它包含一個名為 Bar
的訊息,則 protocol buffer 編譯器將產生檔案 build/gen/kotlin/com/example/BarKt.kt
。如果需要,protocol buffer 編譯器將自動建立 build/gen/kotlin/com
和 build/gen/kotlin/com/example
目錄。但是,它不會建立 build/gen/kotlin
、build/gen
或 build
;它們必須已經存在。您可以在單次調用中指定多個 .proto
檔案;所有輸出檔案將一次產生。
訊息
給定一個簡單的訊息宣告
message FooBar {}
除了產生的 Java 程式碼之外,protocol buffer 編譯器還產生一個名為 FooBarKt
的物件,以及兩個具有以下結構的頂層函數
object FooBarKt {
class Dsl private constructor { ... }
}
inline fun fooBar(block: FooBarKt.Dsl.() -> Unit): FooBar
inline fun FooBar.copy(block: FooBarKt.Dsl.() -> Unit): FooBar
巢狀類型
訊息可以在另一個訊息內宣告。例如
message Foo {
message Bar { }
}
在這種情況下,編譯器將 BarKt
物件和 bar
工廠方法巢狀於 FooKt
內,儘管 copy
方法仍然是頂層的
object FooKt {
class Dsl { ... }
object BarKt {
class Dsl private constructor { ... }
}
inline fun bar(block: FooKt.BarKt.Dsl.() -> Unit): Foo.Bar
}
inline fun foo(block: FooKt.Dsl.() -> Unit): Foo
inline fun Foo.copy(block: FooKt.Dsl.() -> Unit): Foo
inline fun Foo.Bar.copy(block: FooKt.BarKt.Dsl.() -> Unit): Foo.Bar
欄位
除了上一節中描述的方法之外,protocol buffer 編譯器還在 DSL 中為 .proto
檔案中訊息內定義的每個欄位產生可變屬性。(Kotlin 已經從 Java 產生的 getter 推斷訊息物件上的唯讀屬性。)
請注意,屬性始終使用駝峰式命名,即使 .proto
檔案中的欄位名稱使用帶底線的小寫字母 (應該如此)。大小寫轉換的工作方式如下
- 對於名稱中的每個底線,底線都會被移除,並且後面的字母會大寫。
- 如果名稱將附加前綴(例如,「clear」),則第一個字母會大寫。否則,它會小寫。
因此,欄位 foo_bar_baz
變成 fooBarBaz
。
在少數特殊情況下,欄位名稱與 Kotlin 中的保留字或 protobuf 庫中已定義的方法衝突,則會附加一個額外的底線。例如,名為 in
的欄位的 clearer 是 clearIn_()
。
單數欄位 (proto2)
對於任何這些欄位定義
optional int32 foo = 1;
required int32 foo = 1;
編譯器將在 DSL 中產生以下存取器
fun hasFoo(): Boolean
:如果欄位已設定,則傳回true
。var foo: Int
:欄位的目前值。如果欄位未設定,則傳回預設值。fun clearFoo()
:清除欄位的值。呼叫此方法後,hasFoo()
將傳回false
,而getFoo()
將傳回預設值。
對於其他簡單欄位類型,會根據純量值類型表選擇對應的 Java 類型。對於訊息和列舉類型,值類型會替換為訊息或列舉類別。由於訊息類型仍然在 Java 中定義,因此訊息中的無符號類型會使用 DSL 中標準的對應有符號類型來表示,以便與 Java 和舊版本的 Kotlin 相容。
嵌入訊息欄位
請注意,沒有對子訊息進行特殊處理。例如,如果您有一個欄位
optional Foo my_foo = 1;
您必須寫入
myFoo = foo {
...
}
一般來說,這是因為編譯器不知道 Foo
是否完全具有 Kotlin DSL,還是例如僅具有產生的 Java API。這表示您不必等待您依賴的訊息新增 Kotlin 程式碼產生。
單數欄位 (proto3)
對於此欄位定義
int32 foo = 1;
編譯器將在 DSL 中產生以下屬性
var foo: Int
:傳回欄位的目前值。如果欄位未設定,則傳回欄位類型的預設值。fun clearFoo()
:清除欄位的值。呼叫此方法後,getFoo()
將傳回欄位類型的預設值。
對於其他簡單欄位類型,會根據純量值類型表選擇對應的 Java 類型。對於訊息和列舉類型,值類型會替換為訊息或列舉類別。由於訊息類型仍然在 Java 中定義,因此訊息中的無符號類型會使用 DSL 中標準的對應有符號類型來表示,以便與 Java 和舊版本的 Kotlin 相容。
嵌入訊息欄位
對於訊息欄位類型,會在 DSL 中產生額外的存取器方法
boolean hasFoo()
:如果欄位已設定,則傳回true
。
請注意,沒有基於 DSL 設定子訊息的捷徑。例如,如果您有一個欄位
Foo my_foo = 1;
您必須寫入
myFoo = foo {
...
}
一般來說,這是因為編譯器不知道 Foo
是否完全具有 Kotlin DSL,還是例如僅具有產生的 Java API。這表示您不必等待您依賴的訊息新增 Kotlin 程式碼產生。
重複欄位
對於此欄位定義
repeated string foo = 1;
編譯器將在 DSL 中產生以下成員
class FooProxy: DslProxy
,一個僅在泛型中使用的不可建構類型val fooList: DslList<String, FooProxy>
,重複欄位中目前元素清單的唯讀檢視fun DslList<String, FooProxy>.add(value: String)
,允許將元素新增至重複欄位的擴充函數operator fun DslList<String, FooProxy>.plusAssign(value: String)
,add
的別名fun DslList<String, FooProxy>.addAll(values: Iterable<String>)
,允許將元素的Iterable
新增至重複欄位的擴充函數operator fun DslList<String, FooProxy>.plusAssign(values: Iterable<String>)
,addAll
的別名operator fun DslList<String, FooProxy>.set(index: Int, value: String)
,設定給定從零開始的索引處元素值的擴充函數fun DslList<String, FooProxy>.clear()
,清除重複欄位內容的擴充函數
這種不尋常的建構允許 fooList
在 DSL 的範圍內「表現得像」一個可變清單,僅支援底層建構器支援的方法,同時防止可變性「逸出」DSL,這可能會導致混淆的副作用。
對於其他簡單欄位類型,會根據純量值類型表選擇對應的 Java 類型。對於訊息和列舉類型,類型是訊息或列舉類別。
Oneof 欄位
對於此 oneof 欄位定義
oneof oneof_name {
int32 foo = 1;
...
}
編譯器將在 DSL 中產生以下存取器方法
val oneofNameCase: OneofNameCase
:取得已設定oneof_name
欄位中的哪一個(如果有);請參閱 Java 程式碼參考 以取得傳回類型fun hasFoo(): Boolean
(僅限 proto2):如果 oneof case 為FOO
,則傳回true
。val foo: Int
:如果 oneof case 為FOO
,則傳回oneof_name
的目前值。否則,傳回此欄位的預設值。
對於其他簡單欄位類型,會根據純量值類型表選擇對應的 Java 類型。對於訊息和列舉類型,值類型會替換為訊息或列舉類別。
Map 欄位
對於此 map 欄位定義
map<int32, int32> weight = 1;
編譯器將在 DSL 類別中產生以下成員
class WeightProxy private constructor(): DslProxy()
,一個僅在泛型中使用的不可建構類型val weight: DslMap<Int, Int, WeightProxy>
,map 欄位中目前條目的唯讀檢視fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
:將條目新增至此 map 欄位operator fun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
:put
的別名,使用運算子語法fun DslMap<Int, Int, WeightProxy>.remove(key: Int)
:移除與key
關聯的條目(如果存在)fun DslMap<Int, Int, WeightProxy>.putAll(map: Map<Int, Int>)
:將指定 map 中的所有條目新增至此 map 欄位,覆寫已存在鍵的先前值fun DslMap<Int, Int, WeightProxy>.clear()
:清除此 map 欄位中的所有條目
擴充 (僅限 proto2)
給定具有擴充範圍的訊息
message Foo {
extensions 100 to 199;
}
protocol buffer 編譯器將以下方法新增至 FooKt.Dsl
operator fun <T> get(extension: ExtensionLite<Foo, T>): T
:取得 DSL 中擴充欄位的目前值operator fun <T> get(extension: ExtensionLite<Foo, List<T>>): ExtensionList<T, Foo>
:以唯讀List
形式取得 DSL 中重複擴充欄位的目前值operator fun <T : Comparable<T>> set(extension: ExtensionLite<Foo, T>)
:設定 DSL 中擴充欄位的目前值(適用於Comparable
欄位類型)operator fun <T : MessageLite> set(extension: ExtensionLite<Foo, T>)
:設定 DSL 中擴充欄位的目前值(適用於訊息欄位類型)operator fun set(extension: ExtensionLite<Foo, ByteString>)
:設定 DSL 中擴充欄位的目前值(適用於bytes
欄位)operator fun contains(extension: ExtensionLite<Foo, *>): Boolean
:如果擴充欄位具有值,則傳回 truefun clear(extension: ExtensionLite<Foo, *>)
:清除擴充欄位fun <E> ExtensionList<Foo, E>.add(value: E)
:將值新增至重複擴充欄位operator fun <E> ExtensionList<Foo, E>.plusAssign(value: E)
:add
的別名,使用運算子語法operator fun <E> ExtensionList<Foo, E>.addAll(values: Iterable<E>)
:將多個值新增至重複擴充欄位operator fun <E> ExtensionList<Foo, E>.plusAssign(values: Iterable<E>)
:addAll
的別名,使用運算子語法operator fun <E> ExtensionList<Foo, E>.set(index: Int, value: E)
:設定指定索引處重複擴充欄位的元素inline fun ExtensionList<Foo, *>.clear()
:清除重複擴充欄位的元素
此處的泛型很複雜,但效果是 this[extension] = value
適用於除了重複擴充以外的每個擴充類型,而重複擴充具有「自然」清單語法,其運作方式與 非擴充重複欄位 類似。
給定擴充定義
extend Foo {
optional int32 bar = 123;
}
Java 產生「擴充識別碼」bar
,用於為上述「鍵控」擴充操作。