Tensorflow的Protocol Buffers3编程(三)

接下来是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中。

Python生成的代码

字段

除了在前一节描述的方法之外,protocol buffer编译器为在.proto文件中定义的消息的每个字段生成访问器方法集合。读取字段的方法被定义在消息类和它对应的builder上,修改值的方法仅在builder上被定义。

注意方法名称通常使用驼峰法命名,就是在.proto文件中的字段名使用带下划线的小写(应该如此)。转换工作如下:

  1. 对于名字中的每个下划线,下划线被删除,而下一个字母大写。
  2. 如果名字有附加前缀(如 "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(): 如果字段被设置返回true
  • int 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(): 如果字段已经设置则返回true
  • setFoo() 也接受消息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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值