Java 生成コードガイド

プロトコルバッファコンパイラが与えられたプロトコル定義に対して生成する Java コードの内容を正確に記述します。

proto2 と proto3 で生成されるコードの違いを強調しています。これらの違いは、このドキュメントで説明されている生成コードにおける違いであり、両方のバージョンで同じであるベースメッセージクラス/インターフェースの違いではないことに注意してください。このドキュメントを読む前に、proto2 言語ガイドおよび/または proto3 言語ガイドを読む必要があります。

特に明記されていない限り、Java プロトコルバッファメソッドは null を受け入れたり、返したりしないことに注意してください。

コンパイラの起動

プロトコルバッファコンパイラは、--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.protoFooBar というクラス名を生成します。ファイル内に同じ名前のサービス、列挙型、またはメッセージ(ネストされた型を含む)がある場合、「OuterClass」がラッパークラスの名前に追加されます。例

  • foo_bar.protoFooBar というメッセージが含まれている場合、ラッパークラスは FooBarOuterClass というクラス名を生成します。
  • foo_bar.protoFooService というサービスが含まれており、java_outer_classname も文字列 FooService に設定されている場合、ラッパークラスは FooServiceOuterClass というクラス名を生成します。

ネストされたクラスに加えて、ラッパークラス自体には次の 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 である場合、プロトコルバッファコンパイラはファイル build/gen/com/example/FooProtos.java を生成します。プロトコルバッファコンパイラは、必要に応じて build/gen/com および build/gen/com/example ディレクトリを自動的に作成します。ただし、build/gen または build は作成しません。これらは既に存在している必要があります。単一の呼び出しで複数の .proto ファイルを指定できます。すべての出力ファイルは一度に生成されます。

Java コードを出力する場合、プロトコルバッファコンパイラが JAR アーカイブに直接出力できる機能は特に便利です。多くの Java ツールが JAR ファイルから直接ソースコードを読み取ることができるためです。JAR ファイルに出力するには、.jar で終わる出力場所を指定するだけです。Java ソースコードのみがアーカイブに配置されることに注意してください。Java クラスファイルを生成するには、別途コンパイルする必要があります。

パッケージ

生成されたクラスは、java_package オプションに基づいて Java パッケージに配置されます。オプションが省略された場合、代わりに package 宣言が使用されます。

たとえば、.proto ファイルに次が含まれている場合

package foo.bar;

結果の Java クラスは、Java パッケージ foo.bar に配置されます。ただし、.proto ファイルに次のような java_package オプションも含まれている場合

package foo.bar;
option java_package = "com.example.foo.bar";

クラスは代わりに com.example.foo.bar パッケージに配置されます。通常の .proto package 宣言は、逆ドメイン名で始まることは想定されていないため、java_package オプションが提供されています。

メッセージ

新しいプロトコルバッファスキーマを設計する場合は、Java proto 名の推奨事項を参照してください。

簡単なメッセージ宣言が与えられた場合

message Foo {}

プロトコルバッファコンパイラは、Message インターフェースを実装する Foo という名前のクラスを生成します。クラスは final として宣言されます。それ以上のサブクラス化は許可されていません。FooGeneratedMessage を拡張しますが、これは実装の詳細と見なされるべきです。デフォルトでは、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 の例では、プロトコルバッファコンパイラは Foo を構築するために使用できるネストされたクラス Foo.Builder を生成します。Foo.BuilderMessage.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 {
  optional int32 val = 1;
  // some other fields.
}

message Bar {
  optional Foo foo = 1;
  // some other fields.
}

message Baz {
  optional 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 { }
}

この場合、コンパイラは BarFoo 内にネストされた内部クラスとして単純に生成します。

フィールド

前のセクションで説明したメソッドに加えて、プロトコルバッファコンパイラは、.proto ファイルのメッセージ内で定義された各フィールドに対して一連のアクセサメソッドを生成します。フィールド値を読み取るメソッドは、メッセージクラスとそれに対応するビルダーの両方で定義されています。値を変更するメソッドはビルダーでのみ定義されています。

