2009年3月21日

诡异的Java HashMap的序列化Bug (ID 4756277)

今天写个小的文本处理程序,用到了一个临时类的序列化,在类中加入了一个私有变量后运行总会出现错误

加入的私有变量如下:
private ArrayList sortedEntriesByValue;

得到的运行结果是:
java.io.NotSerializableException: java.util.HashMap$Entry

真是百思不得其解。Google了半天,发现Tomcat里面有人提到这个问题,原因好像是Session类的什么地方需要修改。这个原因和我的问题真是风马牛不相及。

再仔细查找,最终发现,原来这是Java语言目前存在的一个Bug。原来HashMap对应衍生元素除了Key可以被序列化,其他的如keySet, EntrySet, Values都不能被序列化。当然,HashMap本身还是可以被序列化的。

最后我选择了新建一个ArrayList来将KeySet中的元素转换为String后存放起来,就可以实现我需要的序列化了。

呵呵,我该买彩票啊,随便玩玩居然玩到Java自带的Bug里面去了。这个Bug为什么会存在Java里面呢,而且这个Bug是2002就被发现了啊。Sigh~! Java也不是完美的啊!

详细的sun网站上登记的Bug信息如下:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4756277


Bug ID:
4756277

Votes
7

Synopsis
(coll) HashMap's keySet(), entrySet() and values() are not serializable

Category
java:classes_util

Reported Against
1.3.1

Release Fixed

State
3-Accepted, bug

Priority:
4-Low

Related Bugs

Submit Date
01-OCT-2002

Description

FULL PRODUCT VERSION :
Occurs in both 1.3.0 and 1.3.1:

java version "1.3.0_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0_02)
Java HotSpot(TM) Client VM (build 1.3.0_02, mixed mode)

java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
(Service Pack 2)

A DESCRIPTION OF THE PROBLEM :
The collections returned by calls to
java.util.HashMap#entrySet, #keySet and #values are not
Serializable which, although consistent with the
documentation, is confusing and counter-intuitive because
HashMap itself is Serializable.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a HashMap.
2. Attempt to serialize the result of calling keySet(),
entrySet() or values() on that HashMap.


EXPECTED VERSUS ACTUAL BEHAVIOR :
I would (naively, optimistically :-) assume these
collections to be Serializable, since the object from which
they come is itself Serializable.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
keySet():
Trying to serialize [two, one, three]...failed:
java.io.NotSerializableException: java.util.HashMap$1
at java.io.ObjectOutputStream.outputObject(ObjectOutputStream.java:1148)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:366)
at
test.MapCollectionsSerializeMain.trySerialize(MapCollectionsSerializeMain.java:15)
at
test.MapCollectionsSerializeMain.main(MapCollectionsSerializeMain.java:36)
entrySet():
Trying to serialize [two=two, one=one, three=three]...failed:
java.io.NotSerializableException: java.util.HashMap$3
at java.io.ObjectOutputStream.outputObject(ObjectOutputStream.java:1148)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:366)
at
test.MapCollectionsSerializeMain.trySerialize(MapCollectionsSerializeMain.java:15)
at
test.MapCollectionsSerializeMain.main(MapCollectionsSerializeMain.java:38)
values():
Trying to serialize [two, one, three]...failed:
java.io.NotSerializableException: java.util.HashMap$2
at java.io.ObjectOutputStream.outputObject(ObjectOutputStream.java:1148)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:366)
at
test.MapCollectionsSerializeMain.trySerialize(MapCollectionsSerializeMain.java:15)
at
test.MapCollectionsSerializeMain.main(MapCollectionsSerializeMain.java:40)

REPRODUCIBILITY :
This bug can be reproduced rarely.

---------- BEGIN SOURCE ----------
package test;

import java.io.*;
import java.util.*;

public class MapCollectionsSerializeMain
{

private static void trySerialize (Object o) {
try {
ObjectOutputStream stream
= new ObjectOutputStream(new ByteArrayOutputStream());
System.out.print("Trying to serialize " + o + "...");
stream.writeObject(o);
stream.flush();
System.out.println("done");
}
catch (Exception e) {
System.out.print("failed: ");
e.printStackTrace();
}
}

// ----------------------------------------------------------------------
// Entry point

public static void main (String[] args) {

Map map = new HashMap();
map.put("one", "one");
map.put("two", "two");
map.put("three", "three");

System.out.println("keySet():");
trySerialize(map.keySet());
System.out.println("entrySet():");
trySerialize(map.entrySet());
System.out.println("values():");
trySerialize(map.values());

}
}

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
For the keySet() and valueSet() cases, one could create a
new HashSet from those sets, but that's inefficient and it
doesn't cover the values() case.

The right answer in the first place is presumably that any
Serializable object which contains a Set (or xxxxx abstract
type not documented as Serializable) must implement its own
readObject and writeObject methods which (de-)serialize the
set manually. Either that, or define the object to contain
a HashSet (or xxxxx type documented as Serializable).
(Review ID: 165184)
======================================================================

Work Around

N/A

Evaluation

N/A

1 条评论:

jeffy 说...

看样子不是java 的bug的问题,是因为你的Entry 类没有实现序列化接口,而之所以转化为String 可以,是英文String类是已经实现序列化接口的。在Entry类实现Comparable接口试试