最近在家里修习 Java 这项技能,估计快点满技能点儿了,很开心。不过遇到了一个问题,困扰了我一阵子。问题是这样的,我要写 Android App,与服务器交互。大家都知道 Javascript 不知为什么占领了浏览器脚本的全部份额,紧接着 Json 成为了 Web 服务器与浏览器传输数据的事实标准。大家也知道 Java 不知为什么成为了 Android App 开发的推荐做法,而 Java 听上去就有着浓浓的“企业级”味道,自有一个序列化和一大堆 RPC 标准。Json 跟 Java 听上去就格格不入,但我要把 Java 对象变成 Json 传出去,再收到 Json 变回 Java 对象!

先展示一下我的工具叭:我使用 android.util.JsonWriter 和 android.util.JsonReader,它们是做什么的呢?简单说,就是把 Json 字符串解析成 AST。比如我们可以用 reader.beginObject(), reader.nextName(), reader.nextInt() 去读取 Json 字符串里的左大括号,属性名和整型值,而不必考虑中间有几个空格,整数怎么一位一位读这些细节。或者说这个就是可以造出来的最低级的工具了。值得注意的是,我只会向同一个方向读写。

然后我先不想太多,先开始写代码。数组很好处理,读的时候先 beginArray() 读左中括号,然后在 hasNext() 返回 true 的时候用 nextXxx() 读下一个值(Xxx 可能是 Double, Int, Long, String 或 Null),如果返回 false 了,就调用 endArray() 读右中括号,保证状态适合接着读东西。至于读进来的东西,随便找个 Collection 扔进去就好。写数组就更好写了,先 beginArray() 写左中括号,然后每次用 value(val) 写值,最后 endArray() 写右中括号就好了。

对象有点麻烦但也不难处理,读的时候先 beginObject() 读左大括号,然后在 hasNext() 返回 true 的时候用 nextName() 读下一个属性名,然后用 switch ... case ... 判断这个属性应该读进哪个变量,并用 nextXxx() 把值读进这个变量,最后不要忘记 default 情况要 skipValue(),不然下面操作读取器的状态就不对了,直到 hasNext() 返回 false,用 endObject()读右大括号。写的话比较好办,就先 beginObject() 写左大括号,然后不停地 name(name) value(val) 写属性,完了 endObject() 写右大括号。一个对象有一个接收 JsonReader 的构造函数和一个接收 JsonWriter 的方法来做这一套过程。

啊听起来非常好,不过麻烦事儿是有继承。有继承后有两个问题,一是继承的对象 Json 数据该长成什么样子,二是这个数据该怎么解析。对问题一我思考一段时间(脑残儿童 QAQ)决定答案是 Json 的结构应该和 Java 保持一致,或者说就是如果 Java 里用了继承,那么新增的属性应该和原来的属性放在同一个对象里,如果 Java 用了委托,新增的属性就应该放在一个新的对象里作为原来对象的属性。对问题二我又思考了一段时间(TAT 脑残儿童不许笑)发现所谓 Json 就是一个序列化机制罢了。那看看前辈是怎么写序列化的,于是我拿来了 Parcelable,发现它们的做法是先写祖宗的属性,然后写自己的属性,读的时侯先在构造函数里调祖宗的构造函数读祖宗的属性,然后读自己的属性。我试图把这个办法搬到 Json 上。果然碰到问题了:祖宗构造函数和自己构造函数都要读大括号,然而我没有带那么多大括号可以读。不过我很快想到了解决办法:构造函数永远永远不读大括号,把大括号留给调用构造函数的人读,这样就好啦(嗯大括号熟么的在这种不需要数据结构自描述的情况下就是冗余米有错)。可是这样子以后我会被人抽的,因为 Json 对象不保证顺序,要是逼着写 Javascript 的在 Json 化时候必须保证顺序的话我相信它会毫不犹豫去跳楼的。

于是我想到了一个办法,就是给基类写一个接口,里面有一个方法。当基类读 Json 读到一个属性自己不认识的时候,就把属性名和 JsonReader 对象传给这个方法。这样,子类只要写一个内部类实现这个接口,在方法中看看这个属性认识不认识,认识就读进来,不认识就递归地给自己的子类的这个接口处理。

