Java语言里Hashmap序列化的一个坑
龚超 2018-07-25 来源 : 阅读 2220 评论 0

摘要:本文主要向大家介绍了Java语言里Hashmap序列化的一个坑,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍了Java语言里Hashmap序列化的一个坑,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

在做业务需求的过程中,遇到一个非常奇怪的问题。在一个继承了 Serializable 接口的java bean里按照常规操作添加了一个hashmap和与之对应的getter、setter,就像这样:


...

private HashMapmChooseMap;


publicHashMapgetChooseMap(){

return mChooseMap;

}


publicvoidsetChooseMap(HashMapchooseMap){

mChooseMap = chooseMap;

}

...


复制代码

然后我在某种情况下对含有这个hashmap的java bean进行了deep clone操作,就像这样:


/**

* 深度拷贝 要求data对象及其引用对象都实现了Serializable接口才可以用

*

* @param o 要深拷贝的对象

*/

public staticTdeepClone(T o)throwsIOException, ClassNotFoundException{

//将对象写到流里

ByteArrayOutputStream bo = new ByteArrayOutputStream();

ObjectOutputStream oo = new ObjectOutputStream(bo);

oo.writeObject(o);

//从流里读出来

ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());

ObjectInputStream oi = new ObjectInputStream(bi);

return (T) oi.readObject();

}


复制代码

很简单,对吧?在我的意料之中,这件事简直可以小的忽略不计,完全就是一个非常常规的操作。但是我被打脸了,啪啪的打,因为我发现了一个以前没遇到过的问题……为毛我的数据不见了?本来这个bean里的数据应该依照列表的形式在listview里加载出来,但是我发现我的listview一片空白。最关键的问题是,控制台并没有报错信息啊!

查错

面对这个问题,我先怀疑了一会儿人生。错误还是要查的,于是我只能先猜猜为什么会出现这个问题。我和之前的代码版本做了对比,发现我只多写了一个hashmap,就出现了问题,于是我怀疑是这个hashmap出现了一个我以前不太了解的坑,导致了现在的问题。

Debug

首先我要庆幸的是,我的listview是应该拿到后端接口数据之后渲染的,既然控制台没有相关的日志输出,我就debug了一下,看看是不是后端的数据问题。结果我发现了一个让我惊讶的现象,在我处理请求返回的代码中,我正好做了deep clone操作,结果出现了如下错误:


咦,控制台毛错没报,为毛一debug就出现了这个问题?在一开始的代码里,我在方法注释里已经写了, 深度拷贝 要求data对象及其引用对象都实现了Serializable接口才可以用 。这个错太打脸了,我竟然传了一个没有实现序列化接口的对象?再根据刚才的推断,难道说hashmap没有实现序列化接口?

追根溯源

非常震惊的我赶紧点开hashmap看了一眼:


public classHashMapextendsAbstractMapimplementsCloneable,Serializable


复制代码

我的脸有点疼。代码清清楚楚的写着,明明是序列化了……我不甘心,再次debug发现crash出现在这里:


...

oo.writeObject(o);

...


复制代码

有意思,这不是就是写对象吗,没想明白为什么挂在这里,于是我瞅了一眼hashmap的writeObject方法:


privatevoidwriteObject(ObjectOutputStream stream)throwsIOException{

// Emulate loadFactor field for other implementations to read

ObjectOutputStream.PutField fields = stream.putFields();

fields.put("loadFactor", DEFAULT_LOAD_FACTOR);

stream.writeFields();


stream.writeInt(table.length); // Capacity

stream.writeInt(size);

for (Entrye : entrySet()) {

stream.writeObject(e.getKey());

stream.writeObject(e.getValue());

}

}


复制代码

是 private 的?为什么不是public?于是我从crash出现的地方一层层点进去看代码,边看边想是哪里出了问题。看着看着我发现这么一段代码,在 ObjectOutputStream 类的 writeObjectInternal 方法中:


...

if (clDesc.hasMethodWriteReplace()){

Method methodWriteReplace = clDesc.getMethodWriteReplace();

Object replObj;

try {

replObj = methodWriteReplace.invoke(object, (Object[]) null);

} catch (IllegalAccessException iae) {

replObj = object;

} catch (InvocationTargetException ite) {

// WARNING - Not sure this is the right thing to do

// if we can't run the method

Throwable target = ite.getTargetException();

if (target instanceof ObjectStreamException) {

throw (ObjectStreamException) target;

} else if (target instanceof Error) {

throw (Error) target;

} else {

throw (RuntimeException) target;

}

}

...

}

}


