介绍 .proto Java 对象中的方法 methods in interface methods in object methods in Builder 示例
介绍
介绍日后再补 TODO, 总之想要了解 protobuf 的工作原理, 需要首先知道编程中的 "Builder 模式"-- 由于类属性过多而出现的一种较好的解决方式. 没有公有构造函数, 设置属性仅能通过 Builder 的 set 类方法等等. 具体可查阅资料学习.
这里介绍一下 protobuf 生成的 Java 对象结构.
.proto
假设 protobuf 定义文件如下:
- option java_outer_classname = "Test";
- message A {
- required string a = 1;
- message B1 {
- optional string b = 1;
- message C {
- optional string c = 1;
- }
- optional C c = 2;
- }
- optional B1 b1 = 2;
- message B2 {
- optional int32 b = 1;
- }
- optional B2 b2 = 3;
- optional int32 num = 4;
- }
则会生成类名为 Test.java 的文件, 结构和 protobuf 文件定义的一致: A 对象, 包含 a/b1/b2/num 四个属性, 其中 b1 类型为 B1,b2 类型为 B2;B1 中包含 b/c 两个属性, 其中 c 类型为 C, 其中只包含一个属性 c;B2 只包含一个属性 b.
Java 对象中的方法
主要看一下生成的对象中, 都有哪些方法可供使用. 由于生成的 Java 文件太大, 就不再贴上来了.
methods in interface
首先, 针对最外层, 会生成 AOrBuilder 接口. 类 A 会实现该接口, 类 A 的静态内部类 (或者说静态嵌套类)A.Builder 也会实现该接口.
接口中包含的方法基本都是获取各个属性的 --
比如对于 A 的 String 类型属性 a, 有:
- hasA(), 判断属性 a 是否存在值;
- getA(), 获取属性 a 的值;
- getABytes(), 仅针对 string 类型的属性, 如果 a 是 int 型, 自然不会有此方法.
如果属性是结构体型的, 比如对于 b1, 其类型为 B1, 那么接口中会有 getB1OrBuilder() 方法. TODO
methods in object
对于 object, 比如类型 A, 主要有以下几种类型的方法 --
object 本身的特性:
- isInitialized(), 判断 required 属性是否设置了, 如果没有则抛异常;
- getSerializedSize(), 获取序列化后的大小;
对属性的 get 方法:
这一点是对上述接口中方法的实现. 比如: hasA()/getA()/getABytes()/getB1OrBuilder(), 详见对接口方法的介绍.
转换方法:
- newBuilder(), 新建一个 Builder 对象, 这也是构造对象的最常用方式, 毕竟没有公有构造函数;
- toBuilder(), 将对象转换为 Builder, 返回的是深拷贝, 对该 Builder 的属性的修改不会影响到原对象;
- parseFrom(ByteString/byte[]/InputStream), 从一堆字节中解析出对象;
转换方法是比较重要的.
object 无法做到对属性的值进行删改, 只能查询. 删改只有通过 Builder 才能做到.
methods in Builder
对于 Builder, 比如 A.Builder, 主要有以下几种类型的方法 --
查询属性:
同上述接口中介绍的方法, 毕竟 Builder 实现了上述接口. 比如: hasA()/getA()/getABytes()/getB1OrBuilder()
删改属性. 比如对于 A 的属性 a, 有:
- setA(String),set 属性 a 的值;
- setABytes(ByteString), 同上, 自然也只有当 a 为 string 的时候才会有此方法;
- clearA(), 清除 a 的值;
如果属性是复杂的结构体, 比如对于属性 b1, 还存在如下删改方法:
- setB1(B1)/setB1(B1.Builder), 前者没什么特别之处, 但是后者作为重载方法, 可以使用 B1.Builder 类型的对象直接 setB1 类型的对象的值;
- mergeB1(B1), 同样是设置 b1 的值, 区别: TODO
转换方法:
- build(), 将 Builder 转为 object, 如果 required 属性没有设置值, 抛异常;
- builderPartial(), 同样将 Builder 转为 object, 但是 required 属性没有设置值也不会有问题;
- mergeFrom(A), 直接从一个现有的 A 对象, 深拷贝其属性, 构造一个全新的 A.Builder 对象;
Builder 本身的一些方法:
- - clone()
- - clear()
最值得注意的是, A.Builder 中含有便捷的方法, 能够直接获取 Builder 中嵌套的结构体的 Builder, 即 "使用 builder 直接获取深层 builder".
比如给出 A.Builder, 想要修改其中的属性 b1 的属性 b:
aBuilder.getB1Builder().setB("convenint_set")
就完成了对值的修改.
否则, 需要这样去修改:
- Test.A.B1.Builder b1Builder = aBuilder.getB1().toBuilder().setB("change_only_if_set_again");
- aBuilder.setB1(b1Builder); // 使用 b1 或 b1Builder 都行, 有重载方法
先获取 A.Builder 中的 B1 对象, 再转 B1.Builder, 最后 set 属性 b; 再把新对象设置回 A.Builder. 否则原 aBuilder 是不会改变的.
所以使用 builder 获取深层次 builder 并修改, 是非常方便的, 尤其是在嵌套层级比较深的情况下.
再比如, 想要修改其中的属性 b1 的属性 c 的属性 c, 只需:
aBuilder.getB1Builder().getCBuilder().setC("builder_get_builder")
就完成了对值的修改.
示例
- /**
- * @author liuhaibo on 2018/06/27
- */
- public class Main {
- public static void main(String... args) {
- Test.A.B1.C cObject = Test.A.B1.C.newBuilder().setC("c").build();
- Test.A.B1 b1Object = Test.A.B1.newBuilder().setB("b").setC(cObject).build();
- Test.A.B2 b2Object = Test.A.B2.newBuilder().setB(1).build();
- Test.A aObject = Test.A.newBuilder().setA("a").setB1(b1Object).setB2(b2Object).build();
- System.out.println("///* Methods for object *///");
- methodsForObject(aObject);
- System.out.println("///* Methods for Builder *///");
- methodsForBuilder(aObject, b1Object);
- }
- private static void methodsForObject(Test.A aObject) {
- System.out.println("A =" + aObject);
- System.out.println("A.isInitialized() =" + aObject.isInitialized());
- // Exception in thread "main" com.google.protobuf.UninitializedMessageException: Message missing required fields: a
- // System.out.println("A(without a field).isInitialized() =" + aObject.toBuilder().clearA().build().isInitialized());
- System.out.println("A.getSerializedSize() =" + aObject.getSerializedSize());
- System.out.println("A.Builder =" + aObject.toBuilder());
- System.out.println("A.getB1OrBuilder() =" + aObject.getB1OrBuilder());
- System.out.println("A.b1 =" + aObject.getB1());
- }
- private static void methodsForBuilder(Test.A aObject, Test.A.B1 b1Object) {
- // builder
- Test.A.Builder aBuilder = Test.A.newBuilder();
- // merge from a existing object
- aBuilder.mergeFrom(aObject);
- System.out.println(aBuilder);
- // change a structure in object
- aBuilder.mergeB1(b1Object.toBuilder().setB("change_a_structure").build());
- System.out.println(aBuilder);
- // 用 builder 去 get builder, 需要修改值直接 set 就可以了
- aBuilder.getB1Builder().getCBuilder().setC("builder_get_builder").build();
- System.out.println(aBuilder);
- // 用 object 去 toBuilder, 再 set 值, 那么原本的 object 不会变
- aBuilder.getB1().toBuilder().setB("no_change_happens");
- System.out.println(aBuilder);
- // 除非再 set 回去
- Test.A.B1.Builder b1Builder = aBuilder.getB1().toBuilder().setB("change_only_if_set_again");
- aBuilder.setB1(b1Builder); // 使用 b1 或 b1Builder 都行, 有重载方法
- System.out.println(aBuilder);
- // buildPartial() vs. build()
- aBuilder.clearA().getB1Builder().getCBuilder().setC("2333"); // 现在已经是 CBuilder 对象了, 不再是 ABuilder, 所以调用 build() 也是 CBuilder 的方法
- // safe
- System.out.println(aBuilder.buildPartial());
- aBuilder.clearA();
- // Exception in thread "main" com.google.protobuf.UninitializedMessageException: Message missing required fields: a
- // System.out.println(aBuilder.build());
- }
- }
来源: https://www.2cto.com/kf/201806/757458.html