最近在家里修习 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基础知识8-异常处理

    配图 异常概述 异常是运行是在代码序列中引起的非正常情况.换句话说,异常是运行时错误. Java异常是用来描述在一段代码中发生的异常情况(也就是错误)的对象.当出现引起异常的情况时,就会创建用来表示异 ...

  2. java type 简书_Java中的Type详解

    ParameterizedType 参数化类型的使用 package com.keytop.att; /** * 实体类 * Created by fengwenhua on 2017/4/25. * ...

  3. json与java反射_Java 对象的 Json 化与反 Json 化

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

  4. java对象生命周期_Java对象生命周期和类生命周期

    原标题:Java对象生命周期和类生命周期 作者:彭空空 链接:https://www.jianshu.com/p/25ea857ba78b 导读 对象的生命周期 类的加载机制 类的生命周期 类加载器 ...

  5. java对象的类型转换_Java对象的类型转换和属性复制

    1. 问题的源由 在J2EE项目开发中,会涉及很多领域模型对象,例如, VO (View Object) 视图对象,也叫展示对象,用于前端页面渲染所需要的数据 DTO (Data Transfer O ...

  6. 回调java 简书_Java接口回调机制详解【转】

    一.回调的含义和用途 1. 什么是回调? 一般来说,模块之间都存在一定的调用关系,从调用方式上看,可以分为三类:同步调用.异步调用和回调.同步调用是一种阻塞式调用,即在函数A的函数体里通过书写函数B的 ...

  7. java 对象池 实现_Java对象池技术的原理及其实现

    看到一片有关于java 对象基础知识,故转载一下,同时学习一下. 摘 要 本文在分析对象池技术基本原理的基础上,给出了对象池技术的两种实现方式.还指出了使用对象池技术时所应注意的问题. 关键词 对象池 ...

  8. java 对象向上转型_JAVA对象向上转型和向下转型

    今天做了一个测试的题目,发现自己还是很多问题没有静下心来做.很多问题是可以自己解决的但是自己一是没有读清题意,二是自己心里太急躁了.所以这个要自己应以为鉴! 对象的转型问题其实并不复杂,我们记住一句话 ...

  9. java 对象数组定义_Java对象数组定义与用法详解

    本文实例讲述了Java对象数组定义与用法.分享给大家供大家参考,具体如下: 所谓的对象数组,就是指包含了一组相关的对象,但是在对象数组的使用中一定要清楚一点:数组一定要先开辟空间,但是因为其是引用数据 ...

最新文章

  1. Centos服务器常用安装指南
  2. php 字符串转换xml,php 数组转xml字符串、xml字符串转数组方法案例
  3. C# linq Expression left join如何使用
  4. 推荐 12个Idea 免费实用插件及插件离线安装方法
  5. 简约至上,美炸天的Vue圆环菜单组件
  6. jQuery中each()的用法
  7. windows下socket编程GetLastError()函数返回结果与对照表-转
  8. vue key重复_12道vue高频原理面试题,你能答出几道?
  9. 主板找不到SSD解决一例
  10. python与Android有关的库,这15个Android开源库,只有经常逛Github的才知道!
  11. Caffe安装 (OPENCV4 Cuda10.2 Xavier)
  12. 2.2.7 虚拟Ethernet以太网卡
  13. web前端网页设计期末课程大作业:企业网页主题网站设计——舞蹈培训11页HTML+CSS+JavaScript
  14. action中获取jsp页面的参数的方法
  15. HUAWEIWATCH GT3有哪些功能?华为新款手表测评
  16. 最新咸鱼数据采集工具
  17. CentOS的自动化安装光盘制作 (详细流程)
  18. wps 甘特图_项目进度管理的好方法:甘特图
  19. Vue项目学习(2)
  20. 如何建设与推广地方旅游网站

热门文章

  1. 音视频技术开发周刊 | 239
  2. Java多线程之volatile详解
  3. 大牛书单 | 迎金秋,与腾讯技术大咖共读好书
  4. 当一个美术生开始在腾讯撸代码… |「递归」第1集
  5. redis接口的二次封装
  6. nginx基本数据结构及接口
  7. android adb wifi
  8. Hive 03_DML、SerDe、Beeline、JDBC
  9. java小编程---最长公共前缀
  10. 怎么在java中创建一个自定义的collector