2012-07-31 8 views
9

Właśnie napotkałem interesujący problem związany z serializacją Java.Serializowanie map zainicjowanych w konstruktorach

Wydaje się, że jeśli moja mapa jest zdefiniowany następująco:

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

I staram się szeregować je do pliku z ObjectOutputStream:

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

... mam java.io. NotSerializableException.

Jeśli jednak zamiast umieścić wartości mapie standardowy sposób:

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

... następnie pracować serializacji grzywny.

Czy ktoś może mi powiedzieć, dlaczego tak się dzieje i jaka jest różnica między tymi stwierdzeniami? Myślę, że powinny działać tak samo, ale najwyraźniej czegoś mi brakuje.

Odpowiedz

10

Pierwszy przykład to tworzenie anonimowej klasy wewnętrznej. W jaki sposób ?

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

by stworzyć nową klasę pochodną od HashMap (zwróć uwagę na następujące szelki, w którym można umieścić metody, członków itp)

Twoja mapa inicjalizacji następnie deklaruje blok initialiser tak:

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

oraz w tym, że nazywasz swoje metody populacyjne.

Ten idiom jest w porządku, , ale musisz pamiętać, że tworzysz nową klasę, a nie tylko nowy obiekt.

Ponieważ ta klasa jest klasą wewnętrzną, będzie zawierać domyślny wskaźnik this do zewnętrznej klasy zawierającej. Twoja anonimowa klasa będzie mogła być szeregowana z powodu jej wyprowadzenia z klasy, którą można szeregować. Jednak twoja zewnętrzna klasa (wskazana przez wskaźnik this) nie jest.

Narzędzia takie jak XStream, które serializują do XML za pomocą odbicia, wykryją wskaźnik this i spróbują serializować otaczający obiekt, co jest podobnie mylące.

+1

Przez "statyczny inicjator" rozumie się 'inicjator instancji'? –

+0

, więc jaka byłaby oczekiwana klasa obejmująca? – Shark

+0

@ Eng.Fouad - whoops. Amended –

0

chciałem uzupełnić odpowiedź @Brian Agnew jest z tej sugestii:

miałem przypadek gdzie potrzebne nieco inne zachowanie się obiektu, więc rozszerzyć swoje możliwości z anonimowej klasy wewnętrznej, jak miało to miejsce w przykład. Klasa zewnętrzna była aplikacją GUI i nie uczyniłem jej serializowalną, ponieważ to nie było konieczne, dlatego też, jak powiedział @Brian, żadne anonimowe klasy wewnętrzne nie mogły być przekształcane do postaci szeregowej, nawet jeśli klasy, które rozszerzały, były.

W tej sytuacji po prostu trzeba zdefiniować inne zachowanie, gdy klasa jest deserializowana i gdy jest ponownie serializowana.Jeśli masz klasę z określonego konstruktora, należy użyć metody takie jak to w swojej klasie:

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

Więc kiedy deserializacji, można wykonać połączenie tak:

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

Kiedy szeregowania, będziesz musiał stworzyć obiekt new, który jest klonem starego obiektu. Niektóre klasy mają takie zachowanie wbudowane, aw innych trzeba je zdefiniować. Dla serializacji, jeśli masz konstruktora, który może go klonować, lub jeśli klasa ma metodę clone zdefiniowany, można to zrobić:

objectOutputStream.writeObject (fo.clone()); 

Następnie clone tego obiektu nie będzie już odniesienie do listy anonimowa klasa wewnętrzna, ale odwołanie do rzeczywistej kopii obiektu, która jest przekształcalna do postaci szeregowej.

W przypadku Twojego przykład, w mogłeś po prostu zrobić to:

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

To działa, ponieważ klasa HashMap ma konstruktora, że ​​powróci klonem cokolwiek HashMap jest przekazywana do niego. To było dużo słów, aby powiedzieć coś prostego, ale żałowałem, że wcześniej nie otrzymam tej rady.