不过这么做还是有问题。问题是基类的部分控制代码每次写个基类都得写一遍,得抽出来。然后我又想了好长时间,发现其实根本问题是我有一堆属性,我得给每个属性在继承树上找到它应该在的位置。这样说来,每次读取属性名的操作就不应该由基类来做了,应该由外面的函数来做。这些类们只实现一个方法 readProperty:接收一个属性名和一个 JsonReader,要是自己认得,就读出属性值,否则把属性名和 JsonReader 照样传给父类(如果有的话)或跳过值(如果父类没有 readProperty)。然后再写一个工厂方法叫 newInstanceFromJson,接收 JsonReader 返回新对象,它每次新建一个该对象的空白实例,不停地读属性名并喂给接收属性名那个方法,直到所有属性都读完了,这时看看对象需要知道的属性是不是都读进来了,要是没有就返回空或报异常,要是都读进来了就返回这个实例。考虑到上一篇文章所述和方便重用,我把工厂方法写在一个内部类里,并要求它们继承自 JsonCreator。JsonCreator 是个类,里面写着上述工厂方法的代码,并要求继承者实现 newBlankInstance 和 isComplete,前者是返回空白实例的方法,后者是判断是否读了所有要知道的属性的方法。readProperty 不写在 JsonCreator 里的原因是否则需要实现者手工维护 this 指针。然后我写一个接口 Jsonable,要求实现者实现 writeToJson 和 readProperty 方法。接口定义中包含一个内部类叫 JsonCreator 的定义,然后在文档中要求所有实现 Jsonable 的类都要定义一个静态成员叫 JsonCREATOR,它是 JsonCreator 的一个子类,其中实现了 newBlankInstance。

嗯其实我知道 Gson 可以很随意地把这件事做掉,但是我没有用 Gson,原因有两个。一个是我喜欢造轮子,另外一个是 Gson 这种东西一定会用一些反射,我不想用反射,我想只用语言的静态特性(比如找父类不用反射,用 super)。

但是这样子又有点儿问题,每一个 readProperty 里其实是一个拿字符串做的 switch...case... 语句,而 Java 里它的实现可以认为是查一次哈希表。这样一个属性如果一个类不认识,那么它就会去问它父类,父类要再不认识就会问父类的父类……这样问下去可能一直要查掉整个继承链的长度,好慢的感觉。

不过这个问题是没办法的事儿,我们可以把它抽象一下:有 N 个类,它们组成一个有根森林(边代表继承关系);有 M 个属性,每个属性可以被某些类拥有;现在每次给一个类和一个属性,查询从该类到根的路径上,距该类最近的有该属性的类。这个问题还有两个版本,就是可不可以动态添加类和属性,动态语言对应着可以动态添加类和属性的版本,静态语言则相反。这个问题里预处理对应着编译时,而查询处理对应着运行时。

暴力的方法有两个。一是给每个节点存它拥有的属性,然后每次查询从某个类开始逐个查父节点一直到根,预处理复杂度 O(M + N),查询复杂度 O(N) (最坏情况深度等于类的个数,并且认为判断表里是否有一个属性是 O(1) 的)。另一个是给每个节点存一个表,里面存着它拥有每个属性的最近祖先是谁,查询时直接查表即可,预处理复杂度 O(MN),查询复杂度 O(1)。而且前一个方法动态添加删除类和属性效率较高。

OI 里很流行 log 记号,带 log 的算法我只想了一个。先考虑每个属性只被一个类拥有的情况,这时我们可以预处理每个类的深度和它的 1, 2, 4, 8, ... 辈祖宗是谁,这样可以在 O(log N) 时间内找到它的 N 辈祖宗。查询时我们找到这个类,找到拥有这个属性的那个类,如果“这个类”比“那个类”深度更浅,那就找不到,否则求它们的深度差,找“这个类”的“深度差”辈祖宗,看是不是“那个类”。然后考虑每个属性被多个类拥有的情况,先找到这个类,然后找到拥有这个属性的 DFS 序恰小于这个类的那个类。然后把“那个类”到根路径上所有有这个属性的类做成一个表,在这个表里找到深度最深的是“这个类”的祖宗的类。寻找可以用二分的办法,因为如果一个类是“这个类”的祖宗,那么它的祖宗也是“这个类”的祖宗。二分法做起来可以这么办:预处理出“那个类”到根的路径里距它 1, 2, 4, 8, ... 近的有该属性的类,然后就可以折半了。二分后的判断就用之前简单情况的办法,这样整个方法,预处理的复杂度是 O((N + M) log N),查询的复杂度是 O(log N * log M)。不过这个方法不支持动态添加删除类和属性,要支持的的话应该要动态树什么的我不会 T_T。

