2012-07-31 10 views
9

Acabo de encontrar un problema interesante relacionado con la serialización de Java.Serializar mapas que se inicializan en los constructores

Parece que si mi mapa se define así:

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

y trato de serializarlo a un archivo con ObjectOutputStream:

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

... consigo java.io. NotSerializableException.

Sin embargo, si en vez pongo los valores al mapa de la forma estándar:

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

... entonces está bien el trabajo de serialización.

¿Alguien puede decirme por qué sucede y cuál es la diferencia entre estas declaraciones? Creo que deberían funcionar igual, pero aparentemente me falta algo.

Respuesta

10

El primer ejemplo es crear una clase interna anónima. Cómo ?

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

crearía una nueva clase derivada de HashMap (tenga en cuenta los siguientes apoyos, en la que se puede poner métodos, los miembros etc.)

Su mapa de inicialización luego declara un bloque initialiser así:

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

y en que usted llama a sus métodos de población.

Este idioma está bien, pero debe tener en cuenta que está creando una nueva clase, no solo un objeto nuevo.

Como esta clase es una clase interna, tendrá un puntero this implícito a la clase externa contenedora. Su clase anónima sería serializable debido a su derivación de una clase serializable. Sin embargo, su clase externa (a la que hace referencia el puntero this) no lo está.

Las herramientas como XStream, que se serializan en XML a través de la reflexión, descubrirán el puntero this e intentarán serializar el objeto circundante, lo que es igualmente confuso.

+1

Por 'initializador estático', ¿quiere decir' initializer de instancia'? –

+0

Entonces, ¿cuál sería la clase de inclusión esperada? – Shark

+0

@ Eng.Fouad - whoops. Enmendado –

0

me querían para complementar la respuesta de @ Brian Agnew con esta sugerencia:

tuve un caso en el que necesitaba un comportamiento ligeramente diferente de un objeto, por lo que extendió sus capacidades con una clase interna anónima como lo hizo en el ejemplo. La clase externa era una aplicación GUI, y no la convertí en serializable porque eso simplemente no era necesario, así que, como dijo @Brian, ninguna clase interna anónima podría ser serializable, incluso si las clases que estaban extendiendo.

En esta situación, simplemente tiene que definir un comportamiento diferente para cuando una clase se deserializa y cuando se vuelve a serializar.Si usted tiene una clase con un constructor específico, utilice un método como este en su clase:

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

Así que cuando están deserializar, se puede hacer una llamada como ésta:

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

Cuando seria, Deberá crear un objeto new que sea un clon del objeto anterior. Algunas clases tienen este comportamiento integrado, y en otras tendrás que definirlo específicamente. Para la serialización, si usted tiene un constructor que puede clonar, o si su clase tiene el método clone definido, usted puede hacer esto:

objectOutputStream.writeObject (fo.clone()); 

Entonces, el clone de ese objeto ya no será una referencia a su clase interna anónima, pero una referencia a una copia real del objeto, que es serializable.

En el caso de tu ejemplo, podríamos haber hecho esto:

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

Esto funciona porque la clase HashMap tiene un constructor que devolverá un clon de cualquier HashMap se pasa en ella. Fueron muchas palabras para decir algo simple, pero deseé haber tenido este consejo antes.

Cuestiones relacionadas