序列化在构造函数中初始化的映射

时间:2012-07-31 09:12:53

标签: java serialization hashmap

我刚遇到一个与Java序列化相关的有趣问题。

如果我的地图定义如下:

Map<String, String> params = new HashMap<String, String>() {{
  put("param1", "value1");
  put("param2", "value2");
}};

我尝试使用ObjectOutputStream将其序列化为一个文件:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(outputFile));
oos.writeObject(params);

...我得到了java.io.NotSerializableException。

但是,如果我将标准方式的值放到地图上:

Map<String, String> params = new HashMap<String, String>();
params.put("param1", "value1");
params.put("param2", "value2");

...然后序列化工作正常。

有人可以告诉我它为什么会发生这些声明之间有什么区别?我认为他们应该一样,但显然我错过了一些东西。

2 个答案:

答案 0 :(得分:10)

第一个例子是创建一个匿名内部类。怎么样?

Map<String, String> params = new HashMap<String, String>() {};

会创建一个派生自HashMap的新类(请注意以下大括号,您可以在其中放置方法,成员等。)

您的地图初始化然后声明初始化块:

Map<String, String> params = new HashMap<String, String>() { 
                                                             { // here } 
                                                           };

并且您可以调用您的填充方法。

这个成语很好,但是你必须要知道你正在创建一个新类,而不仅仅是一个新对象。

因为这个类是一个内部类,所以它有一个指向包含外部类的隐式this指针。您的匿名类将是可序列化的,因为它派生自可序列化的类。但是,您的外部类(由this指针引用)不是。

XStream这样通过反射串行化为XML的工具将发现this指针并尝试序列化周围的对象,这同样令人困惑。

答案 1 :(得分:0)

我想用这个建议补充@Brian Agnew的回答:

我有一个案例,我需要一个对象略有不同的行为,所以我使用匿名内部类扩展其功能,就像在示例中所做的那样。外部类是一个GUI应用程序,我没有使它可序列化,因为这不是必需的,因此像@Brian所说,没有匿名内部类可以序列化,即使他们扩展的类是。

在这种情况下,您只需要为反序列化类以及何时再次序列化时定义不同的行为。如果您有一个具有特定构造函数的类,请在您的类中使用这样的方法:

public FunctionalObject getNewFunctionalObject (String param1, String param2) {
    // Use an anonymous inner class to extend the behavior
    return new FunctionalObject (param1, param2) {
        { 
            // Initialization block code here
        }
        // Extended behavior goes here
    };
}

因此,当您进行反序列化时,您可以进行如下调用:

FunctionalObject fo = (FunctionalObject) objectInputStream.readObject ();
fo = getNewFunctionalObject(fo.getParam1(), fo.getParam2());

序列化时,您需要创建一个new对象,该对象是旧对象的克隆。有些类内置了这种行为,而在其他类中,您必须专门定义它。对于序列化,如果你有一个可以克隆它的构造函数,或者你的类定义了clone方法,你可以这样做:

objectOutputStream.writeObject ( fo.clone() );

然后,该对象的clone将不再是对您的匿名内部类的引用,而是对该对象的实际副本的引用,该副本是可序列化的。

在你的例子中,你可以这样做:

// Assuming objectOutputStream has already been defined
Map<String, String> params = new HashMap<String, String>() {{
    put("param1", "value1");
    put("param2", "value2");
}};
objectOutputStream.writeObject (new HashMap<String,String> (params));

这是有效的,因为HashMap类有一个构造函数,它将返回传递给它的HashMap的克隆。这说了很多简单的话,但我希望自己能早点得到这个建议。