Ich bin auf etwas gestoßen, das mich neugierig gemacht hat. Anscheinend behält die Serialisierung eines Datensatzes mit einer zyklischen Referenz den Zyklus nicht bei, wenn er erneut deserialisiert wird, er wird null. Wenn Sie dieselbe Übung mit einer regulären Klasse durchführen, ist der Zyklus immer noch vorhanden.
Warum ist das so?
Jeder Hinweis oder auch relevante Ausschnitte aus dem JLS wären willkommen.
Haftungsausschluss
Mir ist bewusst, dass Javas integrierte Serialisierung vorhanden ist schlecht und sollte nicht verwendet werden, dasselbe gilt für zyklische Referenzen, die nicht als transient markiert sind. Das Setup ist gekünstelt und ich werde es nirgendwo verwenden. Ein Student ist darauf gestoßen und ich bin neugierig, warum sich Java so verhält.Minimalbeispiel
Ich habe es auf ein Zyklus-Setup wie folgt reduziert:
Code: Select all
normal.ref.o -> normal
record.ref().o -> record
Code: Select all
import java.io.*;
import java.util.Objects;
public class Test {
static class Ref implements Serializable {
Object o;
@Override
public String toString() {
return "Ref[o=%s]".formatted(Objects.hashCode(o));
}
}
static class Normal implements Serializable {
int x;
Ref ref;
@Override
public String toString() {
return "Normal[x=%d, ref=%s]".formatted(x, ref);
}
}
record Record(int x, Ref ref) implements Serializable {}
public static void main(String[] args) throws Exception {
// Setup
Normal normalBefore = new Normal();
normalBefore.x = 5;
normalBefore.ref = new Ref();
normalBefore.ref.o = normalBefore; // cycle
// normalBefore.ref.o == normalBefore
Record recordBefore = new Record(5, new Ref());
recordBefore.ref().o = recordBefore; // cycle
// recordBefore.ref().o == recordBefore
// Test
Normal normalAfter = serializeAndDeserialize(normalBefore);
Record recordAfter = serializeAndDeserialize(recordBefore);
System.out.println("Normal before:\t" + normalBefore);
System.out.println("Normal after:\t" + normalAfter);
System.out.println("Record before:\t" + recordBefore);
System.out.println("Record after:\t" + recordAfter);
System.out.println("recordAfter.ref().o:\t" + recordAfter.ref().o);
}
static T serializeAndDeserialize(T obj) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
T result = (T) ois.readObject();
ois.close();
return result;
}
}
Code: Select all
Normal before: Normal[x=5, ref=Ref[o=1732398722]]
Normal after: Normal[x=5, ref=Ref[o=1579572132]]
Record before: Record[x=5, ref=Ref[o=1323468385]]
Record after: Record[x=5, ref=Ref[o=0]]
recordAfter.ref().o: null
Der Record lässt jedoch den zyklischen Rückverweis auf sich selbst innerhalb des Ref-Wrapper-Objekts fallen, er wird null.
Code-Anleitung
Um das Lesen des Codes zu erleichtern, besteht er aus einem Wrapper für den Zyklus, Ref:
Code: Select all
static class Ref implements Serializable {
Object o;
@Override
public String toString() {
return "Ref[o=%s]".formatted(Objects.hashCode(o));
}
}
Code: Select all
finalDann die eigentlichen Datenträgerklassen:
Code: Select all
static class Normal implements Serializable {
int x;
Ref ref;
@Override
public String toString() {
return "Normal[x=%d, ref=%s]".formatted(x, ref);
}
}
record Record(int x, Ref ref) implements Serializable {}
Dann haben wir eine Methode, die einfach jedes gegebene Objekt serialisiert und deserialisiert und das Zyklus-Setup in main sowie den abschließenden Test.
Mobile version