复制代码

这下我看懂了,这不就是说,如果要这对象有自己的writeObject方法,就在这里会用反射的方式执行对象自己的writeObject方法么,这么一来hashmap里的 private void writeObject 就可以理解了。那我这个问题也就简单了,我在这里加个断点,我看看hashmap到底writeObject除了什么问题不就可以了么。

于是再次debug。不看不知道,一看吓一跳!我发现原来问题出现在这里:


What?!原来错误是从这里报出来的。我仔细看了一下报错后面的信息,居然是一个其他的类,确实不是一个可以序列化的类。

此时此刻我的脸真的很疼。

代码不会骗人,我确实传进来了一个没有序列化的类。于是我赶紧点进去报错提示的类里查看,我发现了我是在这个类里往bean中set我的map。我保证这是我第一次这么set:


contactItem.setChooseMap(new LinkedHashMap() {

{

put("男", "1");

put("女", "2");

}

});


复制代码

明明是new了一个hashmap,但是writeObject的时候传进去的却是当前的这个类。看来确实是我姿势不对。

在我疑惑的时候,我搜到这么一篇文章:

hashmap-not-serializable

和我差不多的问题嘛,我瞅了一眼回答。

The exception message tells you exactly what the problem is: you are trying to serialize an instance of class SimpleSerializationTest, and that class is not serializable.

Why? Well, you have created an anonymous inner class of SimpleSerializationTest, one that extends HashMap, and you are trying to serialize an instance of that class. Inner classes always have references to the relevant instance of their outer class, and by default, serialization will try to traverse those.

嗯?这话大概意思就是,在一个类里创建一个匿名内部类,相当于扩展了hashmap和要序列化的类的实例。内部类会持有外部类的引用,默认情况下会遍历这些进行序列化。

卧槽!原来是这个原因!怪不得我用来new hashmap的这个类被writeObject然后报错了!因为它根本就不能被序列化嘛。

解决问题

最后我改成了这样:


LinkedHashMapgenderMap = new LinkedHashMap<>();

genderMap.put("男", "1");

genderMap.put("女", "2");

contactItem.setChooseMap(genderMap);


复制代码

问题完美解决。

以后再也不会犯这个错误了,脸疼,这回深刻的记住了。

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!

本文由 @职坐标 发布于职坐标。未经许可,禁止转载。
喜欢 | 3 不喜欢 | 0
看完这篇文章有何感觉?已经有3人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论
本文作者 联系TA

擅长针对企业软件开发的产品设计及开发的细节与流程设计课程内容。座右铭:大道至简!

  • 370
    文章
  • 6492
    人气
  • 89%
    受欢迎度

已有19人表明态度,89%喜欢该老师!

进入TA的空间
名师指导 直通车
  • 索取资料 索取资料 索取资料
  • 答疑解惑 答疑解惑 答疑解惑
  • 技术交流 技术交流 技术交流
  • 职业测评 职业测评 职业测评
  • 面试技巧 面试技巧 面试技巧
  • 高薪秘笈 高薪秘笈 高薪秘笈
TA的其他文章 更多>>
WEB前端必须会的基本知识题目
经验技巧 93% 的用户喜欢
Java语言中四种遍历List的方法总结(推荐)
经验技巧 90% 的用户喜欢
Java语言之SHA-256加密的两种实现方法详解
经验技巧 86% 的用户喜欢
java语言实现把两个有序数组合并到一个数组的实例
经验技巧 86% 的用户喜欢
WEB前端之webpack+nodejs+npm构建前端项目
经验技巧 100% 的用户喜欢
其他海同名师 更多>>
刘新华
刘新华 联系TA
实力型。激情饱满,对专业充满热情
吴翠红
吴翠红 联系TA
独创“教、学、练、测”循环教学模式
吕益平
吕益平 联系TA
熟悉企业软件开发的产品设计及开发
黄泽民
黄泽民 联系TA
擅长javase核心技术
程钢
程钢 联系TA
擅长大型企业商业网站开发和管理
经验技巧30天热搜词 更多>>

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:13167058313
小职老师的微信号:13167058313

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    ICP许可  沪B2-20190160

站长统计