前言

最近做项目遇到了一个很奇怪的问题,情况如下:

创建对象TestBean,其中type和name需要接口返回并解析,time字段需要客户端修改,做一些必要的记录,希望time的默认值为10:

val jsonStr ="{type: 99, name:\"superman\"}"data class TestBean(val type: Int, val name: String, var time: Long = 10)

在运行前,我认为这段代码非常完美,但是结果却很意外:

难道Gson把构造方法中的time设置成0了吗?,再次修改代码:

data class TestBean(val type: Int, val name: String) {var time: Long = 10override fun toString(): String {return "type:$type, name:$name, time:$time"}
}

默认情况下,data class的toString方法只会打印构造方法中的属性,所以还需要重写一下toString。我把time属性从构造方法中移出,这次应该是稳得一匹了吧:

what???,time的值并不是10,虽然没有找到原因,但是我还可以再改,男人不可以说不行:

data class TestBean(val type: Int, val name: String) {var time: Long = 10init {time = 10Log.e("lzp", "TestBean create")}override fun toString(): String {return "type:$type, name:$name, time:$time"}
}

这一次,我在init方法中,手动设置time=10,并且输出日志,对象创建时一定会执行init方法,绝对ojbk:

init方法也没执行???好的,让我静下心来仔细的找到问题到底出现在哪里。

正文

经过刚才的踩坑,可以肯定的是,Gson没有调用TestBean的构造方法。那么他是怎么创建TestBean的呢?一般来说创建对象有以下几种方式:

调用构造方法:TestBean() (new关键字或者反射最终都是使用了构造方法)
复制:Object.copy()

还是要从Gson的源码去分析这个问题:

val testBean = Gson().fromJson<TestBean>(jsonStr, TestBean::class.java)

我们对fromJson方法进行追踪,发现fromJson重写了好几个,最终会定位到:

 public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {boolean isEmpty = true;boolean oldLenient = reader.isLenient();reader.setLenient(true);try {reader.peek();isEmpty = false;TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);// 获取具体解析的TypeAdapterTypeAdapter<T> typeAdapter = getAdapter(typeToken);// 返回解析的结果T object = typeAdapter.read(reader);return object;} catch (EOFException e) {...}
}

由于TypeAdapter的子类实在是太多了,如果要去仔细的翻源码太耗时间,直接断点:

我们要找的就是ReflectiveTypeAdapterFactory的内部类Adapter,直接找他的read方法:

