Java 生成コードガイド
proto2、proto3、および Editions で生成されるコードの違いは強調表示されています。これらの違いは、このドキュメントで説明されている生成コードにあり、すべてのバージョンで同じであるベースメッセージクラス/インターフェースにはないことに注意してください。このドキュメントを読む前に、proto2 言語ガイド、proto3 言語ガイド、および/または Editions 言語ガイドを読む必要があります。
特に指定がない限り、Java Protocol Buffer のメソッドは null を受け入れたり返したりしないことに注意してください。
コンパイラの呼び出し
Protocol Buffer コンパイラは、--java_out=
コマンドラインフラグで呼び出されたときに Java 出力を生成します。--java_out=
オプションのパラメータは、コンパイラに Java 出力を書き込ませたいディレクトリです。各 .proto
ファイル入力に対して、コンパイラは .proto
ファイル自体を表す Java クラスを含むラッパー .java
ファイルを作成します。
.proto
ファイルに次のような行が含まれている場合、
option java_multiple_files = true;
コンパイラは、.proto
ファイルで宣言された各トップレベルのメッセージ、列挙型、およびサービスに対して生成するクラス/列挙型ごとに、個別の .java
ファイルも作成します。
そうでない場合 (デフォルトである java_multiple_files
オプションが無効になっている場合)、前述のラッパークラスは外部クラスとしても使用され、.proto
ファイルで宣言された各トップレベルのメッセージ、列挙型、およびサービスに対して生成されたクラス/列挙型はすべて、外部ラッパークラス内にネストされます。したがって、コンパイラは .proto
ファイル全体に対して単一の .java
ファイルのみを生成し、パッケージに余分なレイヤーが追加されます。
ラッパークラスの名前は次のように選択されます。.proto
ファイルに次のような行が含まれている場合、
option java_outer_classname = "Foo";
ラッパークラス名は Foo
になります。そうでない場合、ラッパークラス名は .proto
ファイルのベース名をキャメルケースに変換することによって決定されます。たとえば、foo_bar.proto
は FooBar
というクラス名を生成します。ファイル内に同じ名前のサービス、列挙型、またはメッセージ(ネストされた型を含む)がある場合、ラッパークラス名の末尾に「OuterClass」が追加されます。例:
foo_bar.proto
にFooBar
という名前のメッセージが含まれている場合、ラッパークラスはFooBarOuterClass
というクラス名を生成します。foo_bar.proto
にFooService
という名前のサービスが含まれており、java_outer_classname
も文字列FooService
に設定されている場合、ラッパークラスはFooServiceOuterClass
というクラス名を生成します。
注意
非推奨の protobuf API の v1 を使用している場合、メッセージ名との競合に関係なくOuterClass
が追加されます。ネストされたクラスに加えて、ラッパークラス自体は次のAPIを持ちます(ラッパークラスが Foo
という名前で、foo.proto
から生成されたと仮定します)。
public final class Foo {
private Foo() {} // Not instantiable.
/** Returns a FileDescriptor message describing the contents of {@code foo.proto}. */
public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor();
/** Adds all extensions defined in {@code foo.proto} to the given registry. */
public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry);
public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry);
// (Nested classes omitted)
}
Java のパッケージ名は、下記のパッケージで説明されているように選択されます。
出力ファイルは、--java_out=
のパラメータ、パッケージ名(.
を /
に置き換えたもの)、および .java
ファイル名を連結することによって選択されます。
例えば、次のようにコンパイラを呼び出すとします。
protoc --proto_path=src --java_out=build/gen src/foo.proto
foo.proto
の Java パッケージが com.example
であり、java_multiple_files
を有効にせず、その外部クラス名が FooProtos
である場合、Protocol Buffer コンパイラはファイル build/gen/com/example/FooProtos.java
を生成します。Protocol Buffer コンパイラは、必要に応じて build/gen/com
および build/gen/com/example
ディレクトリを自動的に作成します。ただし、build/gen
や build
は作成しません。それらは事前に存在している必要があります。1回の呼び出しで複数の .proto
ファイルを指定できます。すべての出力ファイルが一度に生成されます。
Java コードを出力する際、Protocol Buffer コンパイラの JAR アーカイブへの直接出力機能は特に便利です。なぜなら、多くの Java ツールは JAR ファイルから直接ソースコードを読み取ることができるからです。JAR ファイルに出力するには、単に .jar
で終わる出力場所を指定します。アーカイブには Java ソースコードのみが配置されることに注意してください。Java クラスファイルを生成するには、別途コンパイルする必要があります。
パッケージ
生成されたクラスは、java_package
オプションに基づいて Java パッケージに配置されます。このオプションが省略された場合、代わりに package
宣言が使用されます。
たとえば、.proto
ファイルに以下が含まれている場合、
package foo.bar;
結果として生成される Java クラスは foo.bar
という Java パッケージに配置されます。ただし、.proto
ファイルに java_package
オプションも含まれている場合、例えば次のようになります。
package foo.bar;
option java_package = "com.example.foo.bar";
その場合、クラスは代わりに com.example.foo.bar
パッケージに配置されます。java_package
オプションが提供されているのは、通常の .proto
の package
宣言が逆引きドメイン名で始まるとは想定されていないためです。
メッセージ
新しい Protocol Buffer スキーマを設計している場合は、Java proto 名に関する推奨事項を参照してください。
単純なメッセージ宣言を考えます。
message Foo {}
Protocol Buffer コンパイラは、Message
インターフェースを実装する Foo
というクラスを生成します。このクラスは final
と宣言されており、これ以上のサブクラス化は許可されていません。Foo
は GeneratedMessage
を拡張しますが、これは実装の詳細と考えるべきです。デフォルトでは、Foo
は最高の速度を実現するために、GeneratedMessage
の多くのメソッドを特殊なバージョンでオーバーライドします。ただし、.proto
ファイルに次の行が含まれている場合、
option optimize_for = CODE_SIZE;
Foo
は機能するために必要な最小限のメソッドのみをオーバーライドし、残りは GeneratedMessage
のリフレクションベースの実装に依存します。これにより、生成されるコードのサイズが大幅に削減されますが、パフォーマンスも低下します。あるいは、.proto
ファイルに以下が含まれている場合、
option optimize_for = LITE_RUNTIME;
Foo
にはすべてのメソッドの高速な実装が含まれますが、Message
のメソッドのサブセットを含む MessageLite
インターフェースを実装します。特に、ディスクリプタ、ネストされたビルダー、またはリフレクションをサポートしません。ただし、このモードでは、生成されたコードは libprotobuf.jar
ではなく libprotobuf-lite.jar
にのみリンクする必要があります。「lite」ライブラリは完全なライブラリよりもはるかに小さく、携帯電話などのリソースに制約のあるシステムに適しています。
Message
インターフェースは、メッセージ全体をチェック、操作、読み取り、または書き込みできるメソッドを定義します。これらのメソッドに加えて、Foo
クラスは次の静的メソッドを定義します。
static Foo getDefaultInstance()
:Foo
のシングルトンインスタンスを返します。このインスタンスの内容は、Foo.newBuilder().build()
を呼び出した場合に得られるものと同一です(つまり、すべての単数フィールドは設定されておらず、すべての繰り返しフィールドは空です)。メッセージのデフォルトインスタンスは、そのnewBuilderForType()
メソッドを呼び出すことでファクトリとして使用できることに注意してください。static Descriptor getDescriptor()
: 型のディスクリプタを返します。これには、型に関する情報(どのフィールドを持ち、それらの型が何かなど)が含まれます。これは、getField()
などのMessage
のリフレクションメソッドで使用できます。static Foo parseFrom(...)
: 指定されたソースからFoo
型のメッセージを解析し、それを返します。Message.Builder
インターフェースのmergeFrom()
の各バリアントに対応するparseFrom
メソッドが1つあります。parseFrom()
は決してUninitializedMessageException
をスローしないことに注意してください。解析されたメッセージに必須フィールドが欠けている場合、InvalidProtocolBufferException
をスローします。これにより、Foo.newBuilder().mergeFrom(...).build()
を呼び出すのとは微妙に異なります。static Parser parser()
: 様々なparseFrom()
メソッドを実装するParser
のインスタンスを返します。Foo.Builder newBuilder()
: 新しいビルダー(後述)を作成します。Foo.Builder newBuilder(Foo prototype)
: すべてのフィールドがprototype
内の値と同じ値に初期化された新しいビルダーを作成します。埋め込みメッセージと文字列オブジェクトは不変であるため、元のオブジェクトとコピーの間で共有されます。
ビルダー
メッセージオブジェクト(上記の Foo
クラスのインスタンスなど)は、Java の String
と同様に不変です。メッセージオブジェクトを構築するには、ビルダーを使用する必要があります。各メッセージクラスには独自のビルダークラスがあります。したがって、私たちの Foo
の例では、Protocol Buffer コンパイラは Foo
を構築するために使用できるネストされたクラス Foo.Builder
を生成します。Foo.Builder
は Message.Builder
インターフェースを実装します。これは GeneratedMessage.Builder
クラスを拡張しますが、これも実装の詳細と見なされるべきです。Foo
と同様に、Foo.Builder
は GeneratedMessage.Builder
の汎用メソッド実装に依存する場合もあれば、optimize_for
オプションが使用される場合は、はるかに高速なカスタムコードを生成する場合もあります。Foo.Builder
は、静的メソッド Foo.newBuilder()
を呼び出すことで取得できます。
Foo.Builder
は静的メソッドを定義しません。そのインターフェースは Message.Builder
インターフェースで定義されているものとまったく同じですが、戻り値の型がより具体的である点が異なります。Foo.Builder
のビルダーを変更するメソッドは Foo.Builder
型を返し、build()
は Foo
型を返します。
ビルダーの内容を変更するメソッド(フィールドセッターを含む)は、常にビルダーへの参照を返します(つまり、「return this;
」します)。これにより、複数のメソッド呼び出しを1行に連結できます。例:builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();
ビルダーはスレッドセーフではないため、複数の異なるスレッドが単一のビルダーの内容を変更する必要がある場合は、Java の同期を使用する必要があります。
サブビルダー
サブメッセージを含むメッセージの場合、コンパイラはサブビルダーも生成します。これにより、深くネストされたサブメッセージを再構築することなく繰り返し変更できます。例えば、
message Foo {
int32 val = 1;
// some other fields.
}
message Bar {
Foo foo = 1;
// some other fields.
}
message Baz {
Bar bar = 1;
// some other fields.
}
すでに Baz
メッセージがあり、Foo
の深くネストされた val
を変更したい場合。次の代わりに
baz = baz.toBuilder().setBar(
baz.getBar().toBuilder().setFoo(
baz.getBar().getFoo().toBuilder().setVal(10).build()
).build()).build();
次のように書くことができます。
Baz.Builder builder = baz.toBuilder();
builder.getBarBuilder().getFooBuilder().setVal(10);
baz = builder.build();
ネストされた型
メッセージは別のメッセージ内で宣言できます。例:
message Foo {
message Bar { }
}
この場合、コンパイラは単に Foo
内部にネストされた内部クラスとして Bar
を生成します。
フィールド
前節で説明したメソッドに加えて、Protocol Buffer コンパイラは .proto
ファイルのメッセージ内で定義された各フィールドに対して、一連のアクセサメソッドを生成します。フィールド値を読み取るメソッドはメッセージクラスとその対応するビルダーの両方で定義され、値を変更するメソッドはビルダーのみで定義されます。
メソッド名は、.proto
ファイルのフィールド名がアンダースコア付きの小文字(そうあるべきですが)であっても、常にキャメルケースの命名規則を使用することに注意してください。大文字小文字の変換は次のように機能します。
- 名前の各アンダースコアについて、アンダースコアは削除され、次の文字が大文字になります。
- 名前にプレフィックス(例:「get」)が付く場合、最初の文字は大文字になります。それ以外の場合は小文字になります。
- メソッド名内の各数値の最後の桁に続く文字は大文字になります。
したがって、フィールド foo_bar_baz
は fooBarBaz
になります。get
が接頭辞として付く場合、getFooBarBaz
になります。そして foo_ba23r_baz
は fooBa23RBaz
になります。
アクセサメソッドと同様に、コンパイラは各フィールドに対してそのフィールド番号を含む整数定数を生成します。定数名は、フィールド名を大文字に変換し、末尾に _FIELD_NUMBER
を付けたものです。例えば、フィールド int32 foo_bar = 5;
が与えられた場合、コンパイラは定数 public static final int FOO_BAR_FIELD_NUMBER = 5;
を生成します。
以下のセクションは、明示的なプレゼンスと暗黙的なプレゼンスに分かれています。Proto2 は明示的なプレゼンスを持ち、proto3 はデフォルトで暗黙的なプレゼンスを持ちます。Editions はデフォルトで明示的なプレゼンスを持ちますが、features.field_presence
を使用してこれをオーバーライドできます。
明示的なプレゼンスを持つ単数フィールド
これらのフィールド定義のいずれかについて、
optional int32 foo = 1;
required int32 foo = 1;
コンパイラは、メッセージクラスとそのビルダーの両方で、次のアクセサメソッドを生成します。
boolean hasFoo()
: フィールドが設定されている場合はtrue
を返します。int getFoo()
: フィールドの現在の値を返します。フィールドが設定されていない場合は、デフォルト値を返します。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder setFoo(int value)
: フィールドの値を設定します。これを呼び出した後、hasFoo()
はtrue
を返し、getFoo()
はvalue
を返します。Builder clearFoo()
: フィールドの値をクリアします。これを呼び出した後、hasFoo()
はfalse
を返し、getFoo()
はデフォルト値を返します。
他の単純なフィールド型については、対応する Java 型は スカラー値型テーブル に従って選択されます。メッセージ型と列挙型の場合、値の型はメッセージまたは列挙型のクラスに置き換えられます。
埋め込みメッセージフィールド
メッセージ型の場合、setFoo()
はパラメータとしてメッセージのビルダー型のインスタンスも受け入れます。これは、ビルダーで .build()
を呼び出し、その結果をメソッドに渡すことと等価なショートカットに過ぎません。setFoo
に渡されたサブビルダーをさらに変更しても、メッセージクラスのビルダーには反映**されません**。メッセージクラスのビルダーは、サブメッセージの「所有権を取得」します。
フィールドが設定されていない場合、getFoo()
はどのフィールドも設定されていないFooインスタンス(おそらく Foo.getDefaultInstance()
によって返されるインスタンス)を返します。
さらに、コンパイラはメッセージ型に関連するサブビルダーにアクセスできる2つのアクセサメソッドを生成します。次のメソッドは、メッセージクラスとそのビルダーの両方で生成されます。
FooOrBuilder getFooOrBuilder()
: フィールドのビルダーが既に存在する場合はそれを返し、そうでない場合はメッセージを返します。ビルダーでこのメソッドを呼び出しても、フィールドのサブビルダーは作成されません。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder getFooBuilder()
: フィールドのビルダーを返します。
暗黙的なプレゼンスを持つ単数フィールド
このフィールド定義について:
int32 foo = 1;
コンパイラは、メッセージクラスとそのビルダーの両方で次のアクセサメソッドを生成します。
int getFoo()
: フィールドの現在の値を返します。フィールドが設定されていない場合は、そのフィールドの型のデフォルト値を返します。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder setFoo(int value)
: フィールドの値を設定します。これを呼び出した後、getFoo()
はvalue
を返します。Builder clearFoo()
: フィールドの値をクリアします。これを呼び出した後、getFoo()
はフィールドの型のデフォルト値を返します。
他の単純なフィールド型については、対応する Java 型は スカラー値型テーブル に従って選択されます。メッセージ型と列挙型の場合、値の型はメッセージまたは列挙型のクラスに置き換えられます。
列挙型フィールド
列挙型フィールドの場合、メッセージクラスとそのビルダーの両方に追加のアクセサメソッドが生成されます。
int getFooValue()
: enum の整数値を返します。
コンパイラは、メッセージのビルダーにのみ次の追加メソッドを生成します。
Builder setFooValue(int value)
: enum の整数値を設定します。
さらに、enum の値が不明な場合、getFoo()
は UNRECOGNIZED
を返します。これは、proto3 コンパイラが生成された enum 型 に追加する特別な追加値です。
繰り返しフィールド
このフィールド定義について:
repeated string foos = 1;
コンパイラは、メッセージクラスとそのビルダーの両方で、次のアクセサメソッドを生成します。
int getFoosCount()
: フィールドに現在含まれる要素の数を返します。String getFoos(int index)
: 指定されたゼロベースのインデックスにある要素を返します。ProtocolStringList getFoosList()
: フィールド全体をProtocolStringList
として返します。フィールドが設定されていない場合は、空のリストを返します。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder setFoos(int index, String value)
: 指定されたゼロベースのインデックスにある要素の値を設定します。Builder addFoos(String value)
: 指定された値を持つ新しい要素をフィールドに追加します。Builder addAllFoos(Iterable<? extends String> value)
: 指定されたIterable
内のすべての要素をフィールドに追加します。Builder clearFoos()
: フィールドからすべての要素を削除します。これを呼び出した後、getFoosCount()
はゼロを返します。
他の単純なフィールド型については、対応するJava型はスカラ値型表に従って選択されます。メッセージ型と列挙型の場合、型はメッセージまたは列挙型クラスです。
リピート埋め込みメッセージフィールド
メッセージ型の場合、setFoos()
と addFoos()
は、パラメータとしてメッセージのビルダー型のインスタンスも受け入れます。これは単なるショートカットで、ビルダーで .build()
を呼び出してその結果をメソッドに渡すのと同等です。また、追加で生成されるメソッドもあります。
Builder addFoos(int index, Field value)
: 指定されたゼロベースのインデックスに新しい要素を挿入します。現在その位置にある要素(もしあれば)および後続の要素を右にシフトします(インデックスに1を加えます)。
さらに、コンパイラはメッセージ型に対して、メッセージクラスとそのビルダーの両方で、関連するサブビルダーにアクセスできるようにするための以下の追加アクセサメソッドを生成します。
FooOrBuilder getFoosOrBuilder(int index)
: 指定された要素のビルダーが既に存在する場合はそれを返し、存在しない場合はIndexOutOfBoundsException
をスローします。これをメッセージクラスから呼び出した場合、ビルダーではなく常にメッセージを返すか、例外をスローします。ビルダーでこのメソッドを呼び出しても、フィールドのサブビルダーは作成されません。List<FooOrBuilder> getFoosOrBuilderList()
: フィールド全体を、ビルダー(利用可能な場合)またはメッセージ(利用不可の場合)の変更不可能なリストとして返します。これをメッセージクラスから呼び出した場合、変更不可能なビルダーのリストではなく、常に不変なメッセージのリストを返します。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder getFoosBuilder(int index)
: 指定されたインデックスにある要素のビルダーを返します。インデックスが範囲外の場合はIndexOutOfBoundsException
をスローします。Builder addFoosBuilder(int index)
: 指定されたインデックスに繰り返しメッセージのデフォルトメッセージインスタンスのビルダーを挿入して返します。既存のエントリは、挿入されたビルダーのためのスペースを作るために、より高いインデックスにシフトされます。Builder addFoosBuilder()
: 繰り返しメッセージのデフォルトメッセージインスタンスのビルダーを追加して返します。Builder removeFoos(int index)
: 指定されたゼロベースのインデックスにある要素を削除します。List<Builder> getFoosBuilderList()
: フィールド全体をビルダーの不変リストとして返します。
Repeated Enum Fields (proto3 のみ)
コンパイラは、メッセージクラスとそのビルダーの両方に次の追加メソッドを生成します。
int getFoosValue(int index)
: 指定されたインデックスにある enum の整数値を返します。List<java.lang.Integer> getFoosValueList()
: フィールド全体を整数のリストとして返します。
コンパイラは、メッセージのビルダーにのみ次の追加メソッドを生成します。
Builder setFoosValue(int index, int value)
: 指定されたインデックスにある enum の整数値を設定します。
名前の競合
他の繰り返しでないフィールドが、繰り返しフィールドの生成されたメソッドのいずれかと名前が競合する場合、両方のフィールド名の末尾に protobuf フィールド番号が追加されます。
これらのフィールド定義については、
int32 foos_count = 1;
repeated string foos = 2;
コンパイラはまずそれらを次のように名前変更します。
int32 foos_count_1 = 1;
repeated string foos_2 = 2;
アクセサメソッドは、その後上記のように生成されます。
Oneof フィールド
この oneof フィールド定義について、
oneof choice {
int32 foo_int = 4;
string foo_string = 9;
...
}
choice
oneof 内のすべてのフィールドは、その値に単一のプライベートフィールドを使用します。さらに、Protocol Buffer コンパイラは、oneof case 用に次のような Java enum 型を生成します。
public enum ChoiceCase
implements com.google.protobuf.Internal.EnumLite {
FOO_INT(4),
FOO_STRING(9),
...
CHOICE_NOT_SET(0);
...
};
このenum型の値には、以下の特別なメソッドがあります。
int getNumber()
: .protoファイルで定義されているオブジェクトの数値を返します。static ChoiceCase forNumber(int value)
: 指定された数値に対応する enum オブジェクトを返します(他の数値の場合はnull
を返します)。
コンパイラは、メッセージクラスとそのビルダーの両方で、次のアクセサメソッドを生成します。
boolean hasFooInt()
: oneof のケースがFOO_INT
の場合はtrue
を返します。int getFooInt()
: oneof のケースがFOO_INT
の場合、foo
の現在の値を返します。そうでない場合は、このフィールドのデフォルト値を返します。ChoiceCase getChoiceCase()
: どのフィールドが設定されているかを示す enum を返します。何も設定されていない場合はCHOICE_NOT_SET
を返します。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder setFooInt(int value)
:Foo
をこの値に設定し、oneof case をFOO_INT
に設定します。これを呼び出した後、hasFooInt()
はtrue
を返し、getFooInt()
はvalue
を返し、getChoiceCase()
はFOO_INT
を返します。Builder clearFooInt()
:- oneof ケースが
FOO_INT
でない場合、何も変更されません。 - oneof case が
FOO_INT
の場合、Foo
を null に、oneof case をCHOICE_NOT_SET
に設定します。これを呼び出した後、hasFooInt()
はfalse
を返し、getFooInt()
はデフォルト値を返し、getChoiceCase()
はCHOICE_NOT_SET
を返します。
- oneof ケースが
Builder.clearChoice()
:choice
の値をリセットし、ビルダーを返します。
他の単純なフィールド型については、対応する Java 型は スカラー値型テーブル に従って選択されます。メッセージ型と列挙型の場合、値の型はメッセージまたは列挙型のクラスに置き換えられます。
マップフィールド
このmapフィールド定義の場合:
map<int32, int32> weight = 1;
コンパイラは、メッセージクラスとそのビルダーの両方で、次のアクセサメソッドを生成します。
Map<Integer, Integer> getWeightMap();
: 変更不可能なMap
を返します。int getWeightOrDefault(int key, int default);
: キーに対応する値を返します。存在しない場合はデフォルト値を返します。int getWeightOrThrow(int key);
: キーに対応する値を返します。存在しない場合は IllegalArgumentException をスローします。boolean containsWeight(int key);
: このフィールドにキーが存在するかどうかを示します。int getWeightCount();
: マップ内の要素数を返します。
コンパイラは、メッセージのビルダーにのみ次のメソッドを生成します。
Builder putWeight(int key, int value);
: このフィールドに重みを追加します。Builder putAllWeight(Map<Integer, Integer> value);
: 指定されたマップのすべてのエントリをこのフィールドに追加します。Builder removeWeight(int key);
: このフィールドから重みを削除します。Builder clearWeight();
: このフィールドからすべての重みを削除します。@Deprecated Map<Integer, Integer> getMutableWeight();
: 変更可能なMap
を返します。このメソッドを複数回呼び出すと、異なるマップインスタンスが返される可能性があることに注意してください。返されたマップ参照は、ビルダーへの後続のメソッド呼び出しによって無効になる可能性があります。
メッセージ値マップフィールド
値としてメッセージ型を持つマップの場合、コンパイラはメッセージのビルダーに追加のメソッドを生成します。
Foo.Builder putFooBuilderIfAbsent(int key);
:key
がマッピングに存在することを保証し、まだ存在しない場合は新しいFoo.Builder
を挿入します。返されたFoo.Builder
への変更は、最終的なメッセージに反映されます。
Any
Any
フィールドがこのように与えられた場合
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
google.protobuf.Any details = 2;
}
生成されたコードでは、details
フィールドのゲッターは com.google.protobuf.Any
のインスタンスを返します。これにより、Any
の値をパックおよびアンパックするための次の特別なメソッドが提供されます。
class Any {
// Packs the given message into an Any using the default type URL
// prefix “type.googleapis.com”.
public static Any pack(Message message);
// Packs the given message into an Any using the given type URL
// prefix.
public static Any pack(Message message,
String typeUrlPrefix);
// Checks whether this Any message’s payload is the given type.
public <T extends Message> boolean is(class<T> clazz);
// Unpacks Any into the given message type. Throws exception if
// the type doesn’t match or parsing the payload has failed.
public <T extends Message> T unpack(class<T> clazz)
throws InvalidProtocolBufferException;
}
列挙型
以下のようなenum定義が与えられた場合
enum Foo {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
Protocol Buffer コンパイラは、同じ値のセットを持つ Foo
という名前の Java enum 型を生成します。proto3 を使用している場合、enum 型に特別な値 UNRECOGNIZED
も追加されます。Editions では、OPEN
enum にも UNRECOGNIZED
値がありますが、CLOSED
enum にはありません。生成された enum 型の値には、次の特別なメソッドがあります。
int getNumber()
:.proto
ファイルで定義されているオブジェクトの数値を返します。EnumValueDescriptor getValueDescriptor()
: 値のディスクリプタを返します。これには、値の名前、番号、型に関する情報が含まれています。EnumDescriptor getDescriptorForType()
: enum 型のディスクリプタを返します。これには、定義された各値に関する情報などが含まれます。
さらに、Foo
enum 型には以下の静的メソッドが含まれています。
static Foo forNumber(int value)
: 与えられた数値に対応する enum オブジェクトを返します。対応する enum オブジェクトがない場合は null を返します。static Foo valueOf(int value)
: 与えられた数値に対応する enum オブジェクトを返します。このメソッドはforNumber(int value)
の方が推奨されるため非推奨となり、今後のリリースで削除される予定です。static Foo valueOf(EnumValueDescriptor descriptor)
: 指定された値記述子に対応する enum オブジェクトを返します。valueOf(int)
よりも高速な場合があります。proto3 およびOPEN
enum では、不明な値記述子が渡された場合UNRECOGNIZED
を返します。EnumDescriptor getDescriptor()
: enum 型のディスクリプタを返します。これには、定義された各値に関する情報などが含まれます。(これは静的メソッドであるという点でのみgetDescriptorForType()
と異なります。)
各enum値に対して、接尾辞 _VALUE を持つ整数定数も生成されます。
.proto
言語では、複数の enum シンボルが同じ数値を持つことが許されていることに注意してください。同じ数値を持つシンボルは同義語です。例えば、
enum Foo {
BAR = 0;
BAZ = 0;
}
この場合、BAZ
は BAR
の同義語です。Java では、BAZ
は次のように静的 final フィールドとして定義されます。
static final Foo BAZ = BAR;
したがって、BAR
と BAZ
は等しいと比較され、BAZ
は switch ステートメントに現れるべきではありません。コンパイラは、常に特定の数値で定義された最初のシンボルをそのシンボルの「正規」バージョンとして選択します。同じ番号を持つ後続のすべてのシンボルは、単なるエイリアスです。
列挙型はメッセージ型内にネストして定義できます。コンパイラは、そのメッセージ型のクラス内にネストされた Java 列挙型定義を生成します。
注意: Java コードを生成する際、protobuf enum の値の最大数は驚くほど少ない場合があります—最悪の場合、最大値は 1,700 をわずかに超える程度です。この制限は、Java バイトコードのメソッドごとのサイズ制限によるものであり、Java の実装、protobuf スイートの異なるバージョン、および .proto
ファイルの enum に設定されたオプションによって異なります。
エクステンション
拡張範囲を持つメッセージが与えられた場合
edition = "2023";
message Foo {
extensions 100 to 199;
}
Protocol Buffer コンパイラは、Foo
が通常の GeneratedMessage
ではなく GeneratedMessage.ExtendableMessage
を拡張するようにします。同様に、Foo
のビルダーは GeneratedMessage.ExtendableBuilder
を拡張します。これらの基本型を名前で参照すべきではありません(GeneratedMessage
は実装の詳細と見なされます)。ただし、これらのスーパークラスは、拡張機能を操作するために使用できるいくつかの追加メソッドを定義しています。
特に、Foo
と Foo.Builder
は hasExtension()
、getExtension()
、および getExtensionCount()
メソッドを継承します。さらに、Foo.Builder
は setExtension()
と clearExtension()
メソッドを継承します。これらの各メソッドは、最初のパラメータとして拡張フィールドを識別する拡張識別子(後述)を取ります。残りのパラメータと戻り値は、拡張識別子と同じ型の通常の(非拡張)フィールドに対して生成される対応するアクセサメソッドのものとまったく同じです。
エクステンション定義が与えられた場合:
edition = "2023";
import "foo.proto";
extend Foo {
int32 bar = 123;
}
Protocol Buffer コンパイラは bar
という「拡張識別子」を生成します。これを使用して Foo
の拡張アクセサでこの拡張にアクセスできます。次のようになります。
Foo foo =
Foo.newBuilder()
.setExtension(bar, 1)
.build();
assert foo.hasExtension(bar);
assert foo.getExtension(bar) == 1;
(拡張識別子の正確な実装は複雑で、ジェネリクスの魔法のような使用を伴いますが、それらを使用するために拡張識別子がどのように機能するかを心配する必要はありません。)
bar
は、上記で説明したように、.proto
ファイルのラッパークラスの静的フィールドとして宣言されることに注意してください。この例ではラッパークラス名を省略しています。
拡張機能は、生成されるシンボル名に接頭辞を付けるために、別の型のスコープ内で宣言できます。たとえば、一般的なパターンは、フィールドの型の宣言*内*でメッセージをフィールドによって拡張することです。
edition = "2023";
import "foo.proto";
message Baz {
extend Foo {
Baz foo_ext = 124;
}
}
この場合、識別子 foo_ext
と型 Baz
を持つ拡張が Baz
の宣言内で宣言されており、foo_ext
を参照するには Baz.
プレフィックスを追加する必要があります。
Baz baz = createMyBaz();
Foo foo =
Foo.newBuilder()
.setExtension(Baz.fooExt, baz)
.build();
assert foo.hasExtension(Baz.fooExt);
assert foo.getExtension(Baz.fooExt) == baz;
拡張機能を持つ可能性のあるメッセージを解析する場合、解析したい拡張機能を登録した ExtensionRegistry
を提供する必要があります。そうしないと、それらの拡張機能は未知のフィールドとして扱われ、拡張機能を監視するメソッドは存在しないかのように動作します。
ExtensionRegistry registry = ExtensionRegistry.newInstance();
registry.add(Baz.fooExt);
Foo foo = Foo.parseFrom(input, registry);
assert foo.hasExtension(Baz.fooExt);
ExtensionRegistry registry = ExtensionRegistry.newInstance();
Foo foo = Foo.parseFrom(input, registry);
assert foo.hasExtension(Baz.fooExt) == false;
サービス
.proto
ファイルに次の行が含まれている場合、
option java_generic_services = true;
その後、Protocol Buffer コンパイラは、このセクションで説明されているように、ファイル内で見つかったサービス定義に基づいてコードを生成します。ただし、生成されたコードは特定のRPCシステムに結びついていないため、望ましくない場合があります。そのため、1つのシステムに合わせて調整されたコードよりも多くの間接レベルが必要になります。このコードを生成したくない場合は、ファイルに次の行を追加してください。
option java_generic_services = false;
上記のいずれの行も指定されていない場合、オプションはデフォルトで false
になります。これは、汎用サービスが非推奨であるためです。(2.4.0より前は、オプションのデフォルトは true
であったことに注意してください)
.proto
言語のサービス定義に基づくRPCシステムは、そのシステムに適したコードを生成するためのプラグインを提供すべきです。これらのプラグインは、同じ名前の独自のクラスを生成できるように、抽象サービスが無効にされていることを要求する可能性があります。
このセクションの残りの部分では、抽象サービスが有効になっている場合に Protocol Buffer コンパイラが何を生成するかについて説明します。
インターフェース
サービス定義が与えられた場合
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
Protocol Bufferコンパイラは、このサービスを表すために抽象クラス Foo
を生成します。Foo
は、サービス定義で定義された各メソッドに対して抽象メソッドを持ちます。この場合、メソッド Bar
は次のように定義されます。
abstract void bar(RpcController controller, FooRequest request,
RpcCallback<FooResponse> done);
パラメータは Service.CallMethod()
のパラメータと同等ですが、method
引数は暗黙的に指定され、request
と done
は正確な型を指定します。
Foo
はService
インターフェースをサブクラス化します。Protocol Bufferコンパイラは、Service
のメソッドの実装を次のように自動的に生成します。
getDescriptorForType
: サービスのServiceDescriptor
を返します。callMethod
: 提供されたメソッド記述子に基づいて呼び出されているメソッドを決定し、リクエストメッセージとコールバックを正しい型にダウンキャストして直接呼び出します。getRequestPrototype
とgetResponsePrototype
: 指定されたメソッドに対して、正しい型のリクエストまたはレスポンスのデフォルトインスタンスを返します。
次の静的メソッドも生成されます。
static ServiceDescriptor getServiceDescriptor()
: 型のディスクリプタを返します。これには、このサービスが持つメソッドとその入出力型に関する情報が含まれています。
Foo
には、ネストされたインターフェース Foo.Interface
も含まれます。これは純粋なインターフェースで、サービス定義の各メソッドに対応するメソッドが再び含まれています。ただし、このインターフェースは Service
インターフェースを拡張しません。これは問題です。なぜなら、RPC サーバーの実装は通常、特定のサービスではなく、抽象的な Service
オブジェクトを使用するように書かれているからです。この問題を解決するために、Foo.Interface
を実装するオブジェクト impl
がある場合、Foo.newReflectiveService(impl)
を呼び出して、単に impl
に委譲し、Service
を実装する Foo
のインスタンスを構築できます。
要約すると、独自のサービスを実装する場合、2つの選択肢があります。
Foo
をサブクラス化し、そのメソッドを適切に実装し、サブクラスのインスタンスを RPC サーバー実装に直接渡します。これは通常最も簡単ですが、一部の人はそれを「純粋」ではないと考えています。Foo.Interface
を実装し、Foo.newReflectiveService(Foo.Interface)
を使用してそれをラップするService
を構築し、そのラッパーを RPC 実装に渡します。
スタブ
Protocol Buffer コンパイラは、すべてのサービスインターフェースの「スタブ」実装も生成します。これは、サービスを実装しているサーバーにリクエストを送信したいクライアントによって使用されます。上記の Foo
サービスの場合、スタブ実装 Foo.Stub
はネストされたクラスとして定義されます。
Foo.Stub
は Foo
のサブクラスであり、以下のメソッドも実装しています。
Foo.Stub(RpcChannel channel)
: 指定されたチャネルでリクエストを送信する新しいスタブを構築します。RpcChannel getChannel()
: コンストラクタに渡された、このスタブのチャネルを返します。
スタブはさらに、各サービスメソッドをチャネルのラッパーとして実装します。メソッドのいずれかを呼び出すと、単に channel.callMethod()
が呼び出されます。
Protocol Buffer ライブラリには RPC 実装は含まれていません。ただし、生成されたサービスクラスを任意の RPC 実装に接続するために必要なすべてのツールが含まれています。RpcChannel
と RpcController
の実装を提供するだけで済みます。
ブロッキングインターフェース
上記のRPCクラスはすべて非ブロッキングセマンティクスを持っています。メソッドを呼び出すとき、メソッドが完了したときに呼び出されるコールバックオブジェクトを提供します。多くの場合、ブロッキングセマンティクスを使用してコードを書く方が簡単です(ただし、スケーラビリティは低くなる可能性があります)。ブロッキングセマンティクスでは、メソッドは完了するまで単に戻りません。これに対応するため、Protocol Bufferコンパイラはサービス クラスのブロッキングバージョンも生成します。Foo.BlockingInterface
はFoo.Interface
と同等ですが、各メソッドはコールバックを呼び出すのではなく、単に結果を返す点が異なります。したがって、例えば、bar
は次のように定義されます。
abstract FooResponse bar(RpcController controller, FooRequest request)
throws ServiceException;
非ブロッキングサービスと同様に、Foo.newReflectiveBlockingService(Foo.BlockingInterface)
は、何らかの Foo.BlockingInterface
をラップする BlockingService
を返します。最後に、Foo.BlockingStub
は、特定の BlockingRpcChannel
にリクエストを送信する Foo.BlockingInterface
のスタブ実装を返します。
プラグイン挿入ポイント
コードジェネレータプラグインがJavaコードジェネレータの出力を拡張したい場合、与えられた挿入ポイント名を使用して、以下のタイプのコードを挿入できます。
outer_class_scope
: ファイルのラッパークラスに属するメンバー宣言。class_scope:TYPENAME
: メッセージクラスに属するメンバー宣言。TYPENAME
は完全な proto 名です(例:package.MessageType
)。builder_scope:TYPENAME
: メッセージのビルダー クラスに属するメンバー宣言。TYPENAME
は完全な proto 名です(例:package.MessageType
)。enum_scope:TYPENAME
: enum クラスに属するメンバー宣言。TYPENAME
は完全な proto enum 名です(例:package.EnumType
)。message_implements:TYPENAME
: メッセージクラスのクラス実装宣言。TYPENAME
は完全な proto 名です(例:package.MessageType
)。builder_implements:TYPENAME
: ビルダークラスのクラス実装宣言。TYPENAME
は完全な proto 名です(例:package.MessageType
)。
生成されたコードにはインポート文を含めることはできません。これは、生成されたコード自体の中で定義された型名と競合しやすいためです。代わりに、外部クラスを参照する場合は、常にその完全修飾名を使用する必要があります。
Java コードジェネレータで出力ファイル名を決定するロジックはかなり複雑です。すべてのケースを網羅していることを確認するために、おそらく protoc
のソースコード、特に java_headers.cc
を確認する必要があります。
標準のコードジェネレータによって宣言されたプライベートクラスメンバーに依存するコードは生成しないでください。これらの実装詳細は、Protocol Buffers の将来のバージョンで変更される可能性があります。
ユーティリティクラス
Protocol Buffer は、メッセージの比較、JSON 変換、およびよく知られた型(一般的なユースケース向けに事前定義された Protocol Buffer メッセージ)を扱うためのユーティリティクラスを提供しています。