json与java反射_Java 对象的 Json 化与反 Json 化相关推荐

  1. Java反射实例化对象

    Java 反射实例化对象 1.通过反射实例化对象 首先要获取目标类的class对象,可以通过以下三种方法 /** 获取class对象的三种方法`在这里插入代码片`* *///1.通过 类名.class ...

  2. json java对象 简书_Java 对象的 Json 化与反 Json 化

    最近在家里修习 Java 这项技能,估计快点满技能点儿了,很开心.不过遇到了一个问题,困扰了我一阵子.问题是这样的,我要写 Android App,与服务器交互.大家都知道 Javascript 不知 ...

  3. java反射克隆对象_Java反射 - 2(对象复制,父类域,内省)

    为什么要复制对象?假设有个类Car,包含name,color2个属性,那么将car1对象复制给car2对象,只需要car2.setName(car1.getName)与car2.setColor(ca ...

  4. json转java代码_Java代码:javabean和json互转

    使用jsonlib可以将javaBean和json进行相互转化 将一个对象转化成一个json字符串,如下:Student stu=new Student(); stu.setName("JS ...

  5. java 反射获取对象_java 反射-反射对象的获取

    java.lang.Class对象的获取方式 @SuppressWarnings("all") //压制警告 public class Deam { public static v ...

  6. 通过Java反射获取对象上的注解,java反射注解妙用-获取所有接口说明

    原标题:java反射注解妙用-获取所有接口说明 转载请注明出处:https://www.cnblogs.com/wenjunwei/p/10293490.html 前言 最近在做项目权限,使用shir ...

  7. Java反射 - 2(对象复制,父类域,内省)

    为什么要复制对象?假设有个类Car,包含name,color2个属性,那么将car1对象复制给car2对象,只需要car2.setName(car1.getName)与car2.setColor(ca ...

  8. Java反射获取对象的属性值

    当知道某个类,想获取类上的某个属性的值时,有时会用到Java的反射机制,如下参考: /*** 反射获取对象的属性值* @param object 对象(要遍历的对象)* @param targetFi ...

  9. java 反射获取对象_使用Java反射机制获取对象

    本文由广州疯狂软件教育java培训分享: 构造接口Person,所有Person都会问好,但具体讲什么语言就不知道了! package interf; public interface Person ...

最新文章

  1. Java网络编程——11.非阻塞I/O
  2. Hadoop示例程序WordCount详解及实例
  3. 职业生涯又一个转折点
  4. 喜茶获美团点评旗下基金4亿元投资!它离行业独角兽还差多远?
  5. VTK:可编程源用法实战
  6. prototype.js教程及prototype中文手册
  7. 小K的农场(差分约束)
  8. [Linux + 深度学习]Ubuntu18.04 深度学习环境配置
  9. java宠物实训报告,基于Java的宠物用品商城的设计与实现-开题报告
  10. 【转】大厦将倾,互联网将如何变革传统行业(下)
  11. spring-boot-starter-parent 包maven依赖报错
  12. Win8.1下COCOS2D-X 3.4环境搭建
  13. WinDbg蓝屏分析入门
  14. 「需求广场」需求词更新明细(四)
  15. python模拟微信登录公众号_PYTHON 微信公众平台模拟登陆
  16. Functional Programming
  17. 一枚菜鸟前端工程师月度工作总结
  18. 台式计算机识别不了u盘启动,如何解决惠普电脑uefi识别不了u盘
  19. 项目5-模板类中使用友元函数
  20. cdh6.2离线安装(傻瓜式安装教程)

热门文章

  1. SQLI_LAB——Less7~15
  2. Python开发以太坊智能合约指南(web3.py)
  3. legend---十二、js中的js语句和函数和ready函数的关系是什么
  4. python3实现抓取网页资源的 N 种方法(内附200GPython学习资料)
  5. Java 流程控制与数组
  6. @ModelAttribute运用详解
  7. PHP框架 Phalcon 1.0.0 beta发布,实测性能强劲
  8. web.config总结
  9. RPA技术干货 | RPA防锁屏运行技巧
  10. C# API强制关机、重启以及注销计算机