Сериализация карт, которые инициализируются в конструкторах
Я только что столкнулся с интересной проблемой, связанной с сериализацией 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 ответа
Первый пример - создание анонимного внутреннего класса. Как?
Map<String, String> params = new HashMap<String, String>() {};
создаст новый класс, полученный из HashMap
(обратите внимание на следующие скобки, в которые вы можете поместить методы, члены и т. д.)
Инициализация вашей карты затем объявляет блок инициализатора таким образом:
Map<String, String> params = new HashMap<String, String>() {
{ // here }
};
и в том, что вы называете ваши методы населения.
Эта идиома хороша, но вы должны знать, что вы создаете новый класс, а не просто новый объект.
Поскольку этот класс является внутренним классом, он будет иметь неявный this
указатель на содержащий внешний класс. Ваш анонимный класс будет сериализуемым из-за его происхождения из сериализуемого класса. Однако ваш внешний класс (на который ссылается this
указатель) нет.
Инструменты как XStream
, который сериализуется в XML с помощью рефлексии, обнаружит this
указатель и попытка сериализации окружающего объекта, что также сбивает с толку.
Я хотел дополнить ответ @Brian Agnew этим предложением:
У меня был случай, когда мне нужно было немного другое поведение объекта, поэтому я расширил его возможности анонимным внутренним классом, как вы делали в примере. Внешний класс был приложением с графическим интерфейсом, и я не делал его сериализуемым, потому что в этом просто не было необходимости, поэтому, как сказал @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
передается в это. Это было много слов, чтобы сказать что-то простое, но мне хотелось бы, чтобы я сам получил этот совет раньше.