メソッド名は、.proto ファイルのフィールド名がアンダースコア付きの小文字を使用している場合でも (推奨されているように)、常にキャメルケースの名前付けを使用することに注意してください。ケース変換は次のように機能します。

  • 名前の各アンダースコアについて、アンダースコアが削除され、後続の文字が大文字になります。
  • 名前がプレフィックス (例: 「get」) を付ける場合、最初の文字は大文字になります。それ以外の場合は、小文字になります。
  • メソッド名内の各数値の最後の数字に続く文字は大文字になります。

したがって、フィールド foo_bar_bazfooBarBaz になります。get がプレフィックスとして付いている場合は、getFooBarBaz になります。また、foo_ba23r_bazfooBa23RBaz になります。

アクセサメソッドと同様に、コンパイラはフィールド番号を含む各フィールドの整数定数を生成します。定数名は、フィールド名を大文字に変換し、その後に _FIELD_NUMBER を続けたものです。たとえば、フィールド optional int32 foo_bar = 5; が与えられた場合、コンパイラは定数 public static final int FOO_BAR_FIELD_NUMBER = 5; を生成します。

単数フィールド (proto2)

これらのフィールド定義のいずれかの場合

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() を呼び出し、結果をメソッドに渡すのと同じショートカットです。

フィールドが設定されていない場合、getFoo() は、フィールドが設定されていない Foo インスタンス (Foo.getDefaultInstance() によって返されるインスタンスである可能性があります) を返します。

さらに、コンパイラは、メッセージ型に関連するサブビルダーにアクセスできるようにする 2 つのアクセサメソッドを生成します。次のメソッドは、メッセージクラスとビルダーの両方で生成されます。

  • FooOrBuilder getFooOrBuilder(): フィールドのビルダーが既に存在する場合はビルダーを返し、存在しない場合はメッセージを返します。ビルダーでこのメソッドを呼び出しても、フィールドのサブビルダーは作成されません。

コンパイラは、メッセージのビルダーでのみ次のメソッドを生成します。

  • Builder getFooBuilder(): フィールドのビルダーを返します。

単数フィールド (proto3)

このフィールド定義の場合

int32 foo = 1;

コンパイラは、メッセージクラスとビルダーの両方で次のアクセサメソッドを生成します。

  • int getFoo(): フィールドの現在の値を返します。フィールドが設定されていない場合は、フィールド型のデフォルト値を返します。

コンパイラは、メッセージのビルダーでのみ次のメソッドを生成します。

  • Builder setFoo(int value): フィールドの値を設定します。これを呼び出すと、getFoo()value を返します。
  • Builder clearFoo(): フィールドの値をクリアします。これを呼び出すと、getFoo() はフィールド型のデフォルト値を返します。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と列挙型の場合、値型はメッセージまたは列挙クラスに置き換えられます。

埋め込みメッセージフィールド

メッセージフィールド型の場合、追加のアクセサメソッドがメッセージクラスとビルダーの両方で生成されます。

  • boolean hasFoo(): フィールドが設定されている場合は true を返します。

setFoo() は、パラメータとしてメッセージのビルダー型のインスタンスも受け入れます。これは、ビルダーで .build() を呼び出し、結果をメソッドに渡すのと同じショートカットです。

フィールドが設定されていない場合、getFoo() は、フィールドが設定されていない Foo インスタンス (Foo.getDefaultInstance() によって返されるインスタンスである可能性があります) を返します。

さらに、コンパイラは、メッセージ型に関連するサブビルダーにアクセスできるようにする 2 つのアクセサメソッドを生成します。次のメソッドは、メッセージクラスとビルダーの両方で生成されます。

  • FooOrBuilder getFooOrBuilder(): フィールドのビルダーが既に存在する場合はビルダーを返し、存在しない場合はメッセージを返します。ビルダーでこのメソッドを呼び出しても、フィールドのサブビルダーは作成されません。

