接下来是Java的API文档。
编译器调用
当使用--java_out= 命令行标记时,protocol buffer编译器生成java输出。--java_out= 选项的参数是想编译器写java输出的目录。编译器为每个.proto文件输入创建一个单一的.java文件.这个文件包含一个单一的outer class定义,包含一些内嵌类和静态字段,基于.proto文件中的定义。
outer class的名字如下可选:如果.proto文件包含如下的一行:
option java_outer_classname = "Foo";
则outer class的名字将会是 Foo。否则,outer class名字通过转换.proto文件的基本名称为驼峰法来决定。例如,foo_bar.proto将变成FooBar。如果在文件中有相同名字的消息,"OuterClass"将被附加到outer class的名字后。例如,如果 foo_bar.proto 包含一个名为 FooBar 的消息,outer class将变成 FooBarOuterClass 。
Java package名字的选择在后面的 Packages 一节中描述。
通过连接参数到 --java_out= 来选择输出文件, package 名称(使用/替代.)和 .java 文件名。
因此,例如,如下调用编译器:
protoc --proto_path=src --java_out=build/gen src/foo.proto
如果 foo.proto 的java package是 com.example 并且它的outer class名称是 FooProtos,那么protocol buffer编译器将生成文件 build/gen/com/example/FooProtos.java
。protocol buffer编译器在需要时将自动创建 build/gen/com 和 build/gen/com/example 目录。但是,它将不会创建 build/gen 或者 build 。他们必须已经存在。可以在一次调用中指定多个 .proto文件;所有输出文件将被一次性生成。
==当输出java代码时,protocol buffer编译器直接输出到jar包的能力是非常方便的,因为很多java工具可以直接从jar文件中读取源代码。要输出到jar文件,简单的提供以.jar结束的输出位置。注意,仅有java源代码被放置在包中;还是必须单独编译来生成java class文件==
Python生成的代码Packages/包
生成的类被放置在一个基于 java_package 选项的java package中。如果这个选项缺失,将替代使用 package 定义。
例如,如果 .proto 文件包含:
package foo.bar;
那么结果的java类将放置在Java package foo.bar中。不过,如果 .proto 文件也包含一个 java_package 选项,像这样:
package foo.bar;
option java_package = "com.example.foo.bar";
那么类将放置在 com.example.foo.bar package中。提供 java_package 选项是因为通常 .proto package 定义不会以倒置的域名来开始。
Python生成的代码消息
给出一个简单的消息定义:
message Foo {}
protocol buffer 编译器生成名为 Foo 的类,实现 Message 接口。这个类被定义为 final, 不容许任何子类。Foo 继承自 GeneratedMessage, 但是这个可以认为是实现细节。默认, Foo 用为实现最大速度的特别版本来覆盖很多 GeneratedMessage 的方法。不过,如果 .proto 文件包含这行:
option optimize_for = CODE_SIZE;
则 Foo 将只覆盖功能必要方法的最小集合,而其他方法依赖 GeneratedMessage 的基于反射的实现。这显著的降低了生成代码的大小,但是也降低了性能。再不然,如果 .proto 文件包含:
option optimize_for = LITE_RUNTIME;
那么 Foo 将包含所有方法的快速实现,但是将实现 MessageLite 接口,MessageLite 接口只包含 Message 的方法子集。尤其,它不支持描述符和反射。但是,在这种模式下,生成的代码仅需要链接到libprotobuf-lite.jar而不是libprotobuf.jar。这个"lite"类库比完全类库小很多,而且更加适合资源受限系统例如移动手机。
Message 接口定义方法让你检查,操纵,读取或者写入完整的消息。在这些方法之外, Foo class定义了下列静态方法:
- static Foo getDefaultInstance(): 返回Foo的单例实例,这等效于调用Foo.newBuilder().build() (这样所有字段被重置而所有重复字段清空)。注意消息的默认实例可以通过调用它的newBuilderForType()用来作为工厂(factory)。
- static Descriptor getDescriptor(): 返回类型的描述符。这包含关于类型的信息,包括它有什么字段和他们的类型是什么。这可以和消息的反射方法一起使用,例如 getField().
- static Foo parseFrom(...): 从给定的资源解析类型 Foo 的消息并返回它。在Message.Builder接口中的mergeFrom()的每个变量有一个对应的parseFrom方法。注意parseFrom()从来不抛出UninitializedMessageException; 如果被解析的消息缺失必要的字段它会抛出InvalidProtocolBufferException。这使得它和调用Foo.newBuilder().mergeFrom(...).build()有微妙的不同。
- static Parser parser(): 返回解析器实例,它实现了多个parseFrom()方法
- Foo.Builder newBuilder(): 创建一个新的builder(下面描述).
- Foo.Builder newBuilder(Foo prototype): 创建一个新的builder,所有字段初始化为和在prototype中一样的值。因为内嵌消息和字符串对象是不可变,他们将会在原始对象和复制对象之间共享。
Builders
Message 对象 - 例如上面描述的 Foo class的实例 - 是不可变的,就像Java字符串。为了构建一个消息对象,需要使用builder。每个message类有它自己的bilder类 - 因此在我们的Foo例子中,protocol buffer编译器生成了一个内嵌类 Foo.Builder 可以用来构建Foo。Foo.Builder实现Message.Builder接口。它继承自GeneratedMessage.Builder类,但是,再次,这应该视为实现细节。和Foo类似,Foo.Builder 可能依靠范型方法实现,或者,当optimize_for选项被使用时,生成的定制化代码要快的多。
Foo.Builder没有定义任何静态方法。它的接口和通过Message.Builder定义的完全一样,不同的是返回类型更加明确:Foo.Builder的方法修改了builder返回类型Foo.Builder,而build()返回类型Foo。
builder的修改内容的方法 - 包括字段setter - 通常返回builder的引用(例如,他们"return this;")。这容许多个方法调用可以在一行中链起来。例如:
builder.mergeFrom(obj).setFoo(1).setBar("abc").clearBaz();
Sub Builders
对于包含子消息的消息,编译器也生成子builder。这容许你反复修改深层嵌套的子消息而不用重新构建他们。例如:
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。以其:
If you have a Baz message already, and want to change the deeply nested val in Foo. Instead of:
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 { } }
在这种情况下,编译器简单的生成Bar作为内部类内嵌在Foo中。
字段
除了在前一节描述的方法之外,protocol buffer编译器为在.proto文件中定义的消息的每个字段生成访问器方法集合。读取字段的方法被定义在消息类和它对应的builder上,修改值的方法仅在builder上被定义。
注意方法名称通常使用驼峰法命名,就是在.proto文件中的字段名使用带下划线的小写(应该如此)。转换工作如下:
- 对于名字中的每个下划线,下划线被删除,而下一个字母大写。
- 如果名字有附加前缀(如 "get"),第一个字母大写,否则小写
因此,字段 foo_bar_baz
变成 fooBarBaz
. 如果带 get
前缀, 将是 getFooBarBaz
.
除访问方法之外,编译器为每个字段生成一个包含它的字段编号的整型常量。常量名是转换为大写并加上 _FIELD_NUMBER
字段名字。例如,假设字段 optional int32 foo_bar = 5
。编译器将生成常量 public static final int FOO_BAR_FIELD_NUMBER = 5;
。
Singular Fields (proto2)
对于这些字段定义的任何一个:
optional int32 foo = 1;
required int32 foo = 1;
编译器将在消息类和它的builder中生成下列访问器方法:
boolean hasFoo()
: 如果字段被设置返回trueint getFoo()
: 返回字段的当前值。如果字段没有设置,返回默认值。
编译器将仅在消息的builder中生成下列方法:
Builder setFoo(int value)
: 设置字段的值。调用之后,hasFoo()将返回true而getFoo()将返回这个值。Builder clearFoo()
: 清除字段的值。调用之后,hasFoo() 将返回 false 而 getFoo() 将返回默认值。
对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。
内嵌消息字段
对于消息类型,setFoo() 方法也接受消息builder类型的实例作为参数。这仅仅是一个快捷方式,等价于调用 builder的.build()方法并将结果传递给方法。
如果字段没有设置, getFoo() 方法将返回一个任何字段都没有设置的 Foo 实例(很可能是Foo.getDefaultInstance()返回的实例)。
此外,编译器生成两个访问器方法容许你访问消息类型关联的subbuilder。下列方法将被消息类和它的builder中生成:
FooOrBuilder getFooOrBuilder()
: 如果字段的builder已经存在则返回字段的builder;如果不存在则返回消息
编译器仅在消息的builder上生成下列方法:
Builder getFooBuilder()
: 返回字段的builder
Singular Fields (proto3)
对于这个字段定义:
int32 foo = 1;
编译器将在消息类和它的builder中生成下列访问器方法:
int getFoo()
: 返回字段的当前值。如果字段没有设置,返回默认值。
编译器将仅在消息的builder中生成下列方法:
Builder setFoo(int value)
: 设置字段的值。调用之后,hasFoo()将返回true而getFoo()将返回这个值。Builder clearFoo()
: 清除字段的值。调用之后,hasFoo() 将返回 false 而 getFoo() 将返回默认值。
对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。
内嵌消息字段
对于消息字段类型,将在消息类和它的builder上生成一个额外的访问器方法:
boolean hasFoo()
: 如果字段已经设置则返回truesetFoo()
也接受消息builder类型的实例作为参数。这仅仅是一个捷方式,等价于调用 builder 的.build()方法并将结果传递给方法。
如果字段没有设置, getFoo() 方法将返回一个任何字段都没有设置的 Foo 实例(很可能是Foo.getDefaultInstance()返回的实例)。
此外,编译器生成两个访问器方法容许你访问消息类型关联的subbuilder。下列方法将被消息类和它的builder中生成:
FooOrBuilder getFooOrBuilder()
: 如果字段的builder已经存在则返回字段的builder;如果不存在则返回消息
编译器仅在消息的builder上生成下列方法:
Builder getFooBuilder()
: 返回字段的builder
枚举字段
对于枚举字段类型,将会在消息类和它的builder上生成额外的访问器方法:
int getFooValue()
: 返回枚举的整型值
编译器仅在消息的builder上生成下列方法:
Builder setFooValue(int value)
: 设置枚举的整型值。
此外, 如果枚举类型未知,getFoo() 将返回 UNRECOGNIZED - 这是一个特别的附加值, 由proto3编译器添加到生成的枚举类型。
重复字段
对于这个字段定义:
repeated int32 foo = 1;
```language
编译器将在消息类和它的builder中生成下列访问器方法:
int getFooCount()
: 返回当前字段中的元素数量int getFoo(int index)
: 返回给定的以0为基准的下标位置的元素。List<Integer> getFooList()
: 以列表方式返回所有字段。如果字段没有设置,返回一个空列表。返回的列表是对于消息类不可变而对于消息builder类不可修改(原文:The returned list is immutable for message classes and unmodifiable for message builder classes.)。
编译器仅在消息的builder上生成下列方法:
Builder setFoo(int index, int value)
: 设置给定以0为基准的下标位置的元素的值Builder addFoo(int value)
: 使用给定的值附加一个新元素到字段中(最后面)Builder addAllFoo(Iterable<? extends Integer> value)
: 将给定 Iterable 中的所有元素都附加到字段中(最后面)Builder clearFoo()
: 从字段中删除所有字段。调用这个方法之后,getFooCount()将返回0.
对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。
重复内嵌消息字段
对于消息类型,setFoo() 和 addFoo() 也接受消息builder类型的一个实例作为参数。这仅仅是一个快捷方式,等价于调用 builder的.build()方法并将结果传递给方法。
此外,编译器将为消息类型在消息类和它的builer上生成两个额外的访问器方法,容许你访问关联的subbuilder:
FooOrBuilder getFooOrBuilder(int index)
: 如果特定元素的builder已经存在则返回builder;如果不存在则返回元素。如果这个方法调用来自消息类,它将总是返回消息而不是builder。List<FooOrBuilder> getFooOrBuilderList()
: 以不可修改的builder列表(如果可用)的方式返回整个字段。如果这个方法调用来自消息类,它将总是返回消息的不可变列表而不是不可修改的builder列表。
编译器仅在消息的builder上生成下列方法:
Builder getFooBuilder(int index)
: 返回指定下标的元素的builder。Builder addFooBuilder(int index)
: 为位于指定下标的默认消息实例附加并返回builder。Builder addFooBuilder()
: 为默认消息实例附加并返回builder。Builder removeFoo(int index)
: 删除位于给定以0为基准的下标位置的元素。List<Builder> getFooBuilderList()
: 以不可修改的builder列表的方式返回整个字段
重复枚举字段 (仅用于proto3)
编译器将在消息类和它的builder上生成下列额外方法:
int getFooValue(int index)
: 返回位于指定下标的枚举的整型值List getFooValueList()
: 以Integer列表的方式返回整个字段。
编译器将仅在消息的builder上生成下列额外方法:
Builder setFooValue(int index, int value)
: 设置位于指定下标的枚举的整型值。
Oneof 字段
对于这个oneof字段定义:
oneof oneof_name {
int32 foo = 1;
...
}
编译器将在消息类和它的builder中生成下列访问器方法:
- boolean hasFoo() (仅用于proto2): 如果oneof实例是Foo则返回true。
- int getFoo(): 如果oneof实例是Foo则返回oneof_name的当前值。否则,返回这个字段的默认值。
编译器将仅在消息的builder中生成下列方法:
- Builder setFoo(int value): 设置 oneof_name 为这个值并设置oneof实例为FOO。调用这个方法之后, hasFoo()将返回true,getFoo()将返回值而getOneofNameCase()将返回FOO。
-
Builder clearFoo():
- 如果oneof实例不是FOO则不会有任何变化
- 如果oneof实例是FOO,设置 oneof_name 为null,oneof实例为 ONEOF_NAME_NOT_SET。调用这个方法之后,hasFoo()将返回false,getFoo()将返回默认值而getOneofNameCase()将返回 ONEOF_NAME_NOT_SET 。
对于其他简单字段类型,将根据 scalar value types table 选择合适的java类型。对于消息和枚举类型,值类型被消息或者枚举类代替。
Map 字段
对于这个 map 字段定义:
map<int32, int32> weight = 1;
编译器将在消息类和它的builder中生成下列访问器方法:
Map<Integer, Integer> getWeight();
: 返回不可修改的map。
编译器将仅在消息的builder中生成下列方法:
Map<Integer, Integer> getMutableWeight();
: 返回可变的Map。注意对这个方法的多次调用将返回不同的map实例。返回的map引用可能对任何后续对builder的方法调用无效。Builder putAllWeight(Map<Integer, Integer> value);
: 添加在给定map中的所有记录到这个字段。
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 {
// 打包给定的消息到一个Any中,使用默认类型URL前缀 “type.googleapis.com”.
public static Any pack(Message message);
// 打包给定消息到一个Any中,使用给定类型URl前缀
public static Any pack(Message message,
String typeUrlPrefix);
// 检查这个Any消息的负载(payload)是不是给定的类型
public <T extends Message> boolean is(class<T> clazz);
// 解包Any到给定的消息类型。如果类型不匹配或者解析负载失败则抛出异常
public <T extends Message> T unpack(class<T> clazz)
throws InvalidProtocolBufferException;
}
Oneof
给假设有一个类似这样的oneof定义:
oneof oneof_name {
int32 foo_int = 4;
string foo_string = 9;
...
}
在 oneof_name 这个 oneof 中的所有字段将为他们的值使用共享的 oneof_name 对象。此外,protocol buffer compiler 将为每个oneof case生成一个Java枚举类型,如下所示:
public enum OneofNameCase implements com.google.protobuf.Internal.EnumLite {
FOO_INT(4),
FOO_STRING(9),
...
ONEOF_NAME_NOT_SET(0);
...
};
这个枚举类型的值有下列特殊方法:
- int getNumber(): 返回对象在.proto文件中定义的编号
- static Foo valueOf(int value): 返回对应给定编号的枚举对象,对于其他编号则返回null。
编译器也将在消息类和它的builder中生成下列访问方法:
- OneofNameCase getOneofNameCase(): 返回枚举指示哪个字段被设置。如果一个都没有设置返回 ONEOF_NAME_NOT_SET
编译器将仅在消息的builder中生成下列方法:
- Builder clearOneofName(): 设置 oneof_name 为null,并设置 oneof case 为ONEOF_NAME_NOT_SET
枚举
假设有这样的枚举定义:
enum Foo {
VALUE_A = 0;
VALUE_B = 5;
VALUE_C = 1234;
}
protocol buffer 编译器将生成一个名为 Foo 的Java枚举类型,带有同样的值集合。如果正在使用proto3,还将添加特殊值 UNRECOGNIZED 到枚举类型。生成的枚举类型的值有下列特殊方法:
- int getNumber():返回对象定义在.proto文件中的编号
- EnumValueDescriptor getValueDescriptor(): 返回枚举值的描述符,包含枚举值的信息如值的名字,编号和类型。
- EnumDescriptor getDescriptorForType(): 返回枚举类型的描述符,包含关于每个定义值的信息。
此外,Foo 枚举类型包含下列静态方法:
- static Foo forNumber(int value): 返回对应给定编号值的枚举对象。如果没有对应的枚举对象则返回null。
- static Foo valueOf(int value): 返回对应给定编号的枚举对象。这个方法被弃用,以forNumber(int value)方法替代,并将在即将出现的发布版本中移除。
- static Foo valueOf(EnumValueDescriptor descriptor): 返回对应给定枚举值描述符的枚举对象。可能比valueOf(int)快。在proto3中如果传递一个未知的枚举值描述符则会返回 UNRECOGNIZED
- EnumDescriptor getDescriptor(): 返回枚举类型的描述符,包含关于每个定义的枚举值的信息(和getDescriptorForType() 唯一的差别在于它是一个静态方法)
为每个枚举值生成一个带有 _VALUE 后缀的整型常量。
注意 .proto 语言容许多个枚举符号拥有同一个编号。有相同编号的符号是同义词。例如:
enum Foo {
BAR = 0;
BAZ = 0;
}
在这个案例中, BAZ 是 BAR 的同义词。在Java中, BAZ 将被定义为一个像这样的static fianl 字段:
static final Foo BAZ = BAR;
因此, BAR 和 BAZ 是相同的, 而 BAZ 应该从不出现在 switch 语句中。编译器通常选择用给定编号定义的第一个符号作为这个符号的"权威"版本。所有随后有同样编号的符号仅仅是别名。
枚举可以在消息类型中内嵌定义。编译器生成内嵌在消息类型类中的java枚举定义。
扩展 (仅用于proto2)
假设有一个消息带有扩展范围:
message Foo {
extensions 100 to 199;
}
protocol buffer编译器将让 Foo 继承 GeneratedMessage.ExtendableMessage 而不是通常的 GeneratedMessage. 类型的, Foo的builder将继承 GeneratedMessage.ExtendableBuilder 。你应该从不通过名字来引用这些基本类型(GeneratedMessage 被认为是实现细节)。然而,这些超类定义了一些额外的方法让你可以用来操作扩展。
尤其 Foo 和 Foo.Builder 将继承方法 hasExtension(), getExtension() 和 getExtensionCount()。此外, Foo.Builder 将继承方法 setExtension() 和 clearExtension()。每个这些方法都将获取一个扩展标识符(下面描述),作为它们的第一个参数,用来标记一个扩展字段。剩下的参数和返回值和为同样类型的普通(非扩展)字段而生成的那些对应的访问器方法完全相同。
给出一个扩展定义:
extend Foo {
optional int32 bar = 123;
}
protocol buffer 编译器生成一个名为 bar 的"扩展标识符",可以和 Foo的扩展访问器一起使用来访问这个扩展,像这样:
Foo foo =
Foo.newBuilder()
.setExtension(bar, 1)
.build();
assert foo.hasExtension(bar);
assert foo.getExtension(bar) == 1;
(扩展标识符的确切实现非常复杂而且涉及到范型的不可意思的(magical)的使用 - 不过,在使用时你不用担心扩展标识符是如何工作。)
注意 bar 将被声明为.proto文件的outer class的静态字段, 如 前面描述 的。在这个例子中,我们忽略了outer class 的名字。
扩展可以内嵌在其他类型中申明。例如,通用模式是这样:
message Baz {
extend Foo {
optional Baz foo_ext = 124;
}
}
在这个案例中,扩展标识符 foo_ext 内嵌在 Baz 中声明。它可以像下面这样使用:
Baz baz = createMyBaz();
Foo foo =
Foo.newBuilder()
.setExtension(Baz.fooExt, baz)
.build();
当解析一个可能有扩展的消息时,必须提供一个 ExtensionRegistry ,在这里已经注册了需要能够解析的任何扩展。否则,那些扩展将被当成未知字段。例如:
ExtensionRegistry registry = ExtensionRegistry.newInstance();
registry.add(Baz.fooExt);
Foo foo = Foo.parseFrom(input, registry);