PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [JAVA] Problem mit Serialisierung


Nasenbaer
2009-07-02, 16:30:23
Hi,
ich habe ein Problem mit der Serialisierung in Java. Ich habe eine einfache Klasse, die nur ein Boolean enthält. Ich erzeuge davon eine Instanz und schreibe sie in einen ObjectOutputStream, danach switche ich den Wert des Booleans und schreibe das Objekt nochmal in den Stream.
Wenn ich die beiden Instanzen nun wieder aus dem Stream lesen, dann haben beide Instanzen den gleichen Boolean-Wert.

Irgendwas muss ich da falsch verstanden haben. Kann mir da jemand helfen?

Hier noch der Code:


public class SerApp {

public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:\\deleteMe.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);

Test out = new Test(true);
System.out.println("OUT:\n" + out._data);
oos.writeObject(out);
out.toggle();
System.out.println(out._data);
oos.writeObject(out);
oos.close();
fos.close();

FileInputStream fis = new FileInputStream("C:\\deleteMe.dat");
ObjectInputStream ois = new ObjectInputStream(fis);

Test in = (Test)ois.readObject();
System.out.println("IN:\n" + in._data);
in = (Test)ois.readObject();
System.out.println(in._data);
ois.close();
fos.close();
}
}



import java.io.Serializable;

public class Test implements Serializable {
public Boolean _data;


public Test(Boolean data) {
_data = data;
}


public void toggle() {
if (_data)
_data = false;
else
_data = true;
}
}


Und die Ausgabe:
OUT:
true
false
IN:
true
true

Abnaxos
2009-07-02, 16:56:54
Das Problem ist, dass ObjectOutputStream “zu schlau” ist.

Konkret: Er merkt sich die Objekt-Identitäten, um beim Auslesen auch einen kompletten Objekt-Graphen wiederherstellen zu können. Beispiel:


A referenziert C
B referenziert C


Wenn du jetzt A und B serialisierst, referenzieren beide dasselbe Objekt C. Wenn du das wieder ausliest, wird der Graph auch wieder so hergestellt.

Weil du in deinem Beispiel zwei Mal dasselbe Objekt serialisierst, wird beim zweiten Mal nur noch eine Referenz auf das erste in den Stream geschrieben. Dein Code etwas ergänzt:

// ...
Test in = (Test)ois.readObject();
System.out.println("IN:\n" + in._data);
Test in2 = (Test)ois.readObject();
System.out.println(in2._data);
System.out.println("==: "+(in==in2));

Ergibt:
$ java -cp . SerApp
OUT:
true
false
IN:
true
true
==: true

Nasenbaer
2009-07-02, 17:16:28
Und wie bekomme ich das gelöst?
In meinem konkreten Beispiel nutze ich ne Socket-Kommunikation mit den Object-Stream. Jedes mal wenn sie ein Objekt ändert, dann schicke ich es an den Client und der deserialisiert das dann.
Wenn aber dem 2. mal nun natürlich nur Referenzen versendet werden dann bekomme ich die Änderungen natürlich nicht mit. Aber ich brauche natürlich das geänderte Objekt. Irgendwelche Ideen?

Abnaxos
2009-07-02, 17:25:09
Indem du writeUnshared() (http://java.sun.com/javase/6/docs/api/java/io/ObjectOutputStream.html#writeUnshared(java.lang.Object)) verwendest:

Writes an "unshared" object to the ObjectOutputStream. This method is identical to writeObject, except that it always writes the given object as a new, unique object in the stream (as opposed to a back-reference pointing to a previously serialized instance).

Nasenbaer
2009-07-02, 18:14:43
Erstmal Danke für den Tipp - mit der geposteten Test App klappt das auch. In meinem Anwendungsbeispiel sind die Nutzdaten aber nochmals in einer Mesage-Klasse gekapselt.
Wenn ich nun die TestApp hier wiefolgt ändere:

Neue Kapsel-Klasse:

public class Kapsel implements Serializable {

public Test _gekapselteDaten = null;


public Kapsel(Test data) {
_gekapselteDaten = data;

}

}


Geänderte Main-Methode:

public class SerApp {

public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("C:\\deleteMe.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);

Kapsel out = new Kapsel(new Test(true));
System.out.println("OUT:\n" + out._gekapselteDaten._data);
oos.writeUnshared(out);
out._gekapselteDaten.toggle();
System.out.println(out._gekapselteDaten._data);
oos.writeUnshared(out);
oos.close();
fos.close();

FileInputStream fis = new FileInputStream("C:\\deleteMe.dat");
ObjectInputStream ois = new ObjectInputStream(fis);

Kapsel in = (Kapsel)ois.readObject();
System.out.println("IN:\n" + in._gekapselteDaten._data);
in = (Kapsel)ois.readObject();
System.out.println(in._gekapselteDaten._data);
ois.close();
fos.close();
}
}


Wenn es so aussieht, dann habe ich das gleiche Problem wie vorher. :(

mik
2009-07-02, 19:33:14
Ist klar die Objekte der Kapselung haben beide die selbe Referenz auf die selben gekapselten Daten

Was spricht dagegen das du ein neues Objekt erzeugst bzw. klonst?

Warum Serialisierst du eigentlich?
Was ist das Einsatzgebiet?

Nasenbaer
2009-07-02, 19:34:37
Habs jetzt rausgefunden: writeUnshared() berücksichtigt keine eingebetteten Objekte innerhalb des zu serialisierenden Objektes. Um Referenzen innerhalb der Objekte zu unterbinden muss man nach einem Schreibvorgang ObjectOutputStream.reset() aufrufen - dann klappt alles. :)

Nasenbaer
2009-07-02, 19:36:33
Ist klar die Objekte der Kapselung haben beide die selbe Referenz auf die selben gekapselten Daten

Was spricht dagegen das du ein neues Objekt erzeugst bzw. klonst?

Warum Serialisierst du eigentlich?
Was ist das Einsatzgebiet?
Das Objekt neu zu erzeugen wäre recht aufwändig, weil weder clone() noch der Copy-Konstruktor implementiert sind und ich die Klasse auch selbst nicht geschrieben habe.

Ich serialisiere, weil ich Objekte über Sockets im Netzwerk übertragen will.

mik
2009-07-02, 20:13:04
Habs jetzt rausgefunden: writeUnshared() berücksichtigt keine eingebetteten Objekte innerhalb des zu serialisierenden Objektes.

Das hab ich mit selbe referenzen gemeint, hab mich nicht sehr verständlich ausgedrückt! (Nutze Serialisierung eigentlich so gut wie nie) ;-)

Nasenbaer
2009-07-02, 20:27:00
Das hab ich mit selbe referenzen gemeint, hab mich nicht sehr verständlich ausgedrückt! (Nutze Serialisierung eigentlich so gut wie nie) ;-)
Das hab ich auch so verstanden aber das Erstellen deines und meines Beitrages hat sich überschnitten. :)