コンパイラは、メッセージのビルダーでのみ次のメソッドを生成します。

  • Builder getFooBuilder(): フィールドのビルダーを返します。

Enum フィールド

列挙フィールド型の場合、追加のアクセサメソッドがメッセージクラスとビルダーの両方で生成されます。

  • int getFooValue(): 列挙型の整数値を返します。

コンパイラは、メッセージのビルダーでのみ次の追加メソッドを生成します。

  • Builder setFooValue(int value): 列挙型の整数値を設定します。

さらに、列挙値が不明な場合、getFoo()UNRECOGNIZED を返します。これは、proto3 コンパイラによって生成された 列挙型に追加された特別な追加の値です。

繰り返しフィールド

このフィールド定義の場合

repeated string foos = 1;

コンパイラは、メッセージクラスとビルダーの両方で次のアクセサメソッドを生成します。

  • int getFoosCount(): フィールドに現在存在する要素の数を返します。
  • String getFoos(int index): 指定された 0 ベースのインデックスにある要素を返します。
  • ProtocolStringList getFoosList(): フィールド全体を ProtocolStringList として返します。フィールドが設定されていない場合は、空のリストを返します。

コンパイラは、メッセージのビルダーでのみ次のメソッドを生成します。

  • Builder setFoos(int index, String value): 指定された 0 ベースのインデックスにある要素の値を設定します。
  • Builder addFoos(String value): 指定された値を持つ新しい要素をフィールドに追加します。
  • Builder addAllFoos(Iterable<? extends String> value): 指定された Iterable 内のすべての要素をフィールドに追加します。
  • Builder clearFoos(): フィールドからすべての要素を削除します。これを呼び出すと、getFoosCount() はゼロを返します。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と列挙型の場合、型はメッセージまたは列挙クラスです。

繰り返し埋め込みメッセージフィールド

メッセージ型の場合、setFoos()addFoos() は、パラメータとしてメッセージのビルダー型のインスタンスも受け入れます。これは、ビルダーで .build() を呼び出し、結果をメソッドに渡すのと同じショートカットです。追加の生成されたメソッドもあります

  • Builder addFoos(int index, Field value): 指定された 0 ベースのインデックスに新しい要素を挿入します。その位置 (存在する場合) と後続の要素を右にシフトします (インデックスに 1 を加算します)。

さらに、コンパイラは、メッセージ型について、メッセージクラスとビルダーの両方で次の追加のアクセサメソッドを生成し、関連するサブビルダーにアクセスできるようにします。

  • FooOrBuilder getFoosOrBuilder(int index): 指定された要素のビルダーが既に存在する場合はビルダーを返し、存在しない場合は IndexOutOfBoundsException をスローします。これがメッセージクラスから呼び出された場合、常にビルダーではなくメッセージ (または例外をスロー) を返します。ビルダーでこのメソッドを呼び出しても、フィールドのサブビルダーは作成されません。
  • List<FooOrBuilder> getFoosOrBuilderList(): フィールド全体をビルダーの変更不可能なリスト (使用可能な場合) またはメッセージ (使用できない場合) として返します。これがメッセージクラスから呼び出された場合、常にビルダーの変更不可能なリストではなく、メッセージの不変リストを返します。

コンパイラは、メッセージのビルダーでのみ次のメソッドを生成します。

  • Builder getFoosBuilder(int index): 指定されたインデックスにある要素のビルダーを返すか、インデックスが範囲外の場合は IndexOutOfBoundsException をスローします。
  • Builder addFoosBuilder(int index): 指定されたインデックスにある繰り返しメッセージのデフォルトメッセージインスタンスのビルダーを挿入して返します。既存のエントリは、挿入されたビルダーのためにスペースを作るために、より高いインデックスにシフトされます。
  • Builder addFoosBuilder(): 繰り返しメッセージのデフォルトメッセージインスタンスのビルダーを追加して返します。
  • Builder removeFoos(int index): 指定された 0 ベースのインデックスにある要素を削除します。
  • List<Builder> getFoosBuilderList(): フィールド全体をビルダーの変更不可能なリストとして返します。

