Kotlin 生成代码指南
突出显示了 proto2 和 proto3 生成代码之间的任何差异——请注意,这些差异在于本文档中描述的生成代码,而不是基本消息类/接口,它们在两个版本中是相同的。在阅读本文档之前,您应该阅读 proto2 语言指南 和/或 proto3 语言指南。
编译器调用
协议缓冲区编译器生成构建在 Java 代码之上的 Kotlin 代码。因此,必须使用两个命令行标志调用它,即 --java_out=
和 --kotlin_out=
。--java_out=
选项的参数是您希望编译器写入 Java 输出的目录,--kotlin_out=
选项也是如此。对于每个 .proto
文件输入,编译器都会创建一个包装器 .java
文件,其中包含表示 .proto
文件本身的 Java 类。
无论您的 .proto
文件是否包含如下行
option java_multiple_files = true;
编译器将为它将为 .proto
文件中声明的每个顶级消息生成的每个类和工厂方法创建单独的 .kt
文件。
每个文件的 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
的消息,则协议缓冲区编译器将生成文件 build/gen/kotlin/com/example/BarKt.kt
。如果需要,协议缓冲区编译器将自动创建 build/gen/kotlin/com
和 build/gen/kotlin/com/example
目录。但是,它不会创建 build/gen/kotlin
、build/gen
或 build
;它们必须已经存在。您可以在单个调用中指定多个 .proto
文件;所有输出文件将一次生成。
消息
给定一个简单的消息声明
message FooBar {}
除了生成的 Java 代码外,协议缓冲区编译器还生成一个名为 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
字段
除了上一节中描述的方法外,协议缓冲区编译器还在 DSL 中为 .proto
文件中消息内定义的每个字段生成可变属性。(Kotlin 已经从 Java 生成的 getter 推断消息对象上的只读属性。)
请注意,属性始终使用驼峰式命名,即使 .proto
文件中的字段名称使用带下划线的小写字母(正如应该的那样)。大小写转换工作原理如下
- 对于名称中的每个下划线,下划线将被删除,并且后面的字母将大写。
- 如果名称将附加前缀(例如,“clear”),则第一个字母大写。否则,它将小写。
因此,字段 foo_bar_baz
变为 fooBarBaz
。
在少数特殊情况下,如果字段名称与 Kotlin 中的保留字或 protobuf 库中已定义的方法冲突,则会附加一个额外的下划线。例如,名为 in
的字段的 clearer 是 clearIn_()
。
Singular 字段 (proto2)
对于任何这些字段定义
optional int32 foo = 1;
required int32 foo = 1;
编译器将在 DSL 中生成以下访问器
fun hasFoo(): Boolean
:如果字段已设置,则返回true
。var foo: Int
:字段的当前值。如果字段未设置,则返回默认值。fun clearFoo()
:清除字段的值。调用此函数后,hasFoo()
将返回false
,getFoo()
将返回默认值。
对于其他简单字段类型,根据 标量值类型表选择相应的 Java 类型。对于消息和枚举类型,值类型将替换为消息或枚举类。由于消息类型仍然在 Java 中定义,因此为了与 Java 和旧版本的 Kotlin 兼容,消息中的无符号类型在 DSL 中使用标准的相应有符号类型表示。
嵌入式消息字段
请注意,没有对子消息进行特殊处理。例如,如果您有一个字段
optional Foo my_foo = 1;
您必须编写
myFoo = foo {
...
}
总的来说,这是因为编译器不知道 Foo
是否有 Kotlin DSL,或者例如仅生成了 Java API。这意味着您不必等待您依赖的消息添加 Kotlin 代码生成。
Singular 字段 (proto3)
对于此字段定义
int32 foo = 1;
编译器将在 DSL 中生成以下属性
var foo: Int
:返回字段的当前值。如果字段未设置,则返回字段类型的默认值。fun clearFoo()
:清除字段的值。调用此函数后,getFoo()
将返回字段类型的默认值。
对于其他简单字段类型,根据 标量值类型表选择相应的 Java 类型。对于消息和枚举类型,值类型将替换为消息或枚举类。由于消息类型仍然在 Java 中定义,因此为了与 Java 和旧版本的 Kotlin 兼容,消息中的无符号类型在 DSL 中使用标准的相应有符号类型表示。
嵌入式消息字段
对于消息字段类型,在 DSL 中生成了一个额外的访问器方法
boolean hasFoo()
:如果字段已设置,则返回true
。
请注意,没有基于 DSL 设置子消息的快捷方式。例如,如果您有一个字段
Foo my_foo = 1;
您必须编写
myFoo = foo {
...
}
总的来说,这是因为编译器不知道 Foo
是否有 Kotlin DSL,或者例如仅生成了 Java API。这意味着您不必等待您依赖的消息添加 Kotlin 代码生成。
Repeated 字段
对于此字段定义
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;
}
协议缓冲区编译器将向 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
,用于“键控”上面的扩展操作。