Serializing maps which are initialized in constructors

Viewed 185

I've just encountered an interesting problem related to Java serialization.

It seems that if my map is defined like this:

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

And I try to serialize it to a file with ObjectOutputStream:

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

...I get java.io.NotSerializableException.

However, if instead I put values to the map the standard way:

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

...then serialization work fine.

Can anybody tell me why it happens and what's the difference between these statements? I think they should work the same, but apparently I'm missing something.

2 Answers

I wanted to supplement @Brian Agnew's answer with this suggestion:

I had a case where I needed slightly different behavior out of an object, so I extended its capabilities with an anonymous inner class as you did in the example. The outer class was a GUI application, and I did not make it serializable because that just wasn't necessary, so therefore like @Brian said, no anonymous inner classes could be serializable, even if the classes they were extending were.

In this situation, you simply have to define different behavior for when a class is deserialized and when it is again serialized. If you have a class with a specific constructor, use a method like this in your class:

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
    };
}

So when you are deserializing, you can make a call like this:

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

When serializing, you will need to create a new object that is a clone of the old object. Some classes have this behavior built in, and in others you will have to specifically define it. For serialization, if you have a constructor that can clone it, or if your class has the clone method defined, you could do this:

objectOutputStream.writeObject ( fo.clone() );

Then, the clone of that object will no longer be a reference to your anonymous inner class, but a reference to an actual copy of the object, which is serializable.

In your example's case, you could have just done this:

// 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));

This works because the HashMap class has a constructor that will return a clone of whatever HashMap is passed into it. That was a lot of words to say something simple, but I wished I would have had this advice sooner myself.

Related