繰り返し Enum フィールド (proto3 のみ)

コンパイラは、メッセージクラスとビルダーの両方で次の追加メソッドを生成します。

  • int getFoosValue(int index): 指定されたインデックスにある列挙型の整数値を返します。
  • List<java.lang.Integer> getFoosValueList(): フィールド全体を整数のリストとして返します。

コンパイラは、メッセージのビルダーでのみ次の追加メソッドを生成します。

  • Builder setFoosValue(int index, int value): 指定されたインデックスにある列挙型の整数値を設定します。

名前の衝突

別の非繰り返しフィールドの名前が、繰り返しフィールドの生成されたメソッドの 1 つと競合する場合、両方のフィールド名に 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 のすべてのフィールドは、値に単一のプライベートフィールドを使用します。さらに、プロトコルバッファコンパイラは、oneof ケースの Java 列挙型を次のように生成します。

public enum ChoiceCase
        implements com.google.protobuf.Internal.EnumLite {
      FOO_INT(4),
      FOO_STRING(9),
      ...
      CHOICE_NOT_SET(0);
      ...
    };

この列挙型の値には、次の特殊メソッドがあります。

  • int getNumber(): .proto ファイルで定義されているオブジェクトの数値を返します。
  • static ChoiceCase forNumber(int value): 指定された数値に対応する列挙オブジェクト (または他の数値の場合は null) を返します。

コンパイラは、メッセージクラスとビルダーの両方で次のアクセサメソッドを生成します。

  • boolean hasFooInt(): oneof ケースが FOO_INT の場合は true を返します。
  • int getFooInt(): oneof ケースが FOO_INT の場合は foo の現在の値を返します。それ以外の場合は、このフィールドのデフォルト値を返します。
  • ChoiceCase getChoiceCase(): 設定されているフィールドを示す列挙型を返します。いずれも設定されていない場合は CHOICE_NOT_SET を返します。

コンパイラは、メッセージのビルダーでのみ次のメソッドを生成します。

  • Builder setFooInt(int value): Foo をこの値に設定し、oneof ケースを FOO_INT に設定します。これを呼び出すと、hasFooInt()true を返し、getFooInt()value を返し、getChoiceCase()FOO_INT を返します。
  • Builder clearFooInt():
    • oneof ケースが FOO_INT でない場合、何も変更されません。
    • oneof ケースが FOO_INT の場合、Foo を null に設定し、oneof ケースを CHOICE_NOT_SET に設定します。これを呼び出すと、hasFooInt()false を返し、getFooInt() はデフォルト値を返し、getChoiceCase()CHOICE_NOT_SET を返します。
  • Builder.clearChoice(): choice の値をリセットし、ビルダーを返します。

他の単純なフィールド型の場合、対応する Java 型は、スカラー値型テーブルに従って選択されます。メッセージ型と列挙型の場合、値型はメッセージまたは列挙クラスに置き換えられます。

Map フィールド

この 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);: このフィールドに weight を追加します。
  • Builder putAllWeight(Map<Integer, Integer> value);: 指定されたマップ内のすべてのエントリをこのフィールドに追加します。
  • Builder removeWeight(int key);: このフィールドから weight を削除します。
  • Builder clearWeight();: このフィールドからすべての weight を削除します。
  • @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 フィールドの getter は 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;
}

プロトコルバッファコンパイラは、同じ値のセットを持つ Foo という名前の Java enum 型を生成します。proto3 を使用している場合、enum 型に特別な値 UNRECOGNIZED も追加します。生成された 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 では、不明な値記述子が渡されると UNRECOGNIZED を返します。
  • EnumDescriptor getDescriptor(): enum 型の記述子を返します。これには、定義された各値に関する情報などが含まれています。(これは、静的メソッドであるという点を除いて、getDescriptorForType() とのみ異なります。)

整数定数も、各 enum 値のサフィックス _VALUE で生成されます。