@Override public T read(JsonReader in) throws IOException {if (in.peek() == JsonToken.NULL) {in.nextNull();return null;}// 这里创建对象了T instance = constructor.construct();// 解析对应的属性,忽略try {in.beginObject();while (in.hasNext()) {String name = in.nextName();BoundField field = boundFields.get(name);if (field == null || !field.deserialized) {in.skipValue();} else {field.read(in, instance);}}} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IllegalAccessException e) {throw new AssertionError(e);}in.endObject();return instance;}

终于找到创建对象的代码,让我们看看这个constructor到底是个什么玩意,经过各种定位,最终我们找到了分析问题的最关键类:ConstructorConstructor。

public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {final Type type = typeToken.getType();final Class<? super T> rawType = typeToken.getRawType();// 优先使用Type获取对象的构造方法final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);if (typeCreator != null) {return new ObjectConstructor<T>() {@Override public T construct() {return typeCreator.createInstance(type);}};}// 再次使用rawType获取对象的构造方法@SuppressWarnings("unchecked") // types must agreefinal InstanceCreator<T> rawTypeCreator =(InstanceCreator<T>) instanceCreators.get(rawType);if (rawTypeCreator != null) {return new ObjectConstructor<T>() {@Override public T construct() {return rawTypeCreator.createInstance(type);}};}/*** 分割线以上都是instanceCreators取出来的**/// 通过默认的无参构造方法,请注意是无参的构造方法ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);if (defaultConstructor != null) {return defaultConstructor;}// 以上三步仍没有找到构造方法时的处理ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);if (defaultImplementation != null) {return defaultImplementation;}// 通过不安全的形式创建对象???return newUnsafeAllocator(type, rawType);}

其中前两中方法都是我们通过配置Gson可以设置的,因为我们并没有设置,所以这里先跳过,我们需要关注后三步,第一步:找到无参的构造方法:

private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {try {// 反射无参的构造方法final Constructor<? super T> constructor = rawType.getDeclaredConstructor();if (!constructor.isAccessible()) {accessor.makeAccessible(constructor);}return new ObjectConstructor<T>() {@Override public T construct() {try {Object[] args = null;// 调用无参构造方法创建实例return (T) constructor.newInstance(args);} catch (Exception e) {...}}};// 没有无参构造方法直接返回null} catch (NoSuchMethodException e) {return null;}}

熟悉反射的话,这里都很好理解,如果还不太熟悉反射的话,可以先去查看一下反射的知识。如果没有无参的构造方法,会进入newDefaultImplementationConstructor:

private <T> ObjectConstructor<T> newDefaultImplementationConstructor(final Type type, Class<? super T> rawType) {if (Collection.class.isAssignableFrom(rawType)) {... 集合子类会有一些创建操作}if (Map.class.isAssignableFrom(rawType)) {... Map子类的创建操作}return null;}

newDefaultImplementationConstructor只对集合和Map的子类有创建操作,很明显TestBean只是一个普通对象,并不符合需求。经过以上四步,我们没有找到任何可以创建TestBean的方法,那么唯一的答案就只能是在newUnsafeAllocator(type, rawType)中了:

public static UnsafeAllocator create() {// 最关键try {// 反射找到sun.misc.Unsafe类Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");// 找到sun.misc.Unsafe类中的theUnsafe属性Field f = unsafeClass.getDeclaredField("theUnsafe");// 激活theUnsafe属性f.setAccessible(true);// 得到theUnsafe的对象final Object unsafe = f.get(null);// 反射allocateInstance方法final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);return new UnsafeAllocator() {@Override@SuppressWarnings("unchecked")public <T> T newInstance(Class<T> c) throws Exception {assertInstantiable(c);// 调用allocateInstance方法,创建类型为c的对象return (T) allocateInstance.invoke(unsafe, c);}};} catch (Exception ignored) {}// 第二步的实现方案和第一步其实一样,所以忽略...// 第三步,在我的版本源码中ObjectInputStream找不到newInstance方法,所以忽略...     }

还是反射相关的调用,理解起来并不难,重点是理解sun.misc.Unsafe类到底是个什么东西,可以无视对象的构造方法,创建新的对象,我从网上分享的源码中,截取一部分注释的描述一下它:

sun.misc.Unsafesh收集了很多底层的不安全的操作方法。尽管类和方法是开放的,但是要谨慎的使用它,因为只有信任的代码才可以使用它。
allocateInstance方法:创建一个实例,但是不运行它的构造方法,请手动初始化。

sun.misc.Unsafe还有很多其他的native方法,Google已经明确说明要谨慎使用,除非是极其特殊的情况,我们还是把它记在心里就好了。

总结

现在我们已经了解了Gson创建对象的过程,那么一开始的问题要怎么解决呢?经过分析源码我们有以下两种方案:

第一种方案::Gson配置TypeAdapter。

 val testBean2 = GsonBuilder().registerTypeAdapter(TestBean::class.java, TestBeanTypeAdapter()).create().fromJson<TestBean>(jsonStr, TestBean::class.java)class TestBeanTypeAdapter : JsonSerializer<TestBean?>,JsonDeserializer<TestBean?> {@Throws(JsonParseException::class)override fun deserialize(json: JsonElement?, typeOfT: Type?,context: JsonDeserializationContext?): TestBean? {return if (json == null) {null} else {if (json is JsonObject) {return TestBean(json.get("type").asInt, json.get("name").asString)} else {return null}}}override fun serialize(src: TestBean?, typeOfSrc: Type?,context: JsonSerializationContext?): JsonElement? {return JsonPrimitive(Gson().toJson(src))}
}

第二种方案:为TypeAdapter,设置无参的构造方法:

class TestBean {var type: Int = 0var name: String = ""var time: Long = 10init {Log.e("lzp", "TestBean create")}constructor(type: Int, name: String){this.type = typethis.name = name}override fun toString(): String {return "type:$type, name:$name, time:$time"}
}

Gson踩坑笔记:为什么对象的构造方法没有被执行?相关推荐

  1. 「Java」基于Mirai的qq机器人开发踩坑笔记(其二)

    目录 0. 配置机器人 1. onLoad方法 2. onEnable方法 3. 消息属性 4. 消息监听 I. 好友消息 II. 群聊消息 III. 无差别消息 5. 发送消息 I. 文本消息 II ...

  2. iphone se 一代 不完美越狱 14.6 视频壁纸教程(踩坑笔记)

    iphone se 一代 不完美越狱 14.6 加 视频壁纸教程-踩坑笔记 越狱流程 1.爱思助手制作启动u盘 坑点: 2.越狱好后 视频壁纸软件 1.源 2.软件安装 越狱流程 1.爱思助手制作启动 ...

  3. Linux内核踩坑笔记

    systemtap embedded C踩坑笔记戳这: https://blog.csdn.net/qq_41961459/article/details/103093912 task_struct的 ...

  4. 阿里云部署Tiny Tiny RSS踩坑笔记

    阿里云部署Tiny Tiny RSS踩坑笔记 前言 入坑了RSS,之前的配置是阿里云部署RSSHub,配合Inoreader进行文章阅读,详情见RSS入坑指南.阿里云部署RSSHub踩坑笔记.在202 ...

  5. 「Java」基于Mirai的qq机器人开发踩坑笔记(其一)

    目录 0. 前置操作 I. 安装MCL II. MCL自动登录配置 III. 安装IDEA插件 1. 新建Mirai项目 2. 编写主类 3. 添加外部依赖 4. IDEA运行 5. 插件打包 6. ...

  6. 昆仑通态触摸屏1003故障码,踩坑笔记

    昆仑通态触摸屏1003故障码,踩坑笔记 第一次使用这个昆仑通态触摸屏,使用modbusRTU与金田变频器做通讯. 触摸屏在线后报1003通讯错误代码,现象是控制指令正常,但是读取不正常.读取变频器状态 ...

  7. EDUSOHO踩坑笔记之四十二:资讯

    EDUSOHO踩坑笔记之四十二:资讯 获取资讯列表信息 GET /articles/{id} 权限 老API,需要认证 参数 字段 是否必填 描述 sort string 否 排序,'created' ...

  8. EDUSOHO踩坑笔记之三十三:班级

    EDUSOHO踩坑笔记之三十三:班级 班级 班级 获取班级信息 获取班级列表 班级成员 获取班级计划 加入班级 营销平台加入班级 班级 班级 获取班级信息 GET /classrooms/{class ...

  9. uniapp引入vantweapp踩坑笔记

    vue-cli创建uniapp项目引入vantweapp踩坑笔记 uni-app中引入vantweapp vue-cli创建uniapp项目引入vantweapp踩坑笔记 一.环境准备 二.项目搭建 ...

最新文章

  1. 【C++】 保存内容到文件工具
  2. 妙用QTP F1帮助功能
  3. 0902 - Preferences Permission
  4. JSP简单练习-包装类综合应用实例
  5. java.lang.IllegalStateException: ContainerBase.addChild: start
  6. mysql获取当前时间,及其相关操作
  7. [js] innerHTML与outerHTML有什么区别?
  8. 园龄一年了,可还未动笔.
  9. Stanford机器学习---第十一讲.异常检测
  10. Pspice 使用指南(中文)
  11. oracle连接no listener
  12. html里面判断字段显示,HTML特殊字符显示
  13. 支付宝 支付返回 4000 ,系统繁忙请稍后再试
  14. 安卓神器-kiwi browser
  15. 【Jaya算法解决柔性作业车间调度问题(附代码)】
  16. 算法【哈希】 | 哈希表
  17. 按摩界的“爱马仕”,拯救你的发际线,失眠、职业病通通消失,爽爆了!
  18. Revit 视图范围的知识总结
  19. 迅雷起死回生背后的男人,竟然是雷军
  20. 吴恩达深度学习卷积神经网络学习笔记(2)——经典神经网络

热门文章

  1. java使用POI导出word数据以及生成word表格
  2. 视频教程-python全栈习题课-Python
  3. 卢梭:人无往不在枷锁之中
  4. 用两种方法改错,体会封装和友员的关系!
  5. 布尔教育_燕十八 php,布尔教育_燕十八_HTML视频资源课件
  6. 简单的给数字加密解密
  7. EOJ Monthly 2021.1
  8. (转)水经注谷歌地图的级别与对应比例尺及分辨率探究
  9. bit、Byte、bps、Bps、pps单位详解
  10. 多说最近访客通用代码使用方法