Сериализация карт, которые инициализируются в конструкторах

Я только что столкнулся с интересной проблемой, связанной с сериализацией 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 передается в это. Это было много слов, чтобы сказать что-то простое, но мне хотелось бы, чтобы я сам получил этот совет раньше.

Другие вопросы по тегам