.proto 言語では、複数の enum シンボルが同じ数値を持つことができることに注意してください。同じ数値を持つシンボルは同義語です。例

enum Foo {
  BAR = 0;
  BAZ = 0;
}

この場合、BAZBAR の同義語です。Java では、BAZ は次のような静的 final フィールドとして定義されます。

static final Foo BAZ = BAR;

したがって、BARBAZ は等しく比較され、BAZ は switch ステートメントに決して表示されるべきではありません。コンパイラは常に、特定の数値で定義された最初のシンボルをそのシンボルの「正規」バージョンとして選択します。同じ数値を持つ後続のすべてのシンボルは、単なるエイリアスです。

enum はメッセージ型内にネストして定義できます。コンパイラは、Java enum 定義をそのメッセージ型のクラス内にネストして生成します。

注意: Java コードを生成する場合、protobuf enum の最大値の数は驚くほど少ない場合があります。最悪の場合、最大値はわずか 1,700 をわずかに超える値です。この制限は、Java バイトコードのメソッドごとのサイズ制限によるものであり、Java 実装、protobuf スイートの異なるバージョン、および .proto ファイルの enum に設定されたオプションによって異なります。

拡張機能 (proto2 のみ)

拡張範囲を持つメッセージが与えられた場合

message Foo {
  extensions 100 to 199;
}

プロトコルバッファコンパイラは、通常の GeneratedMessage の代わりに FooGeneratedMessage.ExtendableMessage を拡張するようにします。同様に、Foo のビルダーは GeneratedMessage.ExtendableBuilder を拡張します。これらの基本型を名前で参照することは決してありません (GeneratedMessage は実装の詳細と見なされます)。ただし、これらのスーパークラスは、拡張機能を操作するために使用できる追加のメソッドを多数定義しています。

特に、FooFoo.Builder は、メソッド hasExtension()getExtension()、および getExtensionCount() を継承します。さらに、Foo.Builder は、メソッド setExtension()clearExtension() を継承します。これらの各メソッドは、最初のパラメータとして、拡張フィールドを識別する拡張識別子 (後述) を取ります。残りのパラメータと戻り値は、拡張識別子と同じ型の通常の (非拡張) フィールドに対して生成される対応するアクセサメソッドの場合とまったく同じです。

拡張機能の定義が与えられた場合

extend Foo {
  optional int32 bar = 123;
}

プロトコルバッファコンパイラは「拡張識別子」と呼ばれるbarを生成します。これを使って、Fooの拡張アクセサでこの拡張機能にアクセスできます。例えば、以下のようにします。

Foo foo =
  Foo.newBuilder()
     .setExtension(bar, 1)
     .build();
assert foo.hasExtension(bar);
assert foo.getExtension(bar) == 1;

(拡張識別子の正確な実装は複雑で、ジェネリクスの魔法のような使用を含みます—しかし、拡張識別子がどのように機能するかを心配する必要はありません。)

barは、前述のように、.protoファイルのラッパークラスの静的フィールドとして宣言されることに注意してください。例ではラッパークラス名を省略しています。

拡張機能は、生成されたシンボル名にプレフィックスを付けるために、別の型のスコープ内で宣言できます。たとえば、一般的なパターンは、フィールドの型の宣言内側でフィールドによってメッセージを拡張することです。

message Baz {
  extend Foo {
    optional 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;

次に、プロトコルバッファコンパイラは、このセクションで説明されているように、ファイルで見つかったサービス定義に基づいてコードを生成します。ただし、生成されたコードは、特定のRPCシステムに結び付けられていないため、望ましくない場合があります。そのため、1つのシステムに合わせたコードよりも多くのレベルの間接参照が必要です。このコードを生成したくない場合は、この行をファイルに追加してください

option java_generic_services = false;

上記の行のどちらも指定されていない場合、汎用サービスは非推奨であるため、オプションはデフォルトでfalseになります。(2.4.0より前では、オプションはデフォルトでtrueであったことに注意してください)

.proto言語サービス定義に基づくRPCシステムは、システムに適したコードを生成するためにプラグインを提供する必要があります。これらのプラグインは、抽象サービスを無効にすることを要求する可能性が高く、同じ名前の独自のクラスを生成できるようにします。プラグインはバージョン2.3.0(2010年1月)で新しく追加されました。

このセクションの残りの部分では、抽象サービスが有効になっている場合にプロトコルバッファコンパイラが何を生成するかについて説明します。

インターフェース

サービス定義が与えられた場合

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

プロトコルバッファコンパイラは、このサービスを表す抽象クラスFooを生成します。Fooには、サービス定義で定義された各メソッドの抽象メソッドがあります。この場合、メソッドBarは次のように定義されます。

abstract void bar(RpcController controller, FooRequest request,
                  RpcCallback<FooResponse> done);

パラメータは、method引数が暗黙的であり、requestdoneが正確な型を指定することを除いて、Service.CallMethod()のパラメータと同等です。

FooServiceインターフェースをサブクラス化します。プロトコルバッファコンパイラは、次のようにServiceのメソッドの実装を自動的に生成します。

  • getDescriptorForType:サービスのServiceDescriptorを返します。
  • callMethod:提供されたメソッド記述子に基づいて呼び出されているメソッドを決定し、リクエストメッセージとコールバックを正しい型にダウンキャストして直接呼び出します。
  • getRequestPrototypegetResponsePrototype:指定されたメソッドの正しい型のリクエストまたはレスポンスのデフォルトインスタンスを返します。

次の静的メソッドも生成されます

  • 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実装に渡します。

スタブ

プロトコルバッファコンパイラは、すべてのサービスインターフェースの「スタブ」実装も生成します。これは、サービスを実装するサーバーにリクエストを送信したいクライアントによって使用されます。 Fooサービス(上記)の場合、スタブ実装Foo.Stubはネストされたクラスとして定義されます。

Foo.Stubは、次のメソッドも実装するFooのサブクラスです。

  • Foo.Stub(RpcChannel channel):指定されたチャネルでリクエストを送信する新しいスタブを構築します。
  • RpcChannel getChannel():コンストラクタに渡された、このスタブのチャネルを返します。

スタブはさらに、チャネルのラッパーとしてサービスの各メソッドを実装します。メソッドの1つを呼び出すと、単にchannel.callMethod()を呼び出します。

Protocol BufferライブラリにはRPC実装は含まれていません。ただし、生成されたサービスクラスを任意のRPC実装に接続するために必要なすべてのツールが含まれています。 RpcChannelRpcControllerの実装を提供するだけで済みます。

ブロッキングインターフェース

上記のRPCクラスはすべて非ブロッキングセマンティクスを持っています。メソッドを呼び出すと、メソッドが完了すると呼び出されるコールバックオブジェクトを提供します。多くの場合、ブロッキングセマンティクスを使用してコードを作成する方が簡単です(ただし、スケーラビリティは低い可能性があります)。ブロッキングセマンティクスでは、メソッドは完了するまで単にリターンしません。これに対応するために、プロトコルバッファコンパイラは、サービスクラスのブロッキングバージョンも生成します。 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

生成されたコードには、import文を含めることはできません。これらは、生成されたコード自体の中で定義された型名と競合する可能性があるためです。代わりに、外部クラスを参照する場合は、常にその完全修飾名を使用する必要があります。

Javaコードジェネレータで出力ファイル名を決定するロジックは非常に複雑です。すべてのケースを網羅していることを確認するために、protocソースコード、特にjava_headers.ccを確認する必要があります。

標準コードジェネレータによって宣言されたプライベートクラスメンバに依存するコードを生成しないでください。これらの実装の詳細は、Protocol Buffersの将来のバージョンで変更される可能性があるためです。

ユーティリティクラス

Protocol bufferは、メッセージ比較、JSON変換、およびwell-known types(一般的なユースケース向けに事前定義されたプロトコルバッファメッセージ)を扱うためのユーティリティクラスを提供します。