Java 生成コードガイド

Protocol Buffer コンパイラが、任意のプロトコル定義に対してどのような 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.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 である場合、Protocol Buffer コンパイラはファイル build/gen/com/example/FooProtos.java を生成します。Protocol Buffer コンパイラは、必要に応じて build/gen/com および build/gen/com/example ディレクトリを自動的に作成します。ただし、build/genbuild は作成しません。それらは事前に存在している必要があります。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 オプションが提供されているのは、通常の .protopackage 宣言が逆引きドメイン名で始まるとは想定されていないためです。

メッセージ

新しい Protocol Buffer スキーマを設計している場合は、Java proto 名に関する推奨事項を参照してください。

単純なメッセージ宣言を考えます。

message Foo {}

Protocol Buffer コンパイラは、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 の例では、Protocol Buffer コンパイラは Foo を構築するために使用できるネストされたクラス Foo.Builder を生成します。Foo.BuilderMessage.Builder インターフェースを実装します。これは GeneratedMessage.Builder クラスを拡張しますが、これも実装の詳細と見なされるべきです。Foo と同様に、Foo.BuilderGeneratedMessage.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_bazfooBarBaz になります。get が接頭辞として付く場合、getFooBarBaz になります。そして foo_ba23r_bazfooBa23RBaz になります。

アクセサメソッドと同様に、コンパイラは各フィールドに対してそのフィールド番号を含む整数定数を生成します。定数名は、フィールド名を大文字に変換し、末尾に _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 を返します。
  • 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;
}

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

static final Foo BAZ = BAR;

したがって、BARBAZ は等しいと比較され、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 は実装の詳細と見なされます)。ただし、これらのスーパークラスは、拡張機能を操作するために使用できるいくつかの追加メソッドを定義しています。

特に、FooFoo.BuilderhasExtension()getExtension()、および getExtensionCount() メソッドを継承します。さらに、Foo.BuildersetExtension()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 引数は暗黙的に指定され、requestdone は正確な型を指定します。

FooServiceインターフェースをサブクラス化します。Protocol Bufferコンパイラは、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 実装に渡します。

スタブ

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

Foo.StubFoo のサブクラスであり、以下のメソッドも実装しています。

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

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

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

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

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