Kotlin で生成されたコードのガイド
proto2 と proto3 で生成されるコードの相違点はすべて強調表示されています。これらの相違点は、このドキュメントで説明されている生成されたコードにあり、両方のバージョンで同じであるベース メッセージ クラス/インターフェイスにはないことに注意してください。このドキュメントを読む前に、proto2 言語ガイドおよび/またはproto3 言語ガイドを読む必要があります。
コンパイラの起動
プロトコル バッファー コンパイラーは、Java コードの上に構築される Kotlin コードを生成します。その結果、--java_out=
および --kotlin_out=
の 2 つのコマンドライン フラグを指定して呼び出す必要があります。--java_out=
オプションのパラメーターは、コンパイラーが Java 出力を書き込むディレクトリであり、--kotlin_out=
も同様です。各 .proto
ファイル入力に対して、コンパイラーは .proto
ファイル自体を表す Java クラスを含むラッパー .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
という名前のオブジェクトと、次の構造を持つ 2 つのトップレベル関数を生成します。
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
フィールド
前のセクションで説明したメソッドに加えて、プロトコル バッファー コンパイラーは、.proto
ファイル内のメッセージ内で定義された各フィールドの DSL に可変プロパティを生成します。(Kotlin は、Java によって生成されたゲッターからメッセージ オブジェクトの読み取り専用プロパティをすでに推論しています。)
プロパティは、.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 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。メッセージ型は Java で定義されているため、メッセージ内の符号なし型は、Java および古いバージョンの Kotlin との互換性のために、DSL で標準の対応する符号付き型を使用して表されます。
埋め込みメッセージ フィールド
サブメッセージの特別な処理はないことに注意してください。たとえば、フィールドがある場合
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 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。メッセージ型は Java で定義されているため、メッセージ内の符号なし型は、Java および古いバージョンの Kotlin との互換性のために、DSL で標準の対応する符号付き型を使用して表されます。
埋め込みメッセージ フィールド
メッセージ フィールド型の場合、追加のアクセサー メソッドが 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)
: 指定された 0 ベースのインデックスにある要素の値を設定する拡張関数fun DslList<String, FooProxy>.clear()
: 繰り返しフィールドの内容をクリアする拡張関数
この珍しい構造により、fooList
は DSL のスコープ内で「可変リストのように動作」し、基になるビルダーでサポートされているメソッドのみをサポートしますが、DSL から「エスケープ」する可変性を防ぎ、混乱を招く可能性のある副作用を引き起こす可能性があります。
他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、型はメッセージまたは enum クラスです。
Oneof フィールド
この oneof フィールド定義の場合
oneof oneof_name {
int32 foo = 1;
...
}
コンパイラーは、DSL で次のアクセサー メソッドを生成します。
val oneofNameCase: OneofNameCase
:oneof_name
フィールドのどれが設定されているか (または設定されていないか) を取得します。戻り値の型については、Java コード リファレンスを参照してください。fun hasFoo(): Boolean
(proto2 のみ): oneof ケースがFOO
の場合はtrue
を返します。val foo: Int
: oneof ケースがFOO
の場合、oneof_name
の現在の値を返します。それ以外の場合は、このフィールドのデフォルト値を返します。
他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と enum 型の場合、値型はメッセージまたは enum クラスに置き換えられます。
マップ フィールド
このマップ フィールド定義の場合
map<int32, int32> weight = 1;
コンパイラーは、DSL クラスで次のメンバーを生成します。
class WeightProxy private constructor(): DslProxy()
: ジェネリクスでのみ使用される構築不可能な型val weight: DslMap<Int, Int, WeightProxy>
: マップ フィールド内の現在のエントリの読み取り専用ビューfun DslMap<Int, Int, WeightProxy>.put(key: Int, value: Int)
: エントリをこのマップ フィールドに追加します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>)
: 指定されたマップのすべてのエントリをこのマップ フィールドに追加し、既に存在するキーの以前の値を上書きしますfun DslMap<Int, Int, WeightProxy>.clear()
: このマップ フィールドからすべてのエントリをクリアします
拡張機能 (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>
: DSL の繰り返し拡張フィールドの現在の値を読み取り専用のList
として取得します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
: 拡張フィールドに値がある場合は true を返しますfun 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
を生成します。これは、上記の拡張機能操作の「キー